Contents目录
这个是一生一芯E4部分的数字电路实验,记录一下我的完成过程。
Decoder and Encoder
译码器是将n个状态翻译成$2^n$个信号

编码器是将$2^n$个信号编码成n个状态

通过这两个器件我们就可以把信息/状态编码成信号在电路中传输(我的世界中的编码全物品实际上就是将物品信息转换成信号)
我们可以用Verilog来模拟器件的实现:
module decode24(x,en,y);
input [1:0] x;
input en;
output reg [3:0]y;
always @(x or en)
if (en)
begin
case (x)
2'd0 : y = 4'b0001;
2'd1 : y = 4'b0010;
2'd2 : y = 4'b0100;
2'd3 : y = 4'b1000;
endcase
end
else y = 4'b0000;
endmodule
module encode42(x,en,y);
input [3:0] x;
input en;
output reg [1:0]y;
always @(x or en) begin
if (en) begin
case (x)
4'b0001 : y = 2'b00;
4'b0010 : y = 2'b01;
4'b0100 : y = 2'b10;
4'b1000 : y = 2'b11;
default: y = 2'b00;
endcase
end
else y = 2'b00;
end
endmodule
其中编码器中有一种特殊的编码器叫做优先编码器,有的时候我们可能会同时收到多个输入信号,普通的编码器是接受一个信号输入然后编码成一个信息。但是优先编码器能同时接受多个信号,然后按优先级选择一个有效信号,所以我们称之为“优先”编码器,可能概念上很难解释,但是看到具体的实现就很好理解了:
module encode42(x,en,y);
input [3:0] x;
input en;
output reg [1:0]y;
integer i;
always @(x or en) begin
if (en) begin
y = 0;
for( i = 0; i <= 3; i = i+1)
if(x[i] == 1) y = i[1:0];
end
else y = 0;
end
endmodule
lab2
要求如下:
功能描述
查找8-3优先编码器相关原理和实现方法,实现一个8-3编码器,完成8-3编码器的设计、功能仿真和硬件实现。
输入一个8位二进制数,对此8位二进制数进行高位优先编码成一个3位二进制值,并根据是否有输入增加一位输入指示位,即8个输入全0时指示位为0,有任何一个输入为1时指示位为1。编码器的使能端可选实现。将此编码结果及指示位以二进制形式显示在四个发光二极管LED上。再将此结果跟据七段数码管的显示进行译码,将二进制的优先编码结果以十进制的形式显示在数码管上。
输入输出建议
输入可以使用拨动开关SW7-SW0。使能端可以用SW8。输出为LED2-0,输出指示可以是LED4,数码管输出为HEX0。
例:我们从SW7—SW0输入00001110,因为我们设计的是一个高位优先的优先编码器,从左(高位)开始,第一位为1的是第3号位,那么优先编码器的编码二进制结果就为011,将这个值显示在发光二极管上,并且指示位也同时置为1。再对这个数值跟据七段数码管的显示进行译码,此时应显示为
3,用HEX0显示,所以HEX0[6:0]应该译码为0110000(注意高低位顺序),那么在七段数码管上就会显示3这个字符。
首先我们可以定义出我们需要用的元件,然后在top.v中实例化:
module encode83(x,en,y);
input [7:0] x;
input en;
output reg [2:0]y;
integer i;
always @(x or en) begin
if (en) begin
y = 0;
for( i = 0; i <= 7; i = i+1)
if(x[i] == 1) y = i[2:0];
end
else y = 0;
end
endmodule
module bcd7seg(
input [3:0] in,
output reg [6:0] out
);
// A B C D E F G
// reg 0 1 2 3 4 5 6
always @(*) begin
case(in)
4'd0: out = 7'b0000001;
4'd1: out = 7'b1001111;
4'd2: out = 7'b0010010;
4'd3: out = 7'b0000110;
4'd4: out = 7'b1001100;
4'd5: out = 7'b0100100;
4'd6: out = 7'b0100000;
4'd7: out = 7'b0001111;
4'd8: out = 7'b0000000;
4'd9: out = 7'b0000100;
default: out = 7'b1111111;
endcase
end
endmodule
module top(
input[8:0] sw,
output [6:0] seg,
output [2:0] led,
output in
);
wire[2:0] encout;
encode83 u_encode83(
.x(sw[7:0]),
.en(sw[8]),
.y(encout)
);
assign led = encout[2:0];
bcd7seg u_bcd7seg(
.in({1'b0,encout}),
.out(seg)
);
assign in = sw[8] && (|sw[7:0]);
endmodule
然后用nvboard_bind_pin()接口绑定信号和引脚即可正常使用:

ADD and ALU
在Verilog语言中,可以使用算术赋值语句和向量来执行这种算术运算。如果我们定义如下向量:
input [n-1:0] in_x, in_y;
output [n-1:0] out_s;
out_s = in_x + in_y;
就可以实现n位加法器,其他运算符号也是如此,可以生成对应的运算电路。
但是这个电路中并不会生成计算过程中的信号,例如加法电路不会生成对应的溢出信号和输出信号,所以我们需要显式的完成这些信号的计算和提取:
module adder #(
parameter N = 8
)(
input [N-1:0] in_x,
input [N-1:0] in_y,
output [N-1:0] out,
output carry,
output overflow,
output zero
);
assign {carry, out} = in_x + in_y;
assign overflow = (in_x[N-1] == in_y[N-1]) && (out[N-1] != in_x[N-1]);
assign zero = ~|out;
endmodule
我们可以根据补码原理,在加法器的基础上实现减法功能,具体的电路如图:

我们可以实现:
module addsuber #(
parameter N = 32
)(
input SubAdd, //用于加减法控制开关 Sub 1 / Add 0
input [N-1:0] in_x,
input [N-1:0] in_y,
output [N-1:0] out,
output carry,
output overflow,
output zero
);
wire [N-1:0] xortmp;
assign xortmp = in_y ^ {N{SubAdd}};
assign {carry, out} = SubAdd + in_x + xortmp;
assign overflow = (in_x[N-1] == xortmp[N-1]) && (out[N-1] != in_x[N-1]);
assign zero = ~|out;
endmodule
在此基础之上,我们可以给我们的运算器加入更多的基本运算电路,从而综合成CPU中的运算模块ALU。
我们这里按照RISC-V中基础指令集RV32I的ALU的设计要求来进行介绍。
RISC-V基础指令集RV32I只支持32位整型数值的操作。操作数可以是带符号补码整数或无符号数。ALU不需要完成乘除法,不需要进行溢出判断,相关操作由软件来完成。RV32I的ALU需要完成以下操作:
- 加减法操作 :完成带符号补码数和无符号数的加减法操作,无需判断溢出和进位。此条件下,可以统一处理带符号数和无符号数。
- 逻辑运算 :完成XOR、AND及OR操作,其他操作采用软件实现。例如,NOT可以使用与全一操作数进行XOR实现。
- 移位运算 :完成逻辑左移,逻辑右移、算术右移等功能。移位运算将在后面的移位寄存器实验中介绍。
- 比较运算 :完成带符号数与无符号数的全等和大小比较。此类运算均可利用减法实现。
lab3
上板实验: 实现一个带有逻辑运算的简单ALU
设计一个能实现如下功能的4位带符号位的 补码 ALU:
功能选择 功能 操作 000 加法 A+B 001 减法 A-B 010 取反 not A 011 与 A and B 100 或 A or B 101 异或 A xor B 110 比较大小 If A<B then out=1; else out=0; 111 判断相等 If A==B then out=1; else out=0; ALU进行加减运算时,需要能够判断结果是否为0,是否溢出,是否有进位等。这里,输入的操作数A和B都已经是补码。比较大小请按带符号数的方式设置。
执行逻辑操作时不需要考虑overflow和进位。
由于开发板上输入有限,可以使用SW作为数据输入,button作为选择端。
以下是我们ALU的具体实现:
module alu(
input [3:0] A,
input [3:0] B,
input [2:0] mode,
output reg [3:0] res,
output reg carry,
output reg overflow,
output zero
);
localparam ADD =3'b000;
localparam SUB =3'b001;
localparam NOT =3'b010;
localparam AND =3'b011;
localparam OR =3'b100;
localparam XOR =3'b101;
localparam LT =3'b110;
localparam EQUL =3'b111;
wire isSub = (mode == 3'b001);
wire [3:0] Bmux = isSub ? ~B : B;
always @(*) begin
{carry,overflow} = 0;
case(mode)
ADD,SUB: begin
{carry,res} = A + Bmux + isSub;
overflow = (A[3]==Bmux[3])&&(A[3]!=res[3]);
end
NOT: res = ~A;
AND: res = A & B;
OR: res = A | B;
XOR: res = A ^ B;
LT: begin
if($signed(A) < $signed(B))
res = 4'b1;
else
res = 4'b0;
end
EQUL:begin
if(A==B)
res = 4'b0001;
else
res = 4'b0000;
end
default: res = 4'b0;
endcase
end
assign zero = ~|(res);
endmodule
然后我们用top.v封装起来,再在main.cpp中绑定信号引脚就可以正常使用了(这里就不细说信号绑定了),下图是一个减法示例:

Shift and Barrel Register
我们已经实现了编解码器,现在我们在此基础之上又进一步实现了ALU。现在我们需要加入移位的组件,来完善我们的ALU功能。
为了实现数据的移位功能,我们有两种实现方式,一是基于时序电路和D触发器实现的移位寄存器,二是基于组合逻辑实现的桶型寄存器。
移位寄存器
移位寄存器在时钟的触发沿,根据其控制信号,将存储在其中的数据向某个方向移动一位。移位寄存器也是数字系统的常用器件。

随着时序,D触发器每激活一次,寄存器的值就会输出当前值,然后输入前一个的信号。我们可以在此基础上实现算数移位和逻辑移位。
- 算数左移/逻辑左移 数据整体左移 然后最右位置0
- 逻辑右移 数据整体右移 然后最左位置置0
- 算数右移 数据整体右移 然后最右位置置为符号位
以右移为例,用verilog实现就是:
Q <= {Q[0],Q[7:1]}; //循环右移
Q <= {Q[7],Q[7:1]}; //算术右移
对于移位寄存器我们通常有以下功能:
| 控制位 | 工作方式 |
|---|---|
| 0 0 0 | 清0 |
| 0 0 1 | 置数 |
| 0 1 0 | 逻辑右移 |
| 0 1 1 | 逻辑左移 |
| 1 0 0 | 算术右移 |
| 1 0 1 | 左端串行输入1位值,并行输出8位值 |
| 1 1 0 | 循环右移 |
| 1 1 1 | 循环左移 |
我们可以用Verilog实现:
module shift_register (
input wire clk, // 时钟信号
input wire rst_n, // 异步复位信号(低电平有效)
input wire load, // 并行加载使能(1:加载 din,0:执行移位)
input wire dir, // 移位方向(1:左移,0:右移)
input wire sin, // 串行输入数据(用于移位时填充空位)
input wire [7:0] din, // 并行输入数据
output reg [7:0] dout // 寄存器输出
);
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
dout <= 8'b0;
end else if (load) begin
dout <= din;
end else begin
if (dir) begin
dout <= {dout[6:0], sin};
end else begin
dout <= {sin, dout[7:1]};
end
end
end
endmodule
桶型移位器
移位寄存器虽然能够按照我们的设想工作,但是一个8位的移位寄存器需要我们等待8个时间周期来获取输入,在32位64位的情况下效果并不理想。所以我们可以引入一种组合逻辑实现的桶型寄存器来解决这个问题。
桶形移位器采用组合逻辑的方式来实现同时移动多位,在效率上优势极大。因此,桶形移位器常常被用在ALU中来实现移位。 图 Fig. 50 为8位桶形移位器的输入输出引脚图。 其中输入数据din和输出数据dout均为8位,移位位数shamt为3位。 选择端L/R表示左移和右移,置为1为左移,置为0为右移。 选择端A/L为算术逻辑选择,置为1为算术移位,置为0为逻辑移位。

我们可以用verilog来实现他:
module barrel_shifter_behavioral (
input wire [7:0] din,
input wire [2:0] shamt, // 移位量 0~7
input wire dir, // 1: 左移 (Left), 0: 右移 (Right)
input wire arith, // 1: 算术移位 , 0: 逻辑移位
output reg [7:0] dout
);
always @(*) begin
if (dir) begin
dout = din << shamt;
end else begin
if (arith) begin
dout = $signed(din) >>> shamt;
end else begin
dout = din >> shamt;
end
end
end
endmodule
lab6
我们可以利用8位移位寄存器来实现一个简单的随机数发生器。经典的LFSR(线性反馈移位寄存器,Linear-feedback shift register)可以使用 $n$ 位移位寄存器生成长度为 $2^n - 1$ 的二进制循环序列。这类序列的片段在表观上是随机的,所以被广泛用于通信中的随机序列生成。例如,在CDMA通信中的长码的长度就是 $2^{42} - 1$ 的伪随机序列。
具体实现时,可以用一个8位右移移位寄存器,从左到右的比特以 $x_7x_6x_5x_4x_3x_2x_1x_0$ 表示。每个时钟周期右移一位,$x_0$ 被移出,最左边移入的位按照上一周期的值计算 $^1$:
$$x_8 = x_4 \oplus x_3 \oplus x_2 \oplus x_0$$例如,初始二进制值为00000001时,移位寄存器的状态将按 $00000001 \rightarrow 10000000 \rightarrow 01000000 \rightarrow 00100000 \rightarrow 00010000 \rightarrow 10001000 \dots$ 变化。该序列的周期为255。当然,当初始值为全零时,系统将一直停留在全零状态,所以需要对全零状态进行特殊处理。
请实现一个8位的周期为255的伪随机序列,以按钮为时钟信号,并请将8位二进制数以十六进制显示在数码管上,在DE10-Standard开发板上观察生成的随机数序列。
FSM and Keyboard input
← 一生一芯E1