trustintruth · 2020年05月25日

FPGA初始——自制CPU(中)

上回书说到,指令集建立好了,这次我们来做CPU的设计!
作者:Trustintruth
来源:https://zhuanlan.zhihu.com/p/97064690

在说数字通路之前,我们先对几个基础模块进行设计。(说实话,控制信号太多了导致篇幅太长了,经咨询决定数据通路及控制都交给下篇)

宏定义

首先交代宏定义

存储器(存放数据和程序)

可以理解为存储着程序的ROM,当CPU启动时从0地址开始执行程序。在设计这个存储器使用Single Port ROM

使用Xilinx FPGA Block RAM 单端口的RAM

下面一个always块设计来产生就绪信号,当复位信号到来时进行初始化,在片选信号和地址选通信号同时有效时,就绪信号变为有效

    x_s3e_sprom x_s3e_sprom (
        .clka  (clk),                    
        .addra (addr),                    
        .douta (rd_data)                
    );

    /**********  ready signal**********/
    always @(posedge clk or `RESET_EDGE reset) begin
        if (reset == `RESET_ENABLE) begin
            rdy_ <= #1 `DISABLE_;
        end else begin
            if ((cs_ == `ENABLE_) && (as_ == `ENABLE_)) begin
                rdy_ <= #1 `ENABLE_;
            end else begin
                rdy_ <= #1 `DISABLE_;
            end
        end
    end

endmodule

总线(BUS)

总线是将CPU、内存和I/O相互连接的共享通道。我们的CPU设计使用时钟信号同步数据传输的同步总线。具体实现交由下

CPU小部件

1.通用寄存器:指令集限制最大可以指定三个寄存器作为操作数,从两个寄存器中取操作数进行操作,随后将结果写入第三个寄存器中。所以才设计通用寄存器时需要两个读取端口和一个写入端口,同时自然地需要在写入端口加入写使能信号以保障写入数据正确。

读取操作即为两个assign语句实现,注意在读取的同时对相同地址进行写入操作时,则直接将写入的数据输出。

写入操作时,首先异步复位,寄存器初始化为0,当有效信号有效时,向指定地址单元写入制定数据。

    /********** Read(Write After Read) **********/
    // readport 0
    assign rd_data_0 = ((we_ == `ENABLE_) && (wr_addr == rd_addr_0)) ? 
                       wr_data : gpr[rd_addr_0];
    // readport 1
    assign rd_data_1 = ((we_ == `ENABLE_) && (wr_addr == rd_addr_1)) ? 
                       wr_data : gpr[rd_addr_1];

    /********** write **********/
    always @ (posedge clk or `RESET_EDGE reset) begin
        if (reset == `RESET_ENABLE) begin 
            for (i = 0; i < `REG_NUM; i = i + 1) begin
                gpr[i]         <= #1 `WORD_DATA_W'h0;
            end
        end else begin
            if (we_ == `ENABLE_) begin 
                gpr[wr_addr] <= #1 wr_data;
            end
        end
    end



2.SPM

在本CPU设计中增加了一个可以不经过总线直接访问的专用内存,其中包括一个Dual Port RAM,他的宏为:

首先IF阶段(后面我们介绍这部分,现在先理解为一个阶段)的地址有效信号又消失,读写判断信号判断为写入时,A端口(portA)写入有效信号为有效。同理下面B端口,只不过是判断MEM阶段的地址有效信号和读写判断信号为写入。

    always @(*) begin
        /*Port A */
        if ((if_spm_as_ == `ENABLE_) && (if_spm_rw == `WRITE)) begin
            wea = `MEM_ENABLE;    //  able
        end else begin
            wea = `MEM_DISABLE; // disable
        end
        /* Port B */
        if ((mem_spm_as_ == `ENABLE_) && (mem_spm_rw == `WRITE)) begin
            web = `MEM_ENABLE;    // able
        end else begin
            web = `MEM_DISABLE; // disable
        end
    end

    /********** Xilinx FPGA Block RAM  **********/
    x_s3e_dpram x_s3e_dpram (
        /**********Port A   IF**********/
        .clka  (clk),            
        .addra (if_spm_addr),    
        .dina  (if_spm_wr_data),
        .wea   (wea),            
        .douta (if_spm_rd_data),
        /********** Port B : MEM **********/
        .clkb  (clk),            
        .addrb (mem_spm_addr),    
        .dinb  (mem_spm_wr_data), 
        .web   (web),            
        .doutb (mem_spm_rd_data)
    );



3.ALU

ALU,算数逻辑单元是根据输入指定的操作对数据进行处理,并输出处理结果。在本次的设计中,ALU的输入为一个操作码和两个数据,输出为运算结果和溢出信号。ALU承担着CPU中的逻辑预算数运算的任务,所以基础的操作需要包含加法,减法(补码加法),左移,右移,与,或等操作,设计框图如下:

    always @(*) begin
        case (op)
            `ALU_OP_AND     : begin //iAND
                out      = in_0 & in_1;
            end
            `ALU_OP_OR     : begin // iOR
                out      = in_0 | in_1;
            end
            `ALU_OP_XOR     : begin // iXOR
                out      = in_0 ^ in_1;
            end
            `ALU_OP_ADDS : begin 
                out      = in_0 + in_1;
            end
            `ALU_OP_ADDU : begin 
                out      = in_0 + in_1;
            end
            `ALU_OP_SUBS : begin 
                out      = in_0 - in_1;
            end
            `ALU_OP_SUBU : begin 
                out      = in_0 - in_1;
            end
            `ALU_OP_SHRL : begin 
                out      = in_0 >> in_1[`ShAmountLoc];
            end
            `ALU_OP_SHLL : begin 
                out      = in_0 << in_1[`ShAmountLoc];
            end
            default         : begin //  (No Operation)
                out      = in_0;
            end
        endcase
    end

    /********** �I�[�o�t���[�`�F�b�N **********/
    always @(*) begin
        case (op)
            `ALU_OP_ADDS : begin 
                if (((s_in_0 > 0) && (s_in_1 > 0) && (s_out < 0)) ||
                    ((s_in_0 < 0) && (s_in_1 < 0) && (s_out > 0))) begin
                    of = `ENABLE;
                end else begin
                    of = `DISABLE;
                end
            end
            `ALU_OP_SUBS : begin 
                if (((s_in_0 < 0) && (s_in_1 > 0) && (s_out > 0)) ||
                    ((s_in_0 > 0) && (s_in_1 < 0) && (s_out < 0))) begin
                    of = `ENABLE;
                end else begin
                    of = `DISABLE;
                end
            end
            default        : begin 
                of = `DISABLE;
            end
        endcase
    end

推荐阅读

关注此系列,请关注专栏FPGA的逻辑
推荐阅读
关注数
10512
内容数
513
FPGA Logic 二三事
目录
极术微信服务号
关注极术微信号
实时接收点赞提醒和评论通知
安谋科技学堂公众号
关注安谋科技学堂
实时获取安谋科技及 Arm 教学资源
安谋科技招聘公众号
关注安谋科技招聘
实时获取安谋科技中国职位信息