分享交流前沿计算机技术

0%

Verilog学习笔记

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);
//code
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) //错误。应改为 !clear。因为negedge时,clear电平从1变成0,所以该语句将永远不会触发。
qout=0;
else
qout=in;

always也可以不加@,就相当于C++中的while(true)循环。如果加了就是有判断条件的while循环。

注意事项:

  1. 不要在不同的always块内为同一个变量赋值。
  2. 不要在同一个always块内同时使用阻塞赋值(=)和非阻塞赋值(<=)。
  3. 使用always块描述组合逻辑时使用阻塞赋值(=),在使用always块描述时序逻辑时使用非阻塞赋值(<=)。简单理解就是,在电平敏感的always块内使用阻塞赋值,在边沿敏感的always块内使用非阻塞赋值。
  4. 任何在always块内“被赋值的变量”都必须是寄存器型(reg)。即<=或=左边的信号,必须是reg型。
  5. always的敏感列表中可以同时包括多个电平敏感事件,也可以同时包括多个边沿敏感事件,但不能同时有电平和边沿敏感事件。另外,敏感列表中,同时包括一个信号的上升沿和它的下降沿敏感事件也是不允许的,因为这两个事件可以合并为一个电平事件。电平触发比时钟沿触发更容易受到干扰,在高速时容易受干扰,不够好(解决方法见:https://zhidao.baidu.com/question/1512023017981552420.html)。
  6. always不可嵌套使用。
  7. 一个语句块内多个非阻塞赋值是并行执行的(顾名思义),而阻塞赋值则是顺序执行。

1.5 reg&wire

reg使用过程赋值语句,wire使用连续赋值语句,regwire在赋值时实质上表现相同,只不过reg只能在alwaysinitial中赋值,而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
x1=count[1]
x2=x1[0]

还可以通过文件初始化:

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文件范例:

1
2
3
4
5
6
//  Comments are allowed 
CC // This is first address i.e 8'h00
AA // This is second address i.e 8'h01
@55 // Jump to new address 8'h55
5A // This is address 8'h55
69 // This is address 8'h56

注意事项:

  1. //可以使用注释。
  2. @表示跳到新地址。

只初始化一部分数组可以用两种方法,一种是用@跳转地址,另一种是设定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 =和<=

“=”为阻塞赋值,“<=”为非阻塞赋值。遵循以下使用原则:

  1. 时序逻辑一定用非阻塞赋值<=,一旦看到always敏感列表中有posedge(or negedge,下同)就用<=。组合逻辑一定用阻塞赋值=,一旦没有posedge,或者看到assign,就用=。虽然语法上支持你在always里用=,或者在assign里用<=,但这是一种非常愚蠢的做法。
  2. 时序逻辑和组合逻辑分成不同模块,即一个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;//二者同时进行,因而c不会立刻得到a的值(和C不一样),因为在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) // synopsys parallel_case
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
  1. 只写了if没写else,或者case没有写全所有条件分支,编译器认为条件不满足时,会引进锁存器保存原值。
  2. 时序逻辑可利用上述特性来保持状态。
  3. 组合逻辑必须列出所有条件分支,否则会产生隐含锁存器。

在设计电路时,一个很重要的原则是尽量少的出现锁存器。锁存器具备下列三个缺点:

  1. 对毛刺敏感(使能信号有效时,输出状态可能随输入多次变化,产生空翻) ,不能异步复位,因此在上电后处于不确定的状态,这对于下一级电路是极其危险的。
  2. 锁存器会使静态时序分析变得非常复杂,不具备可重用性。 (首先, 锁存器没有时钟参与信号传递,无法做 STA;其次,综合工具会将 latch 优化掉,造成前后仿真结果不一致)
  3. 在 PLD 芯片中,基本的单元是由查找表和触发器组成的,若生成锁存器反而需要更多的资源。根据锁存器的特点可以看出,在电路设计中,要对锁存器特别谨慎,如果设计经过综合后产生出和设计意图不一致的锁存器,则将导致设计错误,包括仿真和综合。因此,在设计中需要避免产生意想不到的锁存器。

1.9 function

1
2
3
4
5
6
7
8
9
function [x:0] funcname;//位宽即返回值位宽,也可以加signed表示有符号
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
  1. 主要面向功能模拟,通常不具有可综合性,只用于testbench。
  2. 模拟0时刻开始执行,只执行一次。
  3. 同一模块含有多个initial模块,模拟开始时并行执行,initial模块内的语句顺序执行。
  4. 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 #5clk=!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);//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")//return 000...00010(32 bits)
handle2=$fopen("file2.dat")//return 000...00100
handle3=$fopen("file3.dat")//return 000...01000
//000...00001 表示transcript窗口,这个是默认开启的
$display("%h %h %h",handle1, handle2, handle3);
end
endmodule

$readmemb&$readmemh

1
2
3
4
5
6
/*file contents:
1234 5678 9012*/
'timescale 10ns/1ns
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 1ns/1ps //一般在 testbench 的开头定义时间单位和仿真精度,前者是时间单位,后者是仿真时间精度。
#200 //表示过了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);//2*2 crosspoint switch
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);//4*4 crosspoint switch
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:

  1. 也可以通过if-else嵌套实现,但是那样代码比较繁琐,二者生成的电路几乎是一样的(vivado综合时内部有优化)
  2. 关于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);
//上面的两行分别表示:a和b进行异或运算值传递给cout,a和b进行与运算值传递给sum
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;
//行为描述(另一种方法)
// {cout, sum} = a + b;
end
endmodule
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//行为描述 case ,真值表
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;
// assign {cout, 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);
//行为描述(另一种方法)
// {cout, sum} = a + b + cin;
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
//结构描述的4为级联(串行进位)全加器
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
//4位全加器的行为描述
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);//流水线加法器,4级流水线
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;
//行为描述(另一种方法)
// {cout, dout} = a - b;
end
endmodule

3.6.2 全减器

1
2
3
4
5
6
7
8
9
10
//行为描述,1位全减器
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
//行为描述,4位全减器
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,也就是锁存的过程)。

不用锁存器的原因:

  1. 锁存器容易产生毛刺。
  2. 锁存器在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) //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) //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 ); //模块名及参数定义,范围至endmodule。
  input R, S, CLK;         //输入端口定义
  output Q, QB;          //输出端口定义
  reg Q;              //寄存器定义
  assign QB = ~Q;         //assign语句,QB=/Q。
  always @( posedge CLK )     //在CLK的上跳沿,执行以下语句。
case ({ R, S })         //case语句,至于endcase为止。
    1: Q <= 1;          //当R,S的组合为01,则令Q=1。
    2: Q <= 0;          //当R,S的组合为01,则令Q=1。
    3: Q <= 1'bx;         //当R,S的组合为11,则令Q为1bit的数,数值为不定(x)。
  endcase             //case语句结束
endmodule              //模块结束

带有R端D触发器

1
2
3
4
5
6
7
8
9
10
module R_SY_D_FF ( RB, D, CLK, Q, QB );  //模块名及参数定义,范围至endmodule。
  input RB, D, CLK;           //输入端口定义
  output Q, QB ;            //输出端口定义
  reg Q;                //寄存器定义

  assign QB = ~Q;             //assign语句,QB=/Q。
  always @( posedge CLK or negedge RB ) //如果CLK端有上跳或RB端有下跳脉冲,
                      //则执行下面(Q <= ( !RB )? 0: D;)的语句。
  Q <= ( !RB )? 0: D;           //如果RB是低电平,则Q=0,否则Q=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 // reg8

异步清零

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; // Allow N to be changed
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 // regN

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 <= 0b'zzzzzzzz;
qtemp <= din;
end
else begin
dout <= qtemp;
qtemp <= 0b'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 //24十进制
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, // Width of the register required
parameter N = 6// We will divide by 12 for example in this case
)
(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 , // Clock Input
address , // Address Input
data_in , // Data Input
cs , // Chip Select
we , // Write Enable/Read Enable
oe ,// Output Enable
data //Output 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];
// If cs == 0, use synchronous read RAM, else, use asynchronous read RAM
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

//Synchronous Read
always @ (posedge clk) begin
if (!cs && !we && oe)
data_temp_0 <= mem[address];
else
data_temp_0 <= data_temp_0;
end
//Asynchronous Read
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
);

//internal variables
reg [7:0] data_temp_0;//synchronous
reg [7:0] data_temp_1;//synchronous
reg [7:0] adata_temp_0;//asychronous
reg [7:0] adata_temp_1;//asychronous
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

//synchronous
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

//asynchronous
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
);

//internal variables
reg [7:0] data_temp_0;//synchronous
reg [7:0] data_temp_1;//synchronous
reg [7:0] adata_temp_0;//asychronous
reg [7:0] adata_temp_1;//asychronous
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

//synchronous
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

//asynchronous
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型

  1. Moore型状态机输出仅和状态机的当前状态有关,与外部输入无关。Mealy型状态机的输出与当前状态和输入都有关。
  2. 体现在状态转移图上就是,Moore机的输出在状态圆圈内,Mealy机的输出在转移曲线上。
  3. Moore完全描述状态转移图会比Mealy机多一个状态。
  4. 体现在verilog代码中就是,Moore机的最后输出逻辑只判断state,Mealy机的输出逻辑中判断。

4.4.2 有限状态机的设计

  1. 分析设计要求,列出全部可能状态
  2. 画出状态转移图
  3. 用HDL语言描述状态机

4.4.3 有限状态机的verilog设计流程

以10010序列检测机为例。

  1. 定义输入输出端口,并用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;
  2. 用两个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
  3. 用单独一个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
  4. 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 分析步骤

  1. 分别用符号标注各级门的输出
  2. 从输入端到输出端逐级写出输入变量到输出变量的逻辑表达式,最后得到输入变量表示的输出函数表达式。需要时用卡诺图或公式化简法化简。
  3. 列真值表
  4. 根据真值表和函数表达式确定电路的逻辑功能。难以用语言描述时列真值表即可。

5.1.2 设计步骤

  1. 进行逻辑抽象:

                          1. 分析因果关系,确定输入输出变量。
                              2. 定义逻辑状态含义
                              3. 根据因果关系列出真值表
    
  2. 确定输出函数,并利用卡诺图或公式法化简

  3. 选定器件类型:SSI、MSI、PLD等
  4. 根据器件类型将逻辑函数化简或变换成适当形式
  5. 画出逻辑电路图

5.1.3 竞争与冒险

竞争:当一个逻辑门的两个输入端信号同时向相反方向变化,而变化的时间有差异。
冒险:两个输入端信号取值的变化方向是相反时,如门电路输出端的逻辑表达式简化成两个互补信号相乘或相加,由竞争而可能产生输出干扰脉冲的现象。
由于竞争而在电路输出端可能产生尖峰脉冲的现象叫做竞争-冒险
消除办法
一、接入滤波电容。优点:简单。缺点:增加了波形的上升和下降时间,波形变坏。
二、引入选通脉冲。优点:简单。缺点:正常的输出信号也会变成脉冲信号,宽度与选通脉冲相同,且此选通脉冲必须与输入信号同步。
三、修改设计(废话)。比如把$Y = AB + \overline{A}C$化成$Y = AB + \overline{A}C + BC$。