数字逻辑期末复习(3)

加法器

半加器(Half Adder)

  • 实现一位加法的器件
  • 由真值表可以看出 C(cout),也就是进位,相当于a&b,**S(sum)**相当于a^b

全加器(Full Adder)

  • 相较于全加器,多了一个输入C(cout)
  • 可以拆分成两个半加器
  • 利用半加器做全加器

  • 半加器与全加器的区别:半加器只有两个输入,全加器多了一个输入(cin),这个cin可以理解为上一个半加器的进位(cout)

1
2
3
4
5
6
7
//代码实现  最简便的写法  相当于三个数加法
module full_adder(
input x, y, cin,
output cout, sum
);
assign {cout, sum} = x + y + cin;
endmodule

波纹进位加法器

  • 结构:由N个一位加法器串联而成
  • 特点:结构直观简单,但是高位运算必须等到低位进位来到后才能进行,导致运行速度慢

选择进位加法器

  • 波纹加法器缺点:加法器计算进位延迟相当慢,第二阶段加法器在第一阶段加法器完成前无法开始计算进位输出
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
module top_module(
input [31:0] a,
input [31:0] b,
output [31:0] sum
);

wire [15:0] sum_lower; // 低16位的结果
wire [15:0] sum_upper_0; // 高16位的结果 (假设cin=0)
wire [15:0] sum_upper_1; // 高16位的结果 (假设cin=1)
wire sel; // 来自低16位的进位,用于选择信号

// 1. 低 16 位加法器
add16 adder_low (
.a(a[15:0]),
.b(b[15:0]),
.cin(1'b0),
.sum(sum_lower),
.cout(sel) // 这个进位输出决定高位选谁
);

// 2. 高 16 位加法器 (假设进位为 0)
add16 adder_high_cin0 (
.a(a[31:16]),
.b(b[31:16]),
.cin(1'b0),
.sum(sum_upper_0),
.cout() // 这里不需要处理高位的cout
);

// 3. 高 16 位加法器 (假设进位为 1)
add16 adder_high_cin1 (
.a(a[31:16]),
.b(b[31:16]),
.cin(1'b1),
.sum(sum_upper_1),
.cout() // 这里不需要处理高位的cout
);

// 4. 多路复用器与结果拼接
// 如果 sel (低位进位) 为 1,选 sum_upper_1,否则选 sum_upper_0
// 最后将高 16 位和低 16 位拼接起来
assign sum = { (sel ? sum_upper_1 : sum_upper_0) , sum_lower };

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
36
37
38
39
40
41
42
43
module top_module(
input [31:0] a,
input [31:0] b,
input sub,
output [31:0] sum
);

// 1. 处理 B 输入:
// 创建一个中间变量 b_n (b_new的意思)
// 利用 Verilog 的复制操作符 {32{sub}} 把 1 位的 sub 扩展成 32 位,
// 然后与 b 进行按位异或。
// 如果 sub=0, b_xor 就是 b;如果 sub=1, b_xor 就是 ~b。
wire [31:0] b_xor;
assign b_xor = b ^ {32{sub}};

// 这里的 {32{sub}} 就是题目提示的 "replicated 32 times"

// 定义一个中间变量来连接低位的 cout 和高位的 cin
wire carry_middle;

// 2. 实例化低 16 位加法器
// 注意:b 输入要连我们处理过的 b_xor[15:0]
// 注意:cin 直接连 sub (实现 +1)
add16 adder_low (
.a(a[15:0]),
.b(b_xor[15:0]),
.cin(sub),
.sum(sum[15:0]),
.cout(carry_middle) // 输出进位给高位
);

// 3. 实例化高 16 位加法器
// 注意:b 输入连 b_xor[31:16]
// 注意:cin 连低位产生的 carry_middle
add16 adder_high (
.a(a[31:16]),
.b(b_xor[31:16]),
.cin(carry_middle),
.sum(sum[31:16]),
.cout() // 最后的进位通常不需要,留空即可
);

endmodule

避免锁存器

  • 注意:在设计组合逻辑电路时,如果使用了 if 语句,必须确保所有情况都有明确的赋值(比如必须写上 else),否则电路会产生“记忆”功能,变成锁存器,这通常是设计错误。
  • 纯组合逻辑设计中,不希望有记忆功能

例题

  • 假设你正在为一个游戏制作一个电路,用于处理来自 PS/2 键盘的扫描码 (Scancodes)

    输入:一个 16 位的扫描码信号 (scancode).

    输出:4 个方向信号(up, down, left, right),代表是否按下了对应的箭头键。

    • 16'he06b -> 左箭头 (Left Arrow)
    • 16'he072 -> 下箭头 (Down Arrow)
    • 16'he074 -> 右箭头 (Right Arrow)
    • 16'he075 -> 上箭头 (Up Arrow)
    • 其他任何值 -> 无按键 (None)
  • 如何避免锁存器:所右输出变量在所有可能的执行路径下都被赋值

  • 错误代码:

    1
    2
    3
    4
    case (scancode)
    16'he075: up = 1; // 这里的 bug 是:down, left, right 没有赋值
    // ...
    endcase
  • 电路综合器会认为:“当按键是 UP 时,你只告诉 up 变 1,没告诉 down 应该是多少,所以必须保持 down 原来的值不变”。这就产生了一个锁存器。

  • 为了修复问题,在每一种case给四个变量赋值,所以我们要进行一个预设默认值(通常是0)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
always @(*) begin
// 1. 先给所有输出赋默认值 0 (防止产生锁存器)
up = 1'b0;
down = 1'b0;
left = 1'b0;
right = 1'b0;

// 2. 这里的赋值会覆盖掉上面的默认值
case (scancode)
16'he06b: left = 1'b1; // 只需写这一个,其他三个自动保持上面的 0
16'he072: down = 1'b1;
16'he074: right = 1'b1;
16'he075: up = 1'b1;
// default 甚至都可以不写,因为上面已经有默认值了
endcase
end

编码器 / 译码器

概念

  • 编码器把输入信号转换为特定的编码,用输出的编码来表示相应的输入信号
  • 通常编码器有 N 个输入端,有 n 个输出端,应该满足 N <= 2的n次方
  • 译码器:将先前编码过的数据解码,是编码器的逆过程

描述译码器/编码器的语句

  • if……else及其嵌套结构
  • case/casex/casez结构

译码器

基本2-4译码器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
module dec2to4 (W, Y);
input [1:0] W;
output reg [0:3] Y;

// 使用组合逻辑的 always 块,敏感列表为 W (或者用 *)
always @(W) begin
case (W)
2'b00: Y = 4'b1000; // 对应真值表第一行:W=00, y0=1
2'b01: Y = 4'b0100; // 对应真值表第二行:W=01, y1=1
2'b10: Y = 4'b0010; // 对应真值表第三行:W=10, y2=1
2'b11: Y = 4'b0001; // 对应真值表第四行:W=11, y3=1
default: Y = 4'b0000; // 默认情况,虽然在这里不会发生,但是好习惯
endcase
end

endmodule
  • 拓展:带使能端的2-4译码器,组合逻辑,行为级描述,加一个if……else判断即可
  • 4-16译码器:可以用两个3-8译码器,或者四个2-4译码器级联而成

编码器

优先编码器

  • 优先编码器是一种组合逻辑电路,它的功能是:在输入的多个二进制位中,找到第一个为 ‘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
28
29
30
// synthesis verilog_input_version verilog_2001
module top_module (
input [3:0] in,
output reg [1:0] pos );
always @(*)begin

case(in)
4'h0: pos = 0;//0000
4'h1: pos = 0;//0001
4'h2: pos = 1;//0010
4'h3: pos = 0;//0011
4'h4: pos = 2;//0100
4'h5: pos = 0;//0101
4'h6: pos = 1;//0110
4'h7: pos = 0;//0111
4'h8: pos = 3;//1000
4'h9: pos = 0;//1001
4'hA: pos = 1;//1010
4'hB: pos = 0;//1011
4'hC: pos = 2;//1100
4'hE: pos = 1;//1110
4'hF: pos = 0;//1111
default: pos = 0;
endcase
end



endmodule

  • **casez语句:**会更方便一点
  • casez语句中:不在意z所代表的是什么,下面代码是一个8为优先编码器的例子,运用的casez语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// synthesis verilog_input_version verilog_2001
module top_module (
input [7:0] in,
output reg [2:0] pos );

always @(*) begin
casez(in[7:0])
8'bzzzzzzz1: pos = 0;
8'bzzzzzz10: pos = 1;
8'bzzzzz100: pos = 2;
8'bzzzz1000: pos = 3;
8'bzzz10000: pos = 4;
8'bzz100000: pos = 5;
8'bz1000000: pos = 6;
8'b10000000: pos = 7;
default: pos = 0;
endcase
end


endmodule

循环

for循环

  • **例题:**给定一个100位输入矢量[99:0],反转其位序。
  • 解释
    • integer i;: 在 always 块之外声明循环变量 i。在 Verilog 中,for 循环的索引变量通常声明为 integer 类型。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
module top_module( 
input [99:0] in,
output [99:0] out
);

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

endmodule

100位完整加法器

  • 注意要点:generate
  • generate 是 Verilog 中用来批量生成电路代码的一种特殊语法结构。
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
module top_module( 
input [99:0] a, b,
input cin,
output [99:0] cout,
output [99:0] sum );


genvar i; //定义生成变量
generate
for(i=0;i<100;i=i+1)begin:
adder_loop
if(i==0)begin
adder fa_inst(
.x(a[i]),
.y(b[i]),
.cin(cin),
.cout(cout[i]),
.sum(sum[i])
);
end
else begin
adder fa_inst(
.x(a[i]),
.y(b[i]),
.cin(cout[i-1]),
.cout(cout[i]),
.sum(sum[i])
);
end
end
endgenerate

endmodule

//定义全加器模块
module adder(
input x,y,cin,
output cout,sum
);
assign {cout,sum} = x + y + cin;
endmodule
  • generate用法:
    1. genvar:定义循环变量
    2. generate……endgenerate:包裹生成代码的块
    3. for:循环体,描述如何重复生成,循环体必须取名字
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
genvar i; //定义生成变量
generate
for(i=0;i<100;i=i+1)begin:
adder_loop //循环体名字
if(i==0)begin
adder fa_inst(
.x(a[i]),
.y(b[i]),
.cin(cin),
.cout(cout[i]),
.sum(sum[i])
);
end
else begin
adder fa_inst(
.x(a[i]),
.y(b[i]),
.cin(cout[i-1]),
.cout(cout[i]),
.sum(sum[i])
);
end
end
endgenerate

组合逻辑电路

  • 功能上,组合逻辑电路在任意时刻的输出仅和电路当前的输入有关
  • 内部结构来看,组合电路都是淡出有逻辑单元组成,不含存储单元,电路无记忆功能

多路选择器(数据选择器)

  • 多路选择器是一个多输入单输出的组合逻辑电路
  • 根据选择码,从多个数据流选取一个输出
  • 典型Verilog描述语句有以下3种
    1. assgin语句+条件操作符(三目操作符)
    2. if……else及其嵌套语句
    3. case多分支语句
  • 如图:n个输入,但是只选择一个进行输出
  • sel位数m满足上图红色式子

多路选择器的编码风格

  • 二选一:if else
  • 四选一以上:case
  • 多选一超过8输入:建议拆分成多个小规模选通器

一些代码

1
2
3
4
5
6
7
8
9
10
11
12
//256选1,case也不好用了,这时候运用Verilog 的数组索引语法
//在 Verilog 里,只要你从向量中取出的位数是固定的(这道题只取 1 位),那么索引值(下标)可以是变量
module top_module(
input [255:0] in,
input [7:0] sel,
output out
);

// 直接用 sel 作为下标去 in 里面取值
assign out = in[sel];

endmodule
  • 注意:wire变量一定是线网,reg变量未必是寄存器

四选一

1
2
3
4
5
6
7
8
9
module mux4to1(
input w0,w`1,w2,w3,
input[1:0]S,
output f;
assign f = S[1] ? (S[0] ? w3 : w2) : (S[0] ? w1 : w0);

);

endmodule //三目运算符