幸运时时彩平台

干货

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

分类名:经验日期:2016-10-27作者:cruelfox 阅读原文

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的状态转移部分:

  1. module cpu0(clk, Iaddr, Ibus, PortI, PortO);

  2. input clk;

  3. output [9:0] Iaddr;

  4. input [7:0] Ibus;

  5. input [7:0] PortI;

  6. output reg [7:0] PortO;


  7. reg [9:0] pc;

  8. reg [7:0] RA;

  9. reg [7:0] Rn[0:7];

  10. reg zflag;


  11. assign Iaddr=pc;


  12. reg [7:0] opr;

  13. always @(posedge clk)

  14.     opr <= Ibus;

  15.    

  16. wire [1:0] opc1=opr[7:6];

  17. wire [5:0] opx=opr[5:0];

  18. wire [1:0] opc2=opr[5:4];

  19. wire [3:0] imm4=opr[3:0];

  20. wire [2:0] sel=opr[2:0];


  21. reg branch;


  22. always @(posedge clk) begin

  23.     pc <= pc + 1'b1;    // default increment

  24.     branch <= 1'b0;

  25.     if(~branch) begin

  26.         if(opc1==2'd3)

  27.             if(opr[5] | zflag) begin

  28.                 pc <= pc + {{5{opr[4]}},opr[4:0]};  // jump instruction

  29.                 branch <= 1'b1;

  30.             end

  31.     end

  32. end


  33. always @(posedge clk) begin

  34.     if(~branch) begin

  35.         if(opc1==2'd1 && opc2==2'd0)

  36.             Rn[sel] <= RA;

  37.     end

  38. end


  39. always @(posedge clk) begin

  40.     if(~branch) begin

  41.         case(opc1)

  42.             2'd0: begin

  43.                 if(opx==6'd0)

  44.                     RA <= PortI;

  45.                 end

  46.             2'd1: begin

  47.                     if(opc2==2'd1)

  48.                         RA <= Rn[sel];

  49.                 end

  50.             2'd2: begin

  51.                     case(opc2)

  52.                         2'd0: RA[7:4] <= imm4;

  53.                         2'd1: RA[3:0] <= imm4;

  54.                         2'd2: RA <= RA + imm4;

  55.                         2'd3: RA <= RA - imm4;

  56.                     endcase

  57.                 end

  58.         endcase

  59.     end

  60. end


  61. always @(posedge clk) begin

  62.     if(~branch) begin

  63.         if(opc1==2'd0 && opx==6'd1)

  64.             PortO <= RA;

  65.     end

  66. end


  67. always @(posedge clk) begin

  68.     if(~branch) begin

  69.         if(opc1==2'd1) begin

  70.             case(opc2)

  71.                 2'd3: zflag <= ~RA[sel];

  72.                 2'd2: zflag <= (RA==Rn[sel]);

  73.             endcase

  74.         end

  75.         if(opc1==2'd2) begin

  76.             if(opc2[1])

  77.                 zflag <= (RA==8'd0);

  78.         end

  79.     end

  80. end


  81. endmodule


复制代码



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


写测试程序了,没有编译器,汇编程序都得自己写呢。先就手写机器码吧

  1. module coderom(addr, data);

  2. input [9:0] addr;

  3. output reg [7:0] data;


  4. always @(addr) begin

  5.   case(addr)

  6.         0 : data = 8'h80;        // LOADAL 0

  7.         1 : data = 8'h90;        // LOADAH 0

  8.         2 : data = 8'h01;        // OUT A

  9.         3 : data = 8'hA1;        // ADDA #1

  10.         4 : data = 8'h40;        // MOV R0, A

  11.         5 : data = 8'h00;        // IN A

  12.         6 : data = 8'h77;        // TESTB A,7

  13.         7 : data = 8'h50;        // MOV A, R0

  14.         8 : data = 8'hDB;        // JUMPZ 4

  15.         9 : data = 8'hF8;        // JUMP 2

  16.         default: data=8'h00;

  17.         endcase

  18. end


  19. endmodule


复制代码



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

顶层模块,将ROM和CPU连起来:

  1. module cpu_top(clk, PortI, PortO);

  2. input clk;

  3. input [7:0] PortI;

  4. output [7:0] PortO;


  5. wire [7:0] rom_q;

  6. wire [9:0] rom_addr;


  7. cpu0 minicpu(.clk(clk),

  8.         .PortI(PortI),

  9.         .PortO(PortO),

  10.         .Ibus(rom_q),

  11.         .Iaddr(rom_addr));

  12.         

  13. coderom rom(

  14.     .addr(rom_addr),

  15.     .data(rom_q));  


  16. endmodule


复制代码




关键字:CPU
阅读原文 浏览量:7650 收藏:9
此内容由EEWORLD论坛网友 cruelfox 原创,如需转载或用于商业用途需征 得作者同意并注明出处

上一篇: 数字接口 开环 stm32F4+l6205步进电机驱动(待续)
下一篇: MicroPython使用MQTT协议接入OneNET云平台

评论

登录 | 注册 需要登陆才可发布评论    
评论加载中......

About Us 关于我们 客户服务 联系方式 器件索引 网站地图 最新更新 手机版

站点相关: 动力系统 底盘电子 车身电子 车载多媒体/导航 安全/防盗 总线与连接 车用传感器/MCU 检测与维修 其他技术 行业动态幸运时时彩平台

友情链接: 汽车电子

北京市海淀区知春路23号集成电路设计园量子银座1305 电话:(010)82350740 邮编:100191

电子工程世界版权所有 京ICP证060456号 幸运时时彩平台电信业务审批[2006]字第258号函 京公海网安备110108001534 Copyright ? 2005-2017 sonata9.com, Inc. All rights reserved
大资本彩票 小米彩票代理 亿信彩票手机官网 广西快3开奖 广西快3 幸运时时彩 广西快3 众盈彩票APP 大有彩票开户 幸运时时彩开奖结果