cruelfox 发表于 2016-10-27 17:25

[10月DIY]编写个超简单的CPU

<div class='showpostmsg'>FPGA的处理能力固然强大,但在进行程序化的任务时,用状态机来实现有时就显得不如CPU写程序那么简洁。在FPGA里面也可以用逻辑来搭出简单的CPU,并固化一小段代码去实现特定的功能。考虑下最简单的CPU是什么样子呢?

最少,需要有读取程序(指令),并执行指令的过程。指令存放在一块内存当中,CPU每步取一条指令来执行,根据读出的指令内容,内部的状态发生转变——比如寄存器按指令要求进行运算,比如访问外部的端口(或总线)。指令是一个编码,描述这一步需要做的事情;执行指令的过程就是状态转移的过程。我实验的这个超简单CPU是这样:

上图中,PC是Program Counter,就是程序计数器,选择ROM中程序执行的地址。opr用来存放当前的指令,它的内容从ROM中读到。寄存器还有A寄存器和R0~R7寄存器,用来计算和存放结果,另外还有一个1-bit的“零"标志位zflag,是给条件转移指令用的。当然,若只是里面的寄存器变来变去,这个CPU就没有实用价值了,所以还有一个输入端口,以及一个输出端口,用来和寄存器A交换数据。

设计指令字长为8-bit,寄存器宽度也为8-bit。每条指令都是从ROM中读8-bit,可以最多有256种不同的指令,当然指令中能编码立即数,所以指令不会有那么多种。我给这个CPU设计了14条指令:

跳转指令有2条,无条件转移和Z条件转移,转移范围为5-bit相对地址,即-16~+15。
带立即数指令有4条,因为指令才8-bit,立即数只好分配4-bit了。装入A寄存器的高4位或低4位,以及与A做加减法。
R0~R7寄存器只能与A寄存器进行copy和比较操作。
影响zflag标志的指令有位测试指令TESTB, 比较指令COMP和加减法指令。
指令空间并没有用完,可以根据需要再补充指令。

用Verilog语言来写这个CPU的状态转移部分:
module cpu0(clk, Iaddr, Ibus, PortI, PortO);
input clk;
output Iaddr;
input Ibus;
input PortI;
output reg PortO;

reg pc;
reg RA;
reg Rn;
reg zflag;

assign Iaddr=pc;

reg opr;
always @(posedge clk)
    opr <= Ibus;
   
wire opc1=opr;
wire opx=opr;
wire opc2=opr;
wire imm4=opr;
wire sel=opr;

reg branch;

always @(posedge clk) begin
    pc <= pc + 1'b1;    // default increment
    branch <= 1'b0;
    if(~branch) begin
      if(opc1==2'd3)
            if(opr | zflag) begin
                pc <= pc + {{5{opr}},opr};// jump instruction
                branch <= 1'b1;
            end
    end
end

always @(posedge clk) begin
    if(~branch) begin
      if(opc1==2'd1 && opc2==2'd0)
            Rn <= RA;
    end
end

always @(posedge clk) begin
    if(~branch) begin
      case(opc1)
            2'd0: begin
                if(opx==6'd0)
                  RA <= PortI;
                end
            2'd1: begin
                  if(opc2==2'd1)
                        RA <= Rn;
                end
            2'd2: begin
                  case(opc2)
                        2'd0: RA <= imm4;
                        2'd1: RA <= imm4;
                        2'd2: RA <= RA + imm4;
                        2'd3: RA <= RA - imm4;
                  endcase
                end
      endcase
    end
end

always @(posedge clk) begin
    if(~branch) begin
      if(opc1==2'd0 && opx==6'd1)
            PortO <= RA;
    end
end

always @(posedge clk) begin
    if(~branch) begin
      if(opc1==2'd1) begin
            case(opc2)
                2'd3: zflag <= ~RA;
                2'd2: zflag <= (RA==Rn);
            endcase
      end
      if(opc1==2'd2) begin
            if(opc2)
                zflag <= (RA==8'd0);
      end
    end
end

endmodule


除了指令所描述的寄存器的操作外,还多了一个branch寄存器和条件判断,这是做什么呢?请注意,PC寄存器所指的是下一条要执行的指令地址(默认总是 pc <= pc + 1),但是如果遇到跳转指令,下一条指令是紧接着跳转指令的,将在下一个时钟沿上被读入opr,但是这条指令不该被执行,所以需要条件判断一下。而要跳转的位置的指令需要在PC更新之后的下一拍才能够被读入opr,这就是转移指令比普通指令要多花一个时钟周期的原因(这个CPU是两级流水线)。


写测试程序了,没有编译器,汇编程序都得自己写呢。先就手写机器码吧
module coderom(addr, data);
input addr;
output reg data;

always @(addr) begin
case(addr)
        0 : data = 8'h80;        // LOADAL 0
        1 : data = 8'h90;        // LOADAH 0
        2 : data = 8'h01;        // OUT A
        3 : data = 8'hA1;        // ADDA #1
        4 : data = 8'h40;        // MOV R0, A
        5 : data = 8'h00;        // IN A
        6 : data = 8'h77;        // TESTB A,7
        7 : data = 8'h50;        // MOV A, R0
        8 : data = 8'hDB;        // JUMPZ 4
        9 : data = 8'hF8;        // JUMP 2
        default: data=8'h00;
        endcase
end

endmodule


这个程序不干啥有价值的,就是检测到输入端口第7位为高时,循环加一计数,输出到端口点LED.

顶层模块,将ROM和CPU连起来:
module cpu_top(clk, PortI, PortO);
input clk;
input PortI;
output PortO;

wire rom_q;
wire rom_addr;

cpu0 minicpu(.clk(clk),
      .PortI(PortI),
      .PortO(PortO),
      .Ibus(rom_q),
      .Iaddr(rom_addr));
      
coderom rom(
    .addr(rom_addr),
    .data(rom_q));

endmodule



</div><script>                                var loginstr = '<div class="locked">查看精华帖全部内容,请<a href="javascript:;"   style="color:#e60000" class="loginf">登录</a>或者<a href="https://bbs.eeworld.com.cn/member.php?mod=register_eeworld.php&action=wechat" style="color:#e60000" target="_blank">注册</a></div>';
                               
                                if(parseInt(discuz_uid)==0){
                                                                                        (function($){
                                                        var postHeight = getTextHeight(400);
                                                        $(".showpostmsg").html($(".showpostmsg").html());
                                                        $(".showpostmsg").after(loginstr);
                                                        $(".showpostmsg").css({height:postHeight,overflow:"hidden"});
                                                })(jQuery);
                                }
</script><script type="text/javascript">(function(d,c){var a=d.createElement("script"),m=d.getElementsByTagName("script"),eewurl="//counter.eeworld.com.cn/pv/count/";a.src=eewurl+c;m.parentNode.insertBefore(a,m)})(document,523)</script>

白丁 发表于 2016-10-27 22:46

本帖最后由 白丁 于 2016-10-31 09:29 编辑

记得夏老师的verilog数字系统设计教程上有个简化risc cpu设计,还可以看看opencores上开源的or1k项目,资料也比较多。如果真是嫌状态机麻烦可以用x或者a家自己的软核啊。记得一个大大对我说,做一个简单cpu简单,但是难点是搞编译器,你这个开始说的固化一小段代码能实现吗?

dontium 发表于 2016-10-27 17:57

好帖子!

michael_llh 发表于 2016-10-27 20:22

66666666666

lb8820265 发表于 2016-10-27 21:11

{:1_103:}牛,期待更多的FPGA设计

yjtyjt 发表于 2016-10-27 21:11

自己学问浅,看不懂。

白丁 发表于 2016-10-27 22:42

很有学习价值,谢谢楼主分享

mars4zhu 发表于 2016-10-28 09:11

白丁 发表于 2016-10-27 22:46
记得系老师的verilog数字系统设计教程上有个简化risc cpu设计,还可以看看opencores上开源的or1k项目,资料 ...

没错,编译器是屠龙之术。

cruelfox 发表于 2016-10-28 10:10

白丁 发表于 2016-10-27 22:46
记得系老师的verilog数字系统设计教程上有个简化risc cpu设计,还可以看看opencores上开源的or1k项目,资料 ...

软核是有现成的,配套的编译器也都全了,当然用起来比这个省心得多。
这个CPU没有RAM支持,没有堆栈,逻辑运算单元基本的AND, OR都没有(虽然可以扩充),和A家NIOS比弱爆了……我这么嘛,就是体验一把CPU怎么工作的,用比较少的资源实现一个执行器(姑且这么叫,比真正CPU弱多了),够完成特殊要求就够了。
我上面的代码是人肉汇编的:lol 编译器?用C写还是不要想了。汇编器可以有。

shinykongcn 发表于 2016-10-31 09:38

看起来好厉害的样子~{:1_103:}

sgf01 发表于 2016-12-23 19:59

厉害

priceruxu 发表于 2017-2-12 18:19

强{:1_124:}

miaods2002 发表于 2020-6-12 10:55

<p>可以这样玩,这太厉害了吧!佩服</p>

w494143467 发表于 2020-10-12 15:42

<p>没有玩过FPGA,但是后面也要开始接触了,原来可以这么玩,以后我也试试,厉害了!!!</p>

rsce 发表于 2020-11-11 15:12

<p>66666666</p>

leo2002zhang 发表于 2021-10-30 11:13

<p>不错,点个赞。虽然简单但是自己做做还是不一样的。兴趣提起来了,慢慢可以更新迭代再丰富。<img height="48" src="https://bbs.eeworld.com.cn/static/editor/plugins/hkemoji/sticker/facebook/wanwan58.gif" width="54" /></p>

1072978274 发表于 2021-12-19 10:57

写的很详细

Fred_1977 发表于 2022-3-26 09:22

<p>写的很不错,特别是整洁规范,</p>

radioo 发表于 2022-7-19 17:16

<p>谢谢分享</p>

吾妻思萌 发表于 2022-8-4 13:29

<p>CPU好做编译器难啊,这玩意最难的是上下层的交互吧。</p>
页: [1] 2
查看完整版本: [10月DIY]编写个超简单的CPU