大三下期末的计算机组成课程设计要求完成一个简易的CPU设计,为了这个课设,,,不得不自己下了一个Vivado(心疼流量…),自己学了一波Verilog语言,有点东西,发誓再也不碰硬件的我学者语言就像是乡下人进城一样颤抖,,,可惜可惜,这波学过以后估计也是不会用了,记一篇博客起码以后能回忆下
简易CPU设计
报告中对三个硬件结构的要求:
结构 | 详细要求 |
---|---|
运算器 | 算术单元、逻辑单元、运算器综合、溢出判断 |
控制器 | 程序计数器、指令寄存器、数据通路设计、控制字、指令译码器、状态寄存器、控制器综合 |
寄存器组 | 需要即使用 |
这个简易CPU也可以说是一个简易的加减法运算器。由于控制器部分有指令寄存器,那么假设加减两个主要指令都在指令寄存器中,指令寻址的方式可以设定为直接寻址;再考虑控制字等因素,整个CPU的输入和输出可以确定有以下几个部分:
这个课程设计中,运算器部分的要求仅仅是实现加减法运算即可,根据该要求,运算器部分期待有两个输入数据和两个输出数据(运算结果和溢出位判断),本人此次计划完成一个8位的CPU设计,因此每根输入的数据线当为8条,输出结果的数据线为8条,溢出数据线为1条。
此外,由于运算的方式选择可能有两种:加法和减法。因此还需要有1条运算方式选择线。
注意:按照课程设计要求,运算器的加减法都必须使用基本逻辑电路实现,所以有必要以基础逻辑运算描述全加和全减器。详细请见compute.v代码部分的封装。
综合整体考虑的话运算器应该有以下的输入和输出:
控制器部分主控整个CPU的运算方式选择,即输入指令地址,取指令,译码和输出指令的过程,由此,控制器部分原则上应当有一个寄存器组用于存放各种指令,这里出于简单的考虑只做最小化处理。
本程序只设置一个数据寄存器组,事先把所有的数据都写好,仿真时直接取数据核查结果
`timescale 1ns / 1ps
module CPU_top(
input clk,
input rst,
input [7:0] CMD,
input start,
input [7:0]Addr_1,
input [7:0]Addr_2,
output [7:0] data_out,
output out_valid,
output overflow
);
wire [7:0]Address_1;
wire [7:0]Address_2;
wire [7:0]compute;
wire c_state;
wire [7:0]data_1;
wire [7:0]data_2;
control control_1(
.clk(clk),
.rst(rst),
.start(start),
.cmd(CMD),
.Addr_1(Addr_1),
.Addr_2(Addr_2),
.Address_1(Address_1),
.Address_2(Address_2),
.compute(compute),
.c_state(c_state)
);
data_ram data_ram_1(
.clk(clk),
.en(c_state),
.rst(rst),
.addr_1(Address_1),
.addr_2(Address_2),
. dout_1(data_1),
. dout_2(data_2)
);
compute compute_1(
.clk(clk ),
.rst(rst),
.comput(compute),
.state(c_state),
.data_1(data_1),
.data_2(data_2),
.out(data_out),
.over(overflow),
.out_vld(out_valid)
);
endmodule
`timescale 1ns / 1ps
module control(
input clk,
input rst,//复位
input start,//使能 连接en
input [7:0] cmd, // 1是加法 2是减法
input [7:0] Addr_1,
input [7:0] Addr_2,
output [7:0] Address_1,
output [7:0] Address_2,
output [7:0] compute, // 相当于cmd 1是加法 2是减法
output c_state // 1是 运算有效 0是无效
);
//reg [7:0]ADD ;
//reg [7:0]SUB ;
parameter ADD =1;
parameter SUB =2;
reg [7:0] Address_1_t; // 一个数字的地址
reg [7:0] Address_2_t; // 一个数字的地址
reg [7:0] compute_t; // 加减选择 0加 1减
reg c_state_t; // 运算有效位
wire is_add, is_sub;
assign is_add = is_same(cmd, ADD); // 加法选择判断
assign is_sub = is_same(cmd, SUB); // 减法选择判断
always@(posedge clk or negedge rst)//时钟上升沿或复位下降沿有效 执行下面的代码
if(~rst ) // 复位低电平有效,
begin
Address_1_t = 0; // 0赋值给address
Address_2_t = 0;
compute_t = 0;
c_state_t = 0;
end
else if(start) // enable使能有效
begin
Address_1_t = Addr_1; // 输入Addr_1 赋值给输出Address_1_t
Address_2_t = Addr_2;
if (is_add) // verilog的if判断,条件不允许是表达式,必须是一个变量
begin
compute_t = 1; // 向运算器传递加法器选择命令
c_state_t =1;
end
// compute_t与cmd相同,都是1是加法2是减法 cmd是输入 compute_t是输出
else if (is_sub)
begin
compute_t = 2; // 向运算器传递减法器选择命令
c_state_t =1;
end
else
c_state_t = 0; //c_state_t 0表示运算无效 1是运算有效
end
// 这个模块的最后输出
assign Address_1 = Address_1_t;
assign Address_2 = Address_2_t;
assign compute = compute_t;
assign c_state = c_state_t;
// ==================================================================================
function is_same; // == 实现
input [7:0] I0,I1;
begin
is_same = (& (I0 ^~ I1));
end
endfunction
endmodule
`timescale 1ns / 1ps
module data_ram(
input clk,
input en, // 使能标志,开始运行后一直置1
input rst, // 复位,仿真时,由rst置1开始运行仿真
input [7:0] addr_1, // 输入地址
input [7:0] addr_2,
output [7:0] dout_1, // 输出数据
output [7:0] dout_2
);
reg [7:0] RAM[0:9]; // 10个8bit的存储器
integer cnt;
always@(posedge rst) // 复位端上升沿有效执行下面的程序
for(cnt =0; cnt<10; cnt=cnt+1) // 初始化寄存器的数据
begin
case(cnt)
0: RAM[0] = 100;
1: RAM[1] = 2;
2: RAM[2] = 3;
3: RAM[3] = 2;
4: RAM[4] = 2;
5: RAM[5] = 50;
6: RAM[6] = 70;
7: RAM[7] = 90;
8: RAM[8] = 10;
9: RAM[9] = 30;
endcase
end
assign dout_1 = en ? RAM[addr_1]:0;
assign dout_2 = en ? RAM[addr_2]:0;
endmodule
`timescale 1ns / 1ps
module compute(
input clk,
input rst,
input [7:0] comput,
input state,
input [7:0] data_1,
input [7:0] data_2,
output [7:0] out,
output over,
output reg out_vld
);
reg [7:0] out_tmp ,over_tmp;//结果输出 溢出结果
wire add_judge, minus_judge;
assign add_judge = is_same(comput, 1, state); // 即(comput==1) && state
assign minus_judge = is_same(comput, 2, state); // 即(comput==2) && state
always@(posedge clk or negedge rst)
if(~rst)
begin
out_tmp <= 0;
out_vld <= 0;
end
else if(add_judge) // 加法 并且 使能端有效
begin
add8(data_1, data_2, out_tmp, over_tmp);
out_vld <= 1; // 输出有效置1
end
else if(minus_judge) // 减法 并且 使能端有效
begin
sub8(data_1, data_2, out_tmp, over_tmp);
out_vld <= 1;
end
else
begin
out_vld <=0;
end // 运算符既不是1也不是2 输出有效置0
// assign over = (out_tmp >255 || out_tmp <0)? 1:0 ;
assign over = over_tmp;
assign out = out_tmp[7:0];
// ==================================================
function is_same; // ==运算符的逻辑门实现 以及 使能状态的判断
input [7:0] I0,I1;
input state;
begin
is_same = (& (I0 ^~ I1)) && state;
end
endfunction
task add8; // 8位全加器
input [7:0] a,b;
output [7:0] sum;
output c_out;
integer i;
reg c_in;
begin
c_in = 0;
begin
for(i=0; i<8; i=i+1)
begin
add1(a[i], b[i], c_in, sum[i], c_out);
c_in = c_out;
end
end
end
endtask
task add1; // 1位全加器
input a,b,c_in; // 加数、被加数、前一次运算的进位
output sum, c_out; // 本位的和、本位运算后是否有进位
begin
sum = a^b^c_in;//异或
c_out = (a & b) | (a & c_in) | (b & c_in);
end
endtask
task sub8; // 8位全减器
input [7:0] a,b;
output [7:0] diff;
output c_in; // 借位
integer i;
reg c_out;
begin
c_out = 0;
for(i=0; i<8; i=i+1)
begin
sub1(a[i],b[i],c_out,diff[i],c_in);
c_out = c_in;
end
end
endtask
task sub1; // 1位全减器
input a,b, c_out; // 被减数、 减数、 低位是否向本位借位
output diff, c_in; // 本位减运算结果, 本位是否向高位借位
begin
diff = a^b^c_out;
c_in = (~a & (b ^ c_out)) | (b & c_out);
end
endtask
endmodule
cpu_test.v
`timescale 1ns / 1ps
`define clk_period 20 // 时钟周期
module CPU_test(
);
reg [7:0]CMD;
reg start;
reg [7:0]Addr_1;
reg [7:0]Addr_2;
wire [7:0]data_out;
wire out_valid;
wire overflow;
reg Clk;
reg Rst_n;
CPU_top CPU_1(
.clk(Clk),
.rst(Rst_n),
.CMD(CMD),
.start(start),
.Addr_1(Addr_1),
.Addr_2(Addr_2),
.data_out(data_out),
.out_valid(out_valid),
.overflow(overflow)
);
initial Clk = 1;
always#(`clk_period/2)Clk = ~Clk; // 跳变
initial begin
Rst_n = 1'b0;//一位二进制数0
#(`clk_period*5); // z在20*5个单位时间后执行下面的
Rst_n = 1'b1; // 一位二进制1 赋值给Rst_n
CMD =2; // 减法
start = 1 ;
Addr_1 = 1;
Addr_2 = 2;
#`clk_period; // 编译预处理指令 一个时钟周期后执行下面
#(`clk_period*5);
Addr_1 = 3;
Addr_2 = 4;
#`clk_period;
#(`clk_period*5);
CMD =1;
Addr_1 = 6;
Addr_2 = 5;
#`clk_period;
#(`clk_period*5);
Addr_1 = 8;
Addr_2 = 0;
#`clk_period;
#(`clk_period*5);
Addr_1 = 9;
Addr_2 = 7;
#`clk_period;
#(`clk_period*5);
Addr_1 = 0;
Addr_2 = 9;
#`clk_period;
$stop;
end
endmodule
大学期间倒数第二个课程设计挂了不少人,这个课设刚拿到的时候显然是蒙蔽的,,,开工的第一步是要试着去构思整个电路,只要每个部分都考虑清楚输入输出和期待的功能,完成它的方式是多种多样的,之前见到班里有很多使用protues和其他的基础电路工具做,本人电脑不知道中了什么邪,protues完全用不了,没有选择对的余地,只能折腾verilog,出了100学费学一波,,,以后”硬件“相逢是路人,溜溜球。
这个CPU其实说不上是简易CPU,缺的东西多了去了,所以记一下这个博客以后某一天有机会了回来填坑
本博客所述工程文件:https://download.csdn.net/download/weixin_42744892/11341020
注意该工程文件的cpu_top.v文件中间有一部分注释掉的变量定义代码,请去掉注释再运行。可以对比上边贴的代码,本博客中的代码是没有问题的。