本人私人博客:Megalomania,大部分文章会现在博客上传,有不足之处欢迎指正。
这个是我这个学期FPGA的期末大作业,老师说是给两周的时间去写,其实还是在最后一周匆匆赶制出来的 不到DDL不开工。其实吧,要我自己评价是挺不满意的,主要是结构缩水了太多,为了节省代码量,我们甚至连PC指针砍掉,并且将ALU和Control Unit强行拼在了一起,变成了一个单地址指令结构的(严格来说应该也算不上)CPU。可能唯一的优点就是执行快吧…
设计原理
看的出来其实我们设计的这个原理十分简单,一个只剩下指令寄存器的控制单元,不用计算PC指针,还和运算单元结合在了一起,连在顶层文件中的连接都省了。由于是在家里上课,也没有上板之类的,只要ModuleSim能跑就行,所以把外围内存都删了(其实原理也挺简单的),直接看输入输出波形就完事了。但是内部的存储单元还是得要,不然有许多操作的中间值没地方放,自己写一个就行了,也不要什么IP Core之类的。
指令列表
输入信号为16位数 别问,问就是强迫症,高8位为指令码,低8位为操作数。
总共就九个指令,全部围绕寄存器A进行:
- in_ram:将A中的数写入RAM,地址为输入数据低8位所指的空间
- load_num:从外部输入数据到A
- out_ram:将RAM中的数写入A,地址为输入数据低8位所指的空间
- clr:清空A
- inc:A中的数自加1
- dec:A中的数自减1
- stay:等待指令
- add:将A中的数与输入数据低8位相加
- min:将A中的数与输入数据低8位相减
代码设计
工程总共有4个文件:输入模块,运算控制模块,RAM模块以及顶层模块
输入模块(IDEC.v)
输入模块负责将输入的16位二进制数分割为2个8位二进制数,高8位为指令,低8位为操作数或者RAM地址。
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
运算控制模块 (IMP.v)
运算控制模块大致代码分为两个部分,前半部分是声明指令是干什么的,后半部分进行相应指令的对应操作,最后的task部分为加减算法。
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)
);
//对相应指令进行相应操作
[email protected](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
RAM模块 (RAM.v)
此模块为我们自己写的RAM存储器,负责存储操作数。
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
顶层文件 (TOP.v)
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文件如下:
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输出先显示出:
- A先加载0FEH
- 将数放入RAM
- 自加1
- 读出数据
- A加1
- A减2
用Synplify Pro以100MHz为仿真输入的结果是:
- Worst Slack: 4.096
- Estimated Frequence: 169.4MHz
电路图我就不贴了,就是俩模块。只要输入输出线都连上了基本就没有什么问题。有能力的可以尝试将ALU与Control Unit分开,再加个PC寄存器,就差不多是个常见的CPU结构了。
文章转载请注明本人博客地址:
转载自:Megalomania - 【FPGA】基于FPGA的极简CPU设计