首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

自己编写一个超简单的CPU

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 [9:0] Iaddr;

input [7:0] Ibus;

input [7:0] PortI;

output reg [7:0] PortO;

reg [9:0] pc;

reg [7:0] RA;

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

reg zflag;

assign Iaddr=pc;

reg [7:0] opr;

always @(posedge clk)

  opr

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

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

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

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

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

reg branch;

always @(posedge clk) begin

  pc

  branch

  if(~branch) begin

      if(opc1==2'd3)

          if(opr[5] | zflag) begin

              pc

              branch

          end

  end

end

always @(posedge clk) begin

  if(~branch) begin

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

          Rn[sel]

  end

end

always @(posedge clk) begin

  if(~branch) begin

      case(opc1)

          2'd0: begin

              if(opx==6'd0)

                  RA

              end

          2'd1: begin

                  if(opc2==2'd1)

                      RA

              end

          2'd2: begin

                  case(opc2)

                      2'd0: RA[7:4]

                      2'd1: RA[3:0]

                      2'd2: RA

                      2'd3: RA

                  endcase

              end

      endcase

  end

end

always @(posedge clk) begin

  if(~branch) begin

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

          PortO

  end

end

always @(posedge clk) begin

  if(~branch) begin

      if(opc1==2'd1) begin

          case(opc2)

              2'd3: zflag

              2'd2: zflag

          endcase

      end

      if(opc1==2'd2) begin

          if(opc2[1])

              zflag

      end

  end

end

endmodule

除了指令所描述的寄存器的操作外,还多了一个branch寄存器和条件判断,这是做什么呢?

请注意,PC寄存器所指的是下一条要执行的指令地址(默认总是 pc

module coderom(addr, data);

input [9:0] addr;

output reg [7:0] 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 [7:0] PortI;

output [7:0] PortO;

wire [7:0] rom_q;

wire [9:0] 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

  • 发表于:
  • 原文链接https://page.om.qq.com/page/O4vBNotUFbtTuI1H-dY_Ykcg0
  • 腾讯「腾讯云开发者社区」是腾讯内容开放平台帐号(企鹅号)传播渠道之一,根据《腾讯内容开放平台服务协议》转载发布内容。
  • 如有侵权,请联系 cloudcommunity@tencent.com 删除。

扫码

添加站长 进交流群

领取专属 10元无门槛券

私享最新 技术干货

扫码加入开发者社群
领券