算术逻辑单元实验
实验内容1:ALU设计与验证
算术逻辑单元(ALU)完成计算机中的算术运算和逻辑运算。本项目在前面加减运算实验的基础上增加移位运算和逻辑运算。
参考设计1:简单ALU
算术逻辑单元(ALU)完成计算机中的算术运算和逻辑运算。图 1是一个简单ALU的设计,虚线框内是前面加减运算实验的参考设计,只是简化了画法,将4位数据用一根较粗的信号线表示;增加了与、或、异或逻辑运算,并通过一个四选一多路器选择运算的功能。
localparam N = 4; logic [N-1:0] A, B, F; logic C0, Cn; logic sign, zero, overflow, carryOut; assign A = X; assign B = Y ^ {N{M0}}; assign C0 = M0; always_comb begin case ({S1,S0}) 2'b00: {Cn, F} = A + B + C0; 2'b01: {Cn, F} = {1'b0, (X & Y)}; 2'b10: {Cn, F} = {1'b0, (X | Y)}; 2'b11: {Cn, F} = {1'b0, (X ^ Y)}; default: {Cn, F} = {(N+1){1'bx}}; endcase end
设计任务1
功能要求
(1)用HDL设计ALU,实现表 1列出的运算功能。
ALUop | 运算 | 功能描述 |
---|---|---|
0001 |
F=X+Y |
加法(ADD) |
0010 |
F=X-Y |
减法(SUB) |
0011 |
F=X∧Y |
按位逻辑与(AND) |
0100 |
F=X∨Y |
按位逻辑或(OR) |
0101 |
F=X⊕Y |
按位逻辑异或(XOR) |
0110 |
F=X>>>Y |
算术右移Y[1:0]位 |
0111 |
F=X<<Y |
逻辑左移Y[1:0]位 |
1000 |
F=X>>Y |
逻辑右移Y[1:0]位 |
和参考设计相比,运算功能增加了移位运算。被移位的数据来自X输入,移位位数由输入Y提供,由于X是4位数据,最多移3位,因此只截取Y的最低两位作为移位位数。
(2)实现比较运算。
在加减电路实验中,数值大小的比较是根据ALU的标志位进行判断的。这种方法可以看成是加减电路的附带功能,没有增加太多硬件成本;但是比较的速度却不够快,因为经过了加减电路、标志位生成,再根据标志位产生比较结果,电路的传输延时较长。另外,这种方法需要利用ALU进行减法运算,如果在CPU设计中需要用ALU计算转移地址,就无法用ALU做减法。
本实验使用独立的数值比较电路,不依赖ALU运算的标志位。就像先行进位已经成为并行加法器的基本模块,比较器也是一种基本的运算模块,使用硬件描述语言的比较运算符,综合工具会自动生成比较器模块。
SystemVerilog比较运算符根据运算数据的类型自动选择进行带符号数比较还是无符号数比较。可使用系统函数$signed()或$unsigned()将运算数转换为带符号数或无符号数,详见SystemVerilog语法部分。 |
描述风格
硬件描述语言具有不同抽象层次的描述能力。如参考设计的代码完全是按照图 1电路结构进行描述,这种风格要求设计者对电路的结构非常清楚,并且有能力设计最简的电路,前面实验大都是这类描述风格。另一种是更高层次的抽象,只关注电路的功能,不关注电路的结构,电路实现交给综合工具。本设计任务可以选择电路结构的描述风格,也可选择更高抽象的行为描述,实验材料中提供了两种方案的虚拟面板。下面对这两种描述风格的设计方案给一些具体的提示。
(1)面向结构的ALU设计方案举例
-
加减运算电路的功能及实现方法与上一个实验的任务相同,可直接采用已经完成设计;
-
移位电路的功能与“流水灯与移位寄存器实验”的任务2相同,可直接采用已经完成的桶形移位设计;
-
数值比较根据运算数X、Y直接进行运算,不依赖ALU的减法标志位。
-
增加ALUop译码,ALUop与运算功能的对应关系应按表 1设计,以便于机器考核。
-
实现传送功能(选做)。增加两组ALUop编码,用于MOVX(F=X)和MOVY(F=Y)传送功能。
也可以设计与图 2不同的电路结构,自己绘制原理框图作为虚拟面板的背景。如果需要可以自行添加指示灯和数码管。 |
(2)面向行为的ALU设计方案
行为描述不关心电路的实现结构,只关心所要实现的电路的功能。验证时仍可以使用图 2的虚拟面板,只用基本的输入输出,忽略电路结构和内部指示灯;也可以使用下面任务2的虚拟面板。
实验内容2:将ALU封装为模块
在上面的参考设计和实验任务中,为了便于在虚拟面板上观察ALU内部信号,代码直接写在VirtualBoard模块;现在将ALU写成独立的模块(功能没有变化),并且字长采用参数定义,以便在后面的CPU设计中重复使用。
该任务的虚拟面板只给出ALU外框,如图 3所示。但并不意味着必须采用面向行为的方案,仍然可以采用面向结构的实现方案。只是封装为模块后,VirtualBoard模块的指示灯无法连接ALU模块的内部信号,所以虚拟面板只有反映功能的输入输出元件。
参考设计2
例 2给出了上面参考设计1的模块封装。代码中使用的结构体和枚举常量定义在 definitions.sv 文件中,因此第一行用 `include "definitions.sv"
将该文件包含进来。
`include "definitions.sv" module ALU #(parameter N=32) ( input wire [N-1:0] iX, iY, input wire [3:0] iALUop, output logic [N-1:0]oF, output alu_defs::t_flag oFlag ); import alu_defs::*; wire [N-1:0] X = iX; wire [N-1:0] Y = iY; logic M0, S1, S0; logic [N-1:0] A,B; logic C0, Cn; assign A = X; assign B = Y ^ {N{M0}}; assign C0 = M0; always_comb begin case ({S1,S0}) 2'b00: {Cn, oF} = A + B + C0; 2'b01: {Cn, oF} = X & Y; 2'b10: {Cn, oF} = X | Y; 2'b11: {Cn, oF} = X ^ Y; default: {Cn, oF} = {(N+1){1'bx}}; endcase end always_comb begin case (iALUop) ADD: {S1,S0,M0} = 3'b000; SUB: {S1,S0,M0} = 3'b001; AND: {S1,S0,M0} = 3'b010; OR: {S1,S0,M0} = 3'b100; XOR: {S1,S0,M0} = 3'b110; default:{S1,S0,M0} = 3'b000; endcase end assign oFlag.sign = oF[N-1]; assign oFlag.zero = ~(|oF); assign oFlag.overflow = ~A[N-1] & ~B[N-1] & oF[N-1] | A[N-1] & B[N-1] & ~oF[N-1] ; assign oFlag.carryOut = (M0==1'b0) ? Cn : ~Cn; endmodule
definitions.sv 文件内容见例 3,使用了SystemVerilog的package、结构体、枚举等描述方法,相关语法和用法与C/C++语言类似。
package alu_defs; typedef struct packed{ logic sign; logic zero; logic overflow; logic carryOut; } t_flag; enum logic [3:0] { ADD = 4'b0001, SUB = 4'b0010, AND = 4'b0011, OR = 4'b0100, XOR = 4'b0101 } aluop; endpackage
这里对结构体定义中的packed修饰符做一些说明, packed
表示这是一个压缩的结构体,它会被当做一个向量进行存储。比如 t_flag
结构体类型将存储为4位向量,结构体的第一个成员 sign
在向量的最左边,最后一个成员 carryOut
是向量的最低位,相当于 {sign, zero, overflow, carryOut}
。正因为如此,才可以在VirtualBoard模块中将结构体变量赋给一个向量。
见例 4中标记①的代码行。
例 4给出了VirtualBoard模块的主要代码,其中实例化了ALU模块,并将输入输出端口连接到开关和指示灯。
/****** Replace input ports with internal signals *******/ wire [3:0] ALUop = S[12:9]; wire [3:0] X = S[7:4]; wire [3:0] Y = S[3:0]; /************* The logic of this experiment *************/ wire [3:0] F; alu_defs::t_flag flag; ALU #(.N(4)) alu( .iX(X), .iY(Y), .iALUop(ALUop), .oF(F), .oFlag(flag)); /***** Internal signals assignment to output ports ******/ assign L[11:8] = F; assign L[15:12] = flag; (1)
标记①的代码行中,flag
为 t_flag
类型的结构体变量,因为 t_flag
声明为 packed
, 所以可以赋给向量 L[15:12]
。等价语句如下。
assign L[15:12] = {flag.sign, flag.zero, flag.overflow, flag.carryOut};
设计任务2
将自己设计的ALU和比较器分别写成独立的模块,具体要求如下。
(1)ALU模块的端口定义如下。
module ALU #(parameter N=4) ( input wire [N-1:0] iX, iY, input wire [3:0] iALUop, output logic [N-1:0]oF );
(2)比较器模块的端口定义如下。
module Compare #(parameter N=4) ( input wire [N-1:0] iX, iY, output defs::t_cmp oCmp );
(3)修改package包。
-
增加
t_cmp
结构体定义,包含6个成员,表示比较运算的结果。 -
按照表 1扩充
aluop
枚举常量的定义。
(4)在ALU模块中使用package中定义的 aluop
枚举常量,在Compare模块中使用 t_cmp
结构体成员。
电路测试采用图 3所示的虚拟面板,仅测试功能,不限制实现方案,面向结构或面向行为均可。 |
讨论:你的设计使用了多少逻辑资源
编译完成后,可以通过Flow Summary查看FPGA逻辑资源的占用情况,如图 4。
也可以打开output文件夹下生成的Lab.fit.summary文件查看,如图 5。
因为一种型号的FPGA芯片的逻辑资源是固定的,所以报告的是占用比例。虽然占用的比例对特定的FPGA芯片来说不存在成本问题,但是可用于其他功能的逻辑资源就少了。而且如果是专用集成电路ASIC,逻辑资源的多少直接影响着硅片面积的大小,也就是芯片的材料成本。类似于软件中的内存占用,逻辑资源占用是硬件设计的一个指标,实现同样的功能,资源占用越少越好。
讨论:
实验指导中提到了两种实现方案,如果两种方案你都做了,查看output文件夹下编译产生的 .fit.summary文件,对比两种方案所占用的逻辑资源数量是否不同。如果只做了一种,和采用另一种方案的同学交流对比,讨论如何减少逻辑资源占用。
另外需要说明的是,编译报告中的逻辑资源并不仅仅是ALU的逻辑资源,还包括了实验系统的片上调试器,但是不影响比较两种方案。