4bit ALU verilog 设计
设计概要如下:
CPU中简单的4位ALU(算术逻辑单元)
输入信号:第一4位操作数op0[3:0]、第二4位操作数op1[3:0]、功能选择s[2:0]、输出使能信号oe、时钟信号clk
输出信号:4位运算结果r[3:0]、进位标志cf
设计要求:
1、根据功能选择信号s[2:0]选择算术或者逻辑运算功能,s[2:0]值为3’b000(即二进制000B)时为加法运算,3’b001时为减法运算,3’b100时为与运算,3’b101时为或运算,3’b110时为非运算(注意非运算只使用第一4位操作数),3’b111时为异或(XOR)运算。
2、运算结果必须先存储在4位结果寄存器(使用D触发器实现)中,clk上升沿时运算结果存入4位结果寄存器,此功能必须实现。
3、如果加法发生向更高位进位(运算结果超过4位),则需要将进位存储在一个1位的进位寄存器(使用D触发器实现)中,发生进位存储1,反之存储0;如果减法发生向更高位借位,则需要将借位也存储在与加法相同的进位寄存器中,发生借位存储1,反之存储0;同样是clk上升沿时进位或者借位存入1位进位寄存器。此功能必须实现。
4、oe低电平有效,oe高电平时,r[3:0]和cf均为高阻态输出(相当于输出无效);oe低电平时,r[3:0]等于4位结果寄存器(D触发器)的输出,cf等于1位进位寄存器(D触发器)的输出。
设计一个四位的ALU直接使用软件思维非常简单,直接疯狂的条件判断就完事了,但值得注意的一点是,我们需要将结果利用 D 触发器保存到寄存器中,然后将寄存器结果与
输出结果相连即可:
module alu(
input wire [3:0] op0, // operator0
input wire [3:0] op1, // operator1
input wire [2:0] s, // functional option
input clk, // clock
input wire oe, // result enable
output reg [3:0] r, // result output
output reg cf // carry
);
// internal register
reg [3:0] result_reg; // 4-bit result register
reg cf_reg; // carry flag register
wire [4:0] adder_result; // output(include carry)
wire [3:0] logic_result; // logic calculated result
wire [3:0] result_raw; // final result
wire cf_raw; // carry/borrow flag
// arithmetic operation module
assign adder_result = (s == 3'b000) ? (op0 + op1) : // add
(s == 3'b001) ? (op0 + (~op1 + 1)) : // sub
5'b0;
// carry/borrow flag calculate
assign cf_raw = s == 3'b000 ? adder_result[4]: // carry or borrow
s == 3'b001 ? (op0 < op1) :
0;
// logic operation module
assign logic_result = (s == 3'b100) ? (op0 & op1) : // and
(s == 3'b101) ? (op0 | op1) : // or
(s == 3'b110) ? (~op0): // not
(s == 3'b111) ? (op0 ^ op1): // xor
4'b0;
// final result choose
assign result_raw = (s[2] == 1'b0) ? adder_result[3:0] : //arithmetic operation
logic_result[3:0]; // logic operation
// D trigger
always @(posedge clk) begin
result_reg <= result_raw;
cf_reg <= cf_raw;
end
// output control
always @(*) begin
if(!oe) begin
r <= result_reg;
cf <= cf_reg;
end else begin
r <= 4'bzzzz;
cf <= 1'bz;
end
end
endmodule
是的,C语言咋写你就咋写,当然设计的核心在硬件思维的设计,下面我将基于这个 ALU 的软件思维的初版代码,设计一个纯硬件思维的电路出来
一、实现所有的运算逻辑,包装成底层模块
1、加减法模块
还记得上一个实验吗,自动售货机,没错,直接把那边的四位加减法器ban过来即可:
module FourBitAdder(
input [3:0] A, B,
input SubEn,
output [3:0] S,
output CO
);
wire [3:0] last_B = {4{SubEn}} ^ B;
wire [3:0] P = last_B ^ A;
wire [3:0] G = A & last_B;
wire [3:0] C;
wire CI = SubEn;
assign C[0] = CI;
assign C[1] = G[0] | (P[0] & CI);
assign C[2] = G[1] | (P[1] & G[0]) | (P[1] & P[0] & CI);
assign C[3] = G[2] | (P[2] & G[1]) | (P[2] & P[1] & G[0]) | (P[2] & P[1] & P[0] & CI);
assign CO = SubEn ^ (G[3] | P[3] & C[3]);
assign S = P ^ C;
endmodule
对的对的直接 ban 就可以了,要是不懂怎么实现的建议回去看一下上一篇文章自动售货机
2、与/或/非/异或模块
module FourBitAnder(
input [3:0] A, B,
output [3:0] S
);
assign S = A & B;
endmodule
module FourBitOrer(
input [3:0] A, B,
output [3:0] S
);
assign S = A | B;
endmodule
module FourBitNoer(
input [3:0] A,
output [3:0] S
);
assign S = ~A;
endmodule
module FourBitXorer(
input [3:0] A, B,
output [3:0] S
);
assign S = A ^ B;
endmodule
这个太简单了就不解释了,按理来说其实不需要给他们单独设置模块,但这样写更清晰一些
二、顶层模块复用所有子模块
房子造好了自己不会住我就帮不了了哈,首先我得说明一下这个 ALU 的全局逻辑,首先,这个 ALU 涉及6个运算逻辑,即加、减、与、或、非、异或,首先让
这些逻辑并行计算所有的结果,也就是所谓的全路径计算,流程图如下:
graph LR
A[输入数A] --> B[并发数据传递]
C[输入数B] --> B
B --> E[加法器/减法器]
B --> F[与运算]
B --> O[或运算]
B --> P[非运算]
B --> Q[异或运算]
E & F & O & P & Q --> H[结果选择器]
H --> I[输出寄存器]
I --> X[最终输出]
复用部分实现如下:
FourBitAdder add(
.A(op0),
.B(op1),
.SubEn(s[0]),
.S(add_res),
.CO(cout)
);
FourBitAnder(
.A(op0),
.B(op1),
.S(and_res)
);
FourBitOrer(
.A(op0),
.B(op1),
.S(or_res)
);
FourBitNoer(
.A(op0),
.S(no_res)
);
FourBitXorer(
.A(op0),
.B(op1),
.S(xor_res)
);
注意:加法和减法使用的是同一个逻辑运算单元
三、结果选择器实现
这样一来,我们就只剩下结果选择没有实现了,这里使用 case 语法即可简单实现:
always @(*) begin
case(s)
3'b000, 3'b001: begin
result_raw = add_res;
cf_raw = cout;
end
3'b100: begin
result_raw = and_res;
cf_raw = 1'b0;
end
3'b101: begin
result_raw = or_res;
cf_raw = 1'b0;
end
3'b110: begin
result_raw = no_res;
cf_raw = 1'b0;
end
3'b111: begin
result_raw = xor_res;
cf_raw = 1'b0;
end
default: begin
result_raw = 4'b0000;
cf_raw = 1'b0;
end
endcase
end
四、D触发器存储结果数据实现
always @(posedge clk) begin
result_reg <= result_raw;
cf_reg <= cf_raw;
end
这个实现很简单,在每次上升沿将结果存储到寄存器中即可,利用D触发器作为数据缓冲,保证数据不会乱(消除毛刺)
五、最终输出
always @(*) begin
if(~oe) begin
r = result_reg;
cf = cf_reg;
end else begin
r = 4'bzzzz;
cf = 1'bz;
end
end
将寄存器中的值传到输出端,从而实现最终数据输出
最终代码实现如下:
module alu(
input wire [3:0] op0, // operator0
input wire [3:0] op1, // operator1
input wire [2:0] s, // functional option
input clk, // clock
input wire oe, // result enable
output reg [3:0] r, // result output
output reg cf // carry
);
// internal register
reg [3:0] result_reg; // 4-bit result register
wire [3:0] add_res, and_res, or_res, no_res, xor_res;
wire cout;
reg cf_reg; // carry flag register
wire adder_co;
reg adder_op0, adder_op1;
reg [3:0] result_raw; // final result
reg cf_raw; // carry/borrow flag
FourBitAdder add(
.A(op0),
.B(op1),
.SubEn(s[0]),
.S(add_res),
.CO(cout)
);
FourBitAnder(
.A(op0),
.B(op1),
.S(and_res)
);
FourBitOrer(
.A(op0),
.B(op1),
.S(or_res)
);
FourBitNoer(
.A(op0),
.S(no_res)
);
FourBitXorer(
.A(op0),
.B(op1),
.S(xor_res)
);
always @(*) begin
case(s)
3'b000, 3'b001: begin
result_raw = add_res;
cf_raw = cout;
end
3'b100: begin
result_raw = and_res;
cf_raw = 1'b0;
end
3'b101: begin
result_raw = or_res;
cf_raw = 1'b0;
end
3'b110: begin
result_raw = no_res;
cf_raw = 1'b0;
end
3'b111: begin
result_raw = xor_res;
cf_raw = 1'b0;
end
default: begin
result_raw = 4'b0000;
cf_raw = 1'b0;
end
endcase
end
// D trigger
always @(posedge clk) begin
result_reg <= result_raw;
cf_reg <= cf_raw;
end
// output control
always @(*) begin
if(~oe) begin
r = result_reg;
cf = cf_reg;
end else begin
r = 4'bzzzz;
cf = 1'bz;
end
end
endmodule
module FourBitAdder(
input [3:0] A, B,
input SubEn,
output [3:0] S,
output CO
);
wire [3:0] last_B = {4{SubEn}} ^ B;
wire [3:0] P = last_B ^ A;
wire [3:0] G = A & last_B;
wire [3:0] C;
wire CI = SubEn;
assign C[0] = CI;
assign C[1] = G[0] | (P[0] & CI);
assign C[2] = G[1] | (P[1] & G[0]) | (P[1] & P[0] & CI);
assign C[3] = G[2] | (P[2] & G[1]) | (P[2] & P[1] & G[0]) | (P[2] & P[1] & P[0] & CI);
assign CO = SubEn ^ (G[3] | P[3] & C[3]);
assign S = P ^ C;
endmodule
module FourBitAnder(
input [3:0] A, B,
output [3:0] S
);
assign S = A & B;
endmodule
module FourBitOrer(
input [3:0] A, B,
output [3:0] S
);
assign S = A | B;
endmodule
module FourBitNoer(
input [3:0] A,
output [3:0] S
);
assign S = ~A;
endmodule
module FourBitXorer(
input [3:0] A, B,
output [3:0] S
);
assign S = A ^ B;
endmodule