【FPGA】基于FPGA的极简CPU设计

这个是我这个学期FPGA的期末大作业,老师说是给两周的时间去写,其实还是在最后一周匆匆赶制出来的 不到DDL不开工。其实吧,要我自己评价是挺不满意的,主要是结构缩水了太多,为了节省代码量,我们甚至连PC指针砍掉,并且将ALU和Control Unit强行拼在了一起,变成了一个单地址指令结构的(严格来说应该也算不上)CPU。可能唯一的优点就是执行快吧……

一、设计原理


看的出来其实我们设计的这个原理十分简单,一个只剩下指令寄存器的控制单元,不用计算PC指针,还和运算单元结合在了一起,连在顶层文件中的连接都省了。由于是在家里上课,也没有上板之类的,只要ModuleSim能跑就行,所以把外围内存都删了(其实原理也挺简单的),直接看输入输出波形就完事了。但是内部的存储单元还是得要,不然有许多操作的中间值没地方放,自己写一个就行了,也不要什么IP Core之类的。

二、指令列表

输入信号为16位数 别问,问就是强迫症,高8位为指令码,低8位为操作数。
总共就九个指令,全部围绕寄存器A进行:

  1. in_ram:将A中的数写入RAM,地址为输入数据低8位所指的空间
  2. load_num:从外部输入数据到A
  3. out_ram:将RAM中的数写入A,地址为输入数据低8位所指的空间
  4. clr:清空A
  5. inc:A中的数自加1
  6. dec:A中的数自减1
  7. stay:等待指令
  8. add:将A中的数与输入数据低8位相加
  9. min:将A中的数与输入数据低8位相减

三、代码设计

工程总共有4个文件:输入模块,运算控制模块,RAM模块以及顶层模块

1、输入模块(IDEC.v)

输入模块负责将输入的16位二进制数分割为2个8位二进制数,高8位为指令,低8位为操作数或者RAM地址。

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
module IDEC(
clk,
reset,
code_in, //指令输入端口
code_addr, //操作码输出端口
data_addr //操作数输出端口
);

input clk,reset;
input [15:0] code_in;
output [7:0] code_addr;
output [7:0] data_addr;

reg [7:0] code_addr;
reg [7:0] data_addr;

always @(posedge clk or negedge reset)
begin
if(!reset)
begin
code_addr <= 8'b00000000;
data_addr <= 8'b00000000;
end
else
begin
code_addr <= code_in [15:8];
data_addr <= code_in [7:0];
end
end
endmodule

2、运算控制模块 (IMP.v)

运算控制模块大致代码分为两个部分,前半部分是声明指令是干什么的,后半部分进行相应指令的对应操作,最后的task部分为加减算法。

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
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
module IMP(
clk,
//reset,
in_data, //1.操作数输入端口 2.ROM地址输入端口
in_code, //操作码输入端口
dout //操作结果输出端口
);
input clk;//reset;
input [7:0] in_data;
input [7:0] in_code;

output [7:0] dout;
reg [7:0] dout;

parameter in_ram = 8'b00000001, //A->ROM[in_data]
load_num = 8'b00000010, //in_data->A
out_ram = 8'b00000011, //ROM[in_data]->A
clr = 8'b00000100, //0->A
inc = 8'b00000101, //A+1->A
dec = 8'b00000110, //A-1->A
stay = 8'b00000111, //等待指令
add = 8'b00001000, //A+in_data->A
min = 8'b00001001; //A-in_data->A

reg [7:0] acc; //A
reg wr; //写使能端
reg rd; //读使能端

reg [7:0] addr_RAM; //RAM输入地址
wire [7:0] out_RAM; //RAM输出数据

reg fullout; //溢出位
RAM ram(
.addr(addr_RAM),
.din(acc),
.dout(out_RAM),
.wr(wr),
.rd(rd)
);

//对相应指令进行相应操作
always@(posedge clk )
begin
case(in_code)
stay:begin acc =acc; end
clr:begin acc = 0;dout= acc; end
inc:begin acc = acc+1;dout= acc; end
dec:begin acc = acc-1;dout= acc; end
load_num:
begin
acc = in_data;
dout= acc;
end
in_ram:
begin
rd=0;
wr=1;
addr_RAM=in_data;
dout=1;
end
out_ram:
begin
rd=1;
wr=0;
addr_RAM=in_data;
acc = out_RAM;
dout= acc;
end
add:
begin
add8(acc, in_data, acc, fullout);
dout=acc;
end
min:
begin
sub8(acc, in_data, acc, fullout);
dout=acc;
end
default: ;
endcase
end
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

3、RAM模块 (RAM.v)

此模块为我们自己写的RAM存储器,负责存储操作数。

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
module RAM(
addr,
din,
dout,
wr,
rd
);

input [7:0] addr; //存储器地址
input wr; //写使能端,高电平有效
input rd; //读使能端,高电平有效
input [7:0] din; //数据线输入
output reg [7:0] dout; //数据线输出

reg [7:0] mem [0:255]; //内部的存储空间

always @(wr or rd)
begin
if ( wr && !rd ) //写操作
begin
mem[addr]<= din;
end
if ( rd && !wr ) //读操作
begin
dout <= mem[addr];
end
end
endmodule

4、顶层文件 (TOP.v)

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
module cpu(
clk,
n_Rst,
data_in,
data_out
);

input clk;
input n_Rst;
input [15:0] data_in; //输入指令,低8位操作码地址,高8位操作数地址
output [7:0] data_out; //操作结果输出

wire [7:0] data_out;
wire [7:0] cade;
wire [7:0] data;


//取指操作人为输入


//译码
IDEC idec(
.clk(clk),
.reset(n_Rst),
.code_in(data_in), //指令输入端口
.code_addr(cade), //操作码输出端口
.data_addr(data) //操作数输出端口
);

//执行,数据data 既可以当操作数,也可以当ROM地址
IMP imp(
.clk(clk),
.in_data(data), //1.操作数输入端口 2.ROM地址输入端口
.in_code(cade), //操作码输入端口
.dout(data_out) //操作结果输出端口
);

endmodule

四、仿真 (tb.v)

我们是用ISE额外的ModelSim软件进行波形仿真,使用的testbench文件如下:

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 TEXT;

// Inputs
reg clk;
reg n_Rst;
reg [15:0] data_in;

// Outputs
wire [7:0] data_out;

// Instantiate the Unit Under Test (UUT)
cpu uut (
.clk(clk),
.n_Rst(n_Rst),
.data_in(data_in),
.data_out(data_out)
);

initial begin
// Initialize Inputs
clk = 0;
n_Rst = 0;
data_in = 0;

// Wait 100 ns for global reset to finish
#100;
#100 n_Rst=1;

// Add stimulus here
#110 data_in = 16'b0000001011111110; //load_rom 1111 1110//MOV A #0FEH
#60 data_in = 16'b0000000100000000; //in_rom 0000 0000 //MOV 00H(ROM0), A
#40 data_in = 16'b0000010100000000; //inc A //ADD A
#20 data_in = 16'b0000001100000000; //out_rom 0000 0000 //MOV A ,00H(ROM0)
#80 data_in = 16'b0000100000000001; //add 0000 0001 //ADD A ,#01H
#20 data_in = 16'b0000100100000010; //min 0000 0010 //SUB A ,#02H
#20 n_Rst=0;
end

always #10 clk = ~clk;

endmodule

得到的波形图如下所示:

可以看出data_out输出先显示出:

  1. A先加载0FEH
  2. 将数放入RAM
  3. 自加1
  4. 读出数据
  5. A加1
  6. A减2

用Synplify Pro以100MHz为仿真输入的结果是:

  • Worst Slack: 4.096
  • Estimated Frequence: 169.4MHz

电路图我就不贴了,就是俩模块。只要输入输出线都连上了基本就没有什么问题。有能力的可以尝试将ALU与Control Unit分开,再加个PC寄存器,就差不多是个常见的CPU结构了。

-------- 本文结束 感谢阅读 --------