锐单电子商城 , 一站式电子元器件采购平台!
  • 电话:400-990-0325

基于Basys2的八位CPU的设计与FPGA实现

时间:2022-11-13 14:30:00 三极管hbd438t集成电路00k3

目录

一、设计指标(直接贴昆坤提出的要求)

二、设计思路:

1、完整的8位CPU的设计:

2.本设计思路:

三、Verilog实现

1、RAM:

2.指示寄存器IR

3、ALU

4、CPU

5、时钟分频

六、二进制译码模块:

7、动态扫描模块

8.数字管动态扫描顶层模块

9、顶层模块

四、FPGA实现

1、管脚约束

2、FPGA实现效果:


一、设计指标(直接贴昆坤提出的要求)

在选择Basys 2/3型开发板,8位自行设计5个指令CPU,并完成其功能仿真(前仿)、时序仿真(后仿)和FPGA无管脚约束,编程测试。

  1. 使用Verilog语言设计8位,有5个指令CPU Verilog代码和约束文件,五个指令分别是加法、减法、乘法、逻辑、逻辑或指令存储器的集成深度不超过16

指令格式:OpcodeACCOpdata1Opdata2

Opcode指令编码、ACC结果寄存器,Opdata18位操作数1、Opdata28位操作数2

  1. 设计功能电路设计、测试环境设计、测试激励设计、测试结果自动验证、测试激励覆盖所有指令功能(不考虑指令组合);
  2. 使用ISE、Vivado执行CPU功能仿真、综合、布局布线、后仿和执行FPGA编程测试。

二、设计思路:

1、完整的8位CPU的设计:

设计主要参考冯诺依曼计算机系统(存储器、控制器、计算器、输入、输出),一般为8位CPU程序计数器PC、指令寄存器IR、地址选择器、数据寄存器、算数逻辑单元ALU、由控制器组成,外设RAM和ROM,一般ROM存储指令,RAM存储数据。CPU执行指令的过程可分为四个阶段:取指-译码-执行-回写。即按程序计数器PC存储地址,从ROM取出指令送到地址总线AB,指令寄存器IR从地址总线获取指令后,将操作代码和操作数量(也可能是地址,查看指令是立即数字搜索还是存储器搜索)分离到算术逻辑单元ALU或RAM从数据总线取操作数,ALU操作后,数据可以回写RAM。

想详细学习CPU的Verilog设计可以看到嘉诚学长的博客:

(94条消息) 手把手教你设计字长8位的简单CPU-Verilog实现_雪天鱼博客-CSDN博客_8位cpu设计

2.本设计思路:

跟嘉诚学长CPU相比之下,作者写的CPU更像是套壳ALU,作者才疏学浅,在大二紧张的课程下只能想到这样的方案来实现:

为避免设计复杂的控制器状态机时序(作者尝试后放弃,CPU时序控制超级麻烦),作者使用Basys2开发板上的8个拨码开关作为指令输入,8个指令的高3个作为操作码,决定5种操作类型,后5个作为指令输入RAM(存储操作数)地址。这就省略了程序计数器PC、设计控制器。

在执行指令时,指令寄存器IR读取拨码开关输入的8位指令,并将高3位opcode与低5位address分离,操作码送往ALU,操作数地址发送RAM,取出连续两个地址的数据作为操作数,并发送到ALU操作数以16进制的形式显示在数字管上,运算结果输出送往LED显示显示模块

三、Verilog实现

省略了控制器和程序计数器PC,整个设计的难度瞬间从hard模式成为了easy,然而,在实现时仍然存在一些麻烦(与之前的实验相比)

1、RAM:

没有调用IP,我自己建模了一个RAM,初始化文件。

odule RAM(     input read,      input write,     input [4:0] addr,      input [7:0] data_in,      output [7:0]data_out1,     output [7:0]data_out2 );     /*RAM用于存储操作数*/     reg [7:0] ram[31:0];      assign data_out1 = (read)? ram[addr]:8'hzz;//从RAM中读取数据     assign data_out2 = read)? ram[addr+5'b00001]:8'hzz;//从RAM中读取数据
 
    always @(posedge write) 
    begin   //写数据给RAM
        ram[addr] <= data_in;
    end
    //RAM初始化
    initial
    begin
        $readmemb("D:/Xilinx/ISE/Project_files/cpu/RAM_initial.txt" , ram);
    end 
    
endmodule

 注意文件路径,初始化文件如下:

11101110
00110100
10111101
00101011
01100000
11001010
01001101
10010011
01000001
10010011
11100100
00101011
01000100
10100000
11000001
10000110
01001110
01010110
11101011
11001110
00010110
11110111
10001101
00111100
01100010
10100000
11011100
10011111
11000000
01010011
00000000
11110111

是通过随机数生成网站来生成的:在线生成随机二进制数 - bfotoolhttps://bfotool.com/zh/random-binary-numbers

 2、指令寄存器IR

module IR(
    input [7:0]instruction,
    output [4:0]addr,
    output [2:0]opcode
    );
    /*指令寄存器IR用于解析指令,将指令拆分为操作码和操作数地址两部分*/
    assign {opcode,addr} = instruction;
    
endmodule

3、ALU

不用多说,一个case语句就可以实现。但是需要注意,本次设计操作数是8位,如果进行乘法的话结果是16位会溢出,笔者又偷懒不想设计通用寄存器来存储高8位,因此笔者就设定在做乘法运算的时候只用两个操作数的低4位进行乘法运算,结果为8位。

module ALU(
    input [2:0]opcode,
    input [7:0]data1,
    input [7:0]data2,
    output reg[7:0]ALU_out
    );
    /*运算逻辑单元ALU,通过opcode判断运算类型,并进行相应的运算操作*/
    //opcode编码定义
    localparam ADD = 3'b000;
    localparam SUB = 3'b001;
    localparam MUL = 3'b011;
    localparam AND = 3'b010;
    localparam OR  = 3'b110;
    //进行相应的运算

    always@(*)
    begin
        casez(opcode)
            3'b000:
            begin
                ALU_out = data1 + data2;
            end
            3'b001:
            begin
                ALU_out = data1 + (~data2 + 8'h01);//补码运算
            end
            MUL:
            begin
                ALU_out = data1[3:0] * data2[3:0];
            end
            AND: 
            begin
                ALU_out = data1 & data2;
            end
            OR: 
            begin
                ALU_out = data1 | data2; 
            end
            default:
                ALU_out = 8'hZZ;
        endcase
    end
endmodule

4、CPU

实例化上述模块

module CPU(
    input [7:0]instruction,
    output [7:0]result,
    output [7:0]data1,
    output [7:0]data2
    );
    /*CPU指令:8位,高3位为操作码,低5位为操作数地址
    操作数存储于RAM中,CPU收到指令之后,通过指令寄存器译码,
    将opcode送往ALU判断运算类型,将addr送往RAM取出第一个操作数,
    然后addr加一,取出第二个操作数,然后ALU进行运算,
    运算结果送往数码管显示模块输出*/
    //声明内部信号线
    wire [7:0]DB1,DB2;//数据总线
    wire [4:0]AB;//地址总线
    wire [2:0]opcode;//操作码
    wire [7:0]ALU_out;
    //声明内部寄存器
    
    //模块实例化
    //RAM实例化
    RAM ram(
        .read(1'b1), 
        .write(1'b0),
        .addr(AB), 
        .data_in(1'b0),
        .data_out1(DB1),
        .data_out2(DB2)
    );
    //IR实例化
    IR ir(
        .instruction(instruction),
        .addr(AB),
        .opcode(opcode)
    );
    //ALU实例化
    ALU alu(
        .opcode(opcode),
        .data1(DB1),
        .data2(DB2),
        .ALU_out(ALU_out)
    );
    //将操作数、运算结果输出
    assign data1 = DB1;
    assign data2 = DB2;
    assign result = ALU_out;
    
endmodule

 5、时钟分频

将系统50MHz时钟分频为1KHz时钟,用于数码管动态扫描

module clk_div(
    input clk,
    output reg clk_div
    );
    //输入板子50MHz时钟,输出1KHz时钟
    //计数分频:50000次
    reg [15:0]cnt = 16'd0;
    always@(posedge clk)
    begin
        if(cnt == 16'd50000)
        begin
            cnt <= 16'd0;
        end
        else
        begin
            cnt <= cnt + 16'd1;
        end
    end
    //输出时钟控制
    always@(posedge clk)
    begin
        if(cnt <= 16'd24999)
        begin
            clk_div <= 1'b1;
        end
        else
        begin
            clk_div <= 1'b0;
        end
    end
    
endmodule

6、二进制译码模块:

将两个操作数转化为共阳极数码管段选码,用于动态扫描显示

module wei_encoder(
    input [3:0]hex_number,
    output reg [7:0]display_code
    );
    //将输入的十六进制代码转换为共阳极数码管段选编码并输出
    always@(*)
    begin
        case(hex_number)
            4'b0000:display_code = 8'hc0;//0
            4'b0001:display_code = 8'hf9;//1
            4'b0010:display_code = 8'ha4;//2
            4'b0011:display_code = 8'hb0;//3
            4'b0100:display_code = 8'h99;//4
            4'b0101:display_code = 8'h92;//5
            4'b0110:display_code = 8'h82;//6
            4'b0111:display_code = 8'hf8;//7
            4'b1000:display_code = 8'h80;//8
            4'b1001:display_code = 8'h90;//9
            4'b1010:display_code = 8'h88;//A
            4'b1011:display_code = 8'h83;//B
            4'b1100:display_code = 8'hc6;//C
            4'b1101:display_code = 8'ha1;//D
            4'b1110:display_code = 8'h86;//E
            4'b1111:display_code = 8'h8e;//F
            default:display_code = 8'hff;//无
        endcase
    end
endmodule

7、动态扫描模块

输入分频后的时钟,通过扫描计数器的状态控制在特定的位选输出特定的段选码

module Display(
    input clk_div,
    input [7:0]Out_number_1,
    input [7:0]Out_number_2,
    input [7:0]Out_number_3,
    input [7:0]Out_number_4,
    output reg [7:0]duan_code,
    output reg [3:0]wei_code
    );
    //将输入的十六进制代码转换为共阳极数码管段选码动态扫描输出
    reg [2:0]sel_cnt;//扫描计数器
    //动态扫描
    always@(posedge clk_div)
    begin
        if(sel_cnt == 3'd4)
        begin
            sel_cnt <= 3'd0;
        end
        else
        begin
            sel_cnt <= sel_cnt + 3'd1;
        end
    end
    always@(posedge clk_div)
    begin
            case(sel_cnt)
                3'd0:
                begin
                    wei_code <= 4'b0001;
                    duan_code <= Out_number_1;
                end
                3'd1:
                begin
                    wei_code <= 4'b0010;
                    duan_code <= Out_number_2;
                end
                3'd2:
                begin
                    wei_code <= 4'b0100;
                    duan_code <= Out_number_3;
                end
                3'd3:
                begin
                    wei_code <= 4'b1000;
                    duan_code <= Out_number_4;
                end
                default:
                begin
                    wei_code <= 4'b0000;
                    duan_code <= 8'hff;
                end
            endcase
    end

endmodule

 8、数码管动态扫描的顶层模块

发现之前写的都是在顶层模块中实力化分频器、译码器和动态扫描,觉得可以将其先封装为一个通用的显示模块

module SEG_display(
    input clk,
    input [7:0]number1,
    input [7:0]number2,
    output [3:0]wei_code,
    output [7:0]duan_code
    );
    //声明内部信号线
    wire [7:0]Out_number_1;//将要输出的段选码
    wire [7:0]Out_number_2;
    wire [7:0]Out_number_3;
    wire [7:0]Out_number_4;
    wire clk_div;//分频后的时钟
    
    //模块实例化
    //时钟分频,用于数码管计数扫描
    clk_div div(
        .clk(clk),
        .clk_div(clk_div)
    );
    //将将要输出的数转化为段选码
    wei_encoder encoder1(
        .hex_number(number1[3:0]),
        .display_code(Out_number_1)
    );
    wei_encoder encoder2(
        .hex_number(number1[7:4]),
        .display_code(Out_number_2)
    );
    wei_encoder encoder3(
        .hex_number(number2[3:0]),
        .display_code(Out_number_3)
    );
    wei_encoder encoder4(
        .hex_number(number2[7:4]),
        .display_code(Out_number_4)
    );
    //数码管动态扫描显示
    Display display(
        .clk_div(clk_div),
        .Out_number_1(Out_number_1),
        .Out_number_2(Out_number_2),
        .Out_number_3(Out_number_3),
        .Out_number_4(Out_number_4),
        .duan_code(duan_code),
        .wei_code(wei_code)
    );
endmodule

 9、顶层模块

模块实例化

module top(
    input clk,
    input [7:0]instruction,
    output [7:0]duan_code,
    output [3:0]wei_code,
    output [7:0]led_display
    );
    //声明内部信号线
    wire [7:0]data1,data2;
    //模块实例化
    CPU core(
        .instruction(instruction),
        .result(led_display),
        .data1(data1),
        .data2(data2)
    );
    
    SEG_display seg(
        .clk(clk),
        .number1(data1),
        .number2(data2),
        .wei_code(wei_code),
        .duan_code(duan_code)
    );
    
endmodule

四、FPGA实现

 1、管脚约束

用IOPlanAhead进行管脚绑定:

生成的约束文件如下: 

NET "duan_code[7]" LOC = N13;
NET "duan_code[6]" LOC = M12;
NET "duan_code[5]" LOC = L13;
NET "duan_code[4]" LOC = P12;
NET "duan_code[3]" LOC = N11;
NET "duan_code[2]" LOC = N14;
NET "duan_code[1]" LOC = H12;
NET "duan_code[0]" LOC = L14;
NET "wei_code[3]" LOC = K14;
NET "wei_code[2]" LOC = M13;
NET "wei_code[1]" LOC = J12;
NET "wei_code[0]" LOC = F12;
NET "led_display[0]" LOC = M5;
NET "led_display[1]" LOC = M11;
NET "led_display[2]" LOC = P7;
NET "led_display[3]" LOC = P6;
NET "led_display[4]" LOC = N5;
NET "led_display[5]" LOC = N4;
NET "led_display[6]" LOC = P4;
NET "led_display[7]" LOC = G1;
NET "clk" LOC = B8;
NET "instruction[0]" LOC = P11;
NET "instruction[1]" LOC = L3;
NET "instruction[2]" LOC = K3;
NET "instruction[3]" LOC = B4;
NET "instruction[4]" LOC = G3;
NET "instruction[5]" LOC = F3;
NET "instruction[6]" LOC = E2;

# PlanAhead Generated physical constraints 

NET "instruction[7]" LOC = N3;

2、FPGA实现效果:

 ①opcode = 000,加法,8’hbd + 8’h34 = 11110001

⑵opcode = 001,减法,8’h34 - 8’hbd = 01110111

⑶opcode = 001,乘法,4’h4 * 4’hd = 00110100

⑷opcode = 010,按位与,8’h34 & 8’hbd = 00110100

⑸opcode = 110,按位或,8’h34 | 8’hbd = 101111010

锐单商城拥有海量元器件数据手册IC替代型号,打造电子元器件IC百科大全!

相关文章