8位CPU硬件设计

8位CPU硬件设计

六月 25, 2025 次阅读

合作方:pointer-to-bios

设计概要如下:

设计要求:
1、通用寄存器8位,ALU 8位,内部数据总线8位。
2、支持4个8位通用寄存器R0、R1、R2和R3。
3、标志寄存器(FLAGS或者PSW)应至少有CF和ZF标志位。
4、指令长度8位(指令操作码4位,指令操作数合计4位),指令寄存器IR 8位,程序(指令)计数器PC 8位。
5、至少支持下列指令:MOV、ADD、SUB、AND、OR、NOT、XOR和RTN,其中RTN指令用于将PC清零,即返回程序第一条指令重新开始执行。
6、设计SHL、SHR等移位指令。
7、设计LD、ST等内存数据读写指令。
8、可选设计JMP、JZ、JNZ等跳转指令。
9、可以具备基本的通用寄存器监控功能用于调试程序。
10、考虑与硬件外设连接的扩展设计能力。

在设计一个通用8位 CPU 之前,首先需要考虑的是如何设计一个 CPU 物理结构图,这里我们先建设一个简易的物理逻辑框架出来:

CPU_8bit

设计概述

  1. 地址总线

地址总线实际上是CU输出的控制信号,为了降低CU的复杂度,将部分仅有输入/输出语义控制信号的模块编址,形成3位目标/源地址总线对,大大减少CU的控制信号数量。

  1. 数据总线的实现

本CPU在FPGA平台 Quartus2 上实现,结构图中的所有输出到总线的信号线通过多路复用实现,否则综合器有可能将全部模块都优化掉。

  1. 锁相环

由于本CPU最终将运行在物理芯片中,需要使用锁相环将芯片的全局时钟频率调整到CPU时序延迟允许之内。

基于这个框架图,我们将依次设计各个部件的模块逻辑

一、PC模块设计

module PC(
    input         clk,
    input         pc_inc,       // PC是否自增
    input         reset,
    (* keep, preserve *)output reg [7:0] databus       // 统一数据总线
);
    // reg [7:0] cur_pc;     // 当前程序计数器值

    //PC自增寄存器
    wire [7:0] next_pc;
    wire co;
    // 三态总线驱动(完全由外部控制)
    Adder PCInc(
        .op1(databus),
        .op2(8'h01),
        .suben(0),
        .sum(next_pc),
        .co(co)
    );
    // 输出到总线
    // assign databus = cur_pc; // 当pc_out为1时,输出当前PC值,否则为高阻态
    // PC核心逻辑
    always @(posedge clk or posedge reset) begin
        if (reset)              // PC置零
            databus <= 8'h00;
        else if (pc_inc)        // PC自增
            databus <= next_pc;
        else databus <= databus;  // PC不变(无信号)
    end
endmodule

PC信号指示图:
PC

输入端有时钟信号,而通过 PC 逻辑图我们可以看出,PC 需要两个(出去输出信号),一个是控制 PC 自增的信号,一个是让 PC 计数器清零的信号,这两个信号均有CU模块控制

而在 PC 内部,PC 模块需要有一个 add 模块用于计算 PC 自增后的下一个数是多少,这个加法逻辑单元直接复用 ALU 模块中的加法器即可

PC 的信号输出由 always 模块去控制,当到达时钟上升沿或者 reset 信号上升沿(reset变为1)的时候,就会触发 PC 内部值的改变,若遇到 reset 信号,则将输出总线数据置零,若
遇到 pc_inc 信号为1,则将 PC 早已算好的 next_pc 传给 总线输出单元,PC 单元的值会再次传递给加法器 Adder 的输入,因为 Adder 为组合电路,因此,几乎在同一时刻 PC 的next_pc会
再次计算好,等待 PC 接收到下一个 pc_inc 信号

二、ALU模块设计

代码总体设计如下:

module ALU(databus,inbus,clk,alua_in,alub_in,sel,cf);
  (* keep, preserve *)output reg [7:0] databus;       // 统一数据总线
    input [7:0] inbus;
  input clk;
  input alua_in;
  input alub_in;
  input[2:0] sel;
  output cf;
  
  wire[7:0] op1;
  wire[7:0] op2;
  wire [7:0] result;

  
  OPReg A(clk,alua_in,inbus,op1);
    OPReg B(clk, alub_in,inbus,op2);
  ALUCore u1(op1,op2,sel,result,cf);
  // OutputBufReg R(clk,result,alu_out,databus);
    always @(*) begin

      databus<=result;
  end

endmodule

module OPReg(clk,we,din,dout);
  input clk;
  input we;
  input[7:0] din;
  output[7:0] dout;
  reg[7:0] dout;
  
  always @(posedge clk)
  begin
    if(we==1'b1)
    begin
      dout <= din;
    end
  end
endmodule

module ALUCore(
    input [7:0]  op1,
    input [7:0]  op2,
    input [2:0]  sel,    // 扩展为3位操作码
    output reg [7:0] result,
    output reg       cf
);
    
    // 加法器接口
    reg [7:0] adder_op1;
    reg [7:0] adder_op2;
    reg suben;
    wire [7:0] adder_sum;
    wire adder_cout;
    
    Adder u1(adder_op2, adder_op1, suben, adder_sum, adder_cout);
    
    always @(*) begin
        case(sel)
            // 算术运算
            3'b000, 3'b001: begin  // 加法/减法
                // 汇编代码其实是左源右目标,所以反过来放
                adder_op1 = op1;
                adder_op2 = op2;
                suben = sel[0];
                result = adder_sum;
                cf = adder_cout;
            end
            3'b010: begin  // 左移
                adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0; // 左移不需要减法
                cf = 1'b0; // 左移不产生进位
                // if(adder_op1[7] | adder_op1[6] | adder_op1[5] | adder_op1[4] | adder_op1[3]) begin
                //     result = 8'b0; // 左移后全零
                // end else begin
                result = op2 << op1;
                // end
            end
            3'b011: begin  // 右移
                adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0; // 左移不需要减法
                cf = 1'b0; // 左移不产生进位
                // if(adder_op1[7] | adder_op1[6] | adder_op1[5] | adder_op1[4] | adder_op1[3]) begin
                //     result = 8'b0; // 右移后全零
                // end else begin
                result = op2 >> op1;
                // end
            end
            // 逻辑运算
            3'b100: begin  // 与(AND)
        adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0;
                result = op1 & op2;
                cf = 1'b0;  // 逻辑运算清零CF
            end
            3'b101: begin  // 或(OR)
        adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0;
                result = op1 | op2;
                cf = 1'b0;
            end
            3'b110: begin  // 与非(NAND)
        adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0;
                result = ~(op1 & op2);
                cf = 1'b0;
            end
            3'b111: begin  // 异或(XOR)
        adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0;
                result = op1 ^ op2;
                cf = 1'b0;
            end
        endcase
    end
endmodule


module Adder(
    input [7:0]  op1,     // 操作数1
    input [7:0]  op2,     // 操作数2
    input        suben,   // 减法使能(1=减法)
    output [7:0] sum,     // 和/差结果
    output       co       // 进位/借位
);
    // 减法转换为补码加法
    wire [7:0] adj_op2 = op2 ^ {8{suben}};
    wire cin = suben;     // 减法时cin=1(补码+1)

    // 生成位(Generate)和传播位(Propagate)
    wire [7:0] G = op1 & adj_op2;
    wire [7:0] P = op1 ^ adj_op2;

    // 超前进位计算(分组优化)
    wire [7:0] C;
    assign C[0] = cin;
    assign C[1] = G[0] | (P[0] & C[0]);
    assign C[2] = G[1] | (P[1] & G[0]) | (P[1] & P[0] & C[0]);
    assign C[3] = G[2] | (P[2] & G[1]) | (P[2] & P[1] & G[0]) | (P[2] & P[1] & P[0] & C[0]);
    
    // 4位组间进位(关键优化点)
    wire C4 = G[3] | (P[3] & G[2]) | (P[3] & P[2] & G[1]) | (P[3] & P[2] & P[1] & G[0]) | 
              (P[3] & P[2] & P[1] & P[0] & C[0]);
    
    assign C[4] = C4;
    assign C[5] = G[4] | (P[4] & C4);
    assign C[6] = G[5] | (P[5] & G[4]) | (P[5] & P[4] & C4);
    assign C[7] = G[6] | (P[6] & G[5]) | (P[6] & P[5] & G[4]) | (P[6] & P[5] & P[4] & C4);

    // 最终进位和结果
    assign co = suben ^ (G[7] | (P[7] & G[6]) | (P[7] & P[6] & G[5]) | 
                (P[7] & P[6] & P[5] & G[4]) | (P[7] & P[6] & P[5] & P[4] & C4));
    assign sum = P ^ C;
endmodule

ALU信号指示图:
ALU

ALU总体顶部模块实现

module ALU(databus,inbus,clk,alua_in,alub_in,sel,cf);
  (* keep, preserve *)output reg [7:0] databus;       // 统一数据总线
    input [7:0] inbus;
  input clk;
  input alua_in;
  input alub_in;
  input[2:0] sel;
  output cf;
  
  wire[7:0] op1;
  wire[7:0] op2;
  wire [7:0] result;

  
  OPReg A(clk,alua_in,inbus,op1);
    OPReg B(clk, alub_in,inbus,op2);
  ALUCore u1(op1,op2,sel,result,cf);
  // OutputBufReg R(clk,result,alu_out,databus);
    always @(*) begin

      databus<=result;
  end

endmodule

输入端口包含如下:
inbus:用于接受数据总线上面的数据,与 alua_in、alub_in、sel 信号配合使用
alua_in:用于接受数据总线上面的操作数 a 的数据(四位操作数)
alub_in:用于接受数据总线上面的操作数 b 的数据(四位操作数)
sel:用于接受数据总线上面的操作符 sel 的数据(3为操作码)
cf:进位标识符,若想扩展进位信号,只需要新建一个寄存器将输入与 cf 连接即可

输出端口包含如下:
op1:输入缓冲寄存器(OPReg A)接受数据后将数据输出给计算核心(ALUCore)的输出线路
op2:输入缓冲寄存器(OPReg B)接受数据后将数据输出给计算核心(ALUCore)的输出线路
result:计算核心(ALUCore)计算结果后的输出线路

ALU输入缓冲寄存器

module OPReg(clk,we,din,dout);
  input clk;
  input we;
  input[7:0] din;
  output[7:0] dout;
  reg[7:0] dout;
  
  always @(posedge clk)
  begin
    if(we==1'b1)
    begin
      dout <= din;
    end
  end
endmodule

当 we 使能信号为有效时,将总线上面的数据锁存起来,结构较为简单,不再赘述

加法器实现

module Adder(
    input [7:0]  op1,     // 操作数1
    input [7:0]  op2,     // 操作数2
    input        suben,   // 减法使能(1=减法)
    output [7:0] sum,     // 和/差结果
    output       co       // 进位/借位
);
    // 减法转换为补码加法
    wire [7:0] adj_op2 = op2 ^ {8{suben}};
    wire cin = suben;     // 减法时cin=1(补码+1)

    // 生成位(Generate)和传播位(Propagate)
    wire [7:0] G = op1 & adj_op2;
    wire [7:0] P = op1 ^ adj_op2;

    // 超前进位计算(分组优化)
    wire [7:0] C;
    assign C[0] = cin;
    assign C[1] = G[0] | (P[0] & C[0]);
    assign C[2] = G[1] | (P[1] & G[0]) | (P[1] & P[0] & C[0]);
    assign C[3] = G[2] | (P[2] & G[1]) | (P[2] & P[1] & G[0]) | (P[2] & P[1] & P[0] & C[0]);
    
    // 4位组间进位(关键优化点)
    wire C4 = G[3] | (P[3] & G[2]) | (P[3] & P[2] & G[1]) | (P[3] & P[2] & P[1] & G[0]) | 
              (P[3] & P[2] & P[1] & P[0] & C[0]);
    
    assign C[4] = C4;
    assign C[5] = G[4] | (P[4] & C4);
    assign C[6] = G[5] | (P[5] & G[4]) | (P[5] & P[4] & C4);
    assign C[7] = G[6] | (P[6] & G[5]) | (P[6] & P[5] & G[4]) | (P[6] & P[5] & P[4] & C4);

    // 最终进位和结果
    assign co = suben ^ (G[7] | (P[7] & G[6]) | (P[7] & P[6] & G[5]) | 
                (P[7] & P[6] & P[5] & G[4]) | (P[7] & P[6] & P[5] & P[4] & C4));
    assign sum = P ^ C;
endmodule

输入输出如注释所示,下面介绍一下该加法器的加减二路选择功能以及串并联混用逻辑:

超前进位计算技术介绍:

若我们在加法计算中每一位都通过上一位的数据以及是否需要进位来判断下一位是否需要进位,那么我们所设计的电路就是8路串行,串行效率是不如并行的,首先串行代码应当如下:

assign C[0] = cin;
assign C[1] = G[0] | (P[0] & cin);       // 第0位进位
assign C[2] = G[1] | (P[1] & C[0]);      // 第1位进位
assign C[3] = G[2] | (P[2] & C[1]);      // 第2位进位
assign C[4] = G[3] | (P[3] & C[2]);      // 第3位进位
assign C[5] = G[4] | (P[4] & C[3]);      // 第4位进位
assign C[6] = G[5] | (P[5] & C[4]);      // 第5位进位
assign C[7] = G[6] | (P[6] & C[5]);      // 第6位进位
assign co = G[7] | (P[7] & C[6]);      // 第7位进位

(注意):C[i]表示的是第i位是否需要进位

显然,我们是可以将 C[i] 的内容直接套到 C[i + 1] 里面去的,这样就不需要在知道 C[i] 后才能知道,当然,也不可能说八个电路全都用并联法去连接,咱的逻辑单元本来就不富裕,要是
到后期发现逻辑单元不够用了将其改成8路串联甚至是一种优化

因此我们将电路改为4路并联2路串联,就变成了一下这种形式:

wire [7:0] C;
    assign C[0] = cin;
    assign C[1] = G[0] | (P[0] & C[0]);
    assign C[2] = G[1] | (P[1] & G[0]) | (P[1] & P[0] & C[0]);
    assign C[3] = G[2] | (P[2] & G[1]) | (P[2] & P[1] & G[0]) | (P[2] & P[1] & P[0] & C[0]);
    
    // 4位组间进位(关键优化点)
    wire C4 = G[3] | (P[3] & G[2]) | (P[3] & P[2] & G[1]) | (P[3] & P[2] & P[1] & G[0]) | 
              (P[3] & P[2] & P[1] & P[0] & C[0]);
    
    assign C[4] = C4;
    assign C[5] = G[4] | (P[4] & C4);
    assign C[6] = G[5] | (P[5] & G[4]) | (P[5] & P[4] & C4);
    assign C[7] = G[6] | (P[6] & G[5]) | (P[6] & P[5] & G[4]) | (P[6] & P[5] & P[4] & C4);

    // 最终进位和结果
    assign co = suben ^ (G[7] | (P[7] & G[6]) | (P[7] & P[6] & G[5]) | 
                (P[7] & P[6] & P[5] & G[4]) | (P[7] & P[6] & P[5] & P[4] & C4));

至于这个加法器是如何兼顾减法操作的,若不清楚建议跳转至这篇文章,查看最下面的
部分,有详细解答:自动售货机

算数核心实现

module ALUCore(
    input [7:0]  op1,
    input [7:0]  op2,
    input [2:0]  sel,    // 扩展为3位操作码
    output reg [7:0] result,
    output reg       cf
);
    
    // 加法器接口
    reg [7:0] adder_op1;
    reg [7:0] adder_op2;
    reg suben;
    wire [7:0] adder_sum;
    wire adder_cout;
    
    Adder u1(adder_op2, adder_op1, suben, adder_sum, adder_cout);
    
    always @(*) begin
        case(sel)
            // 算术运算
            3'b000, 3'b001: begin  // 加法/减法
                // 汇编代码其实是左源右目标,所以反过来放
                adder_op1 = op1;
                adder_op2 = op2;
                suben = sel[0];
                result = adder_sum;
                cf = adder_cout;
            end
            3'b010: begin  // 左移
                adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0; // 左移不需要减法
                cf = 1'b0; // 左移不产生进位
                // if(adder_op1[7] | adder_op1[6] | adder_op1[5] | adder_op1[4] | adder_op1[3]) begin
                //     result = 8'b0; // 左移后全零
                // end else begin
                result = op2 << op1;
                // end
            end
            3'b011: begin  // 右移
                adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0; // 左移不需要减法
                cf = 1'b0; // 左移不产生进位
                // if(adder_op1[7] | adder_op1[6] | adder_op1[5] | adder_op1[4] | adder_op1[3]) begin
                //     result = 8'b0; // 右移后全零
                // end else begin
                result = op2 >> op1;
                // end
            end
            // 逻辑运算
            3'b100: begin  // 与(AND)
        adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0;
                result = op1 & op2;
                cf = 1'b0;  // 逻辑运算清零CF
            end
            3'b101: begin  // 或(OR)
        adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0;
                result = op1 | op2;
                cf = 1'b0;
            end
            3'b110: begin  // 非(NOT)
                adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0;
                result = op2 ^ 8'hFF; // 取反
                cf = 1'b0;
            end
            3'b111: begin  // 异或(XOR)
        adder_op1 = 8'b0;
                adder_op2 = 8'b0;
                suben = 1'b0;
                result = op1 ^ op2;
                cf = 1'b0;
            end
        endcase
    end
endmodule

功能表如下:

操作码 (sel) 运算类型 功能描述 操作数处理 (op1, op2) 结果 (result) 进位标志 (cf) 备注
3'b000 算术运算 加法 (op1 + op2) adder_op1 = op1
adder_op2 = op2
adder_sum adder_cout 直接调用加法器
3'b001 算术运算 减法 (op1 - op2) adder_op1 = op1
adder_op2 = op2
suben = 1
adder_sum suben ^ adder_cout 补码减法,借位取反
3'b010 移位运算 左移 (op2 << op1) 忽略加法器输入 op2 << op1 1'b0 移位位数由 op1 指定
3'b011 移位运算 右移 (op2 >> op1) 忽略加法器输入 op2 >> op1 1'b0 逻辑右移
3'b100 逻辑运算 与 (op1 & op2) 忽略加法器输入 op1 & op2 1'b0 按位与
3'b101 逻辑运算 或 (op1 | op2) 忽略加法器输入 op1 | op2 1'b0 按位或
3'b110 逻辑运算 非 (~op1) 忽略加法器输入 ~op1 1'b0 按位与非
3'b111 逻辑运算 异或 (op1 ^ op2) 忽略加法器输入 op1 ^ op2 1'b0 按位异或

加法和减法运算使用的是加法器实现的,因此,也只有这两个命令是需要传递 adder_op1 和 adder_op2 以及 cf 进位标识的,其他情况直接使用 传进来的 op 计算即可,但需要
注意的是,其他逻辑运算虽然不需要操控 cf、adder_op1 以及 adder_op2 了,但仍然需要给他们赋值,不然模块会将这些无法实时确定的单元设置为寄存器单元,会导致组合电路
时序化

三、寄存器组模块设计

模块设计

module reg_file(
    input         clk,
    // 双地址总线接口
    input  [2:0]  t_addrbus,    // 目标地址总线
  
    (* keep, preserve *)output reg [7:0]  databus0, // 双向数据总线(dMDR与寄存器组之间交互线)
    (* keep, preserve *)output reg [7:0]  databus1, // 双向数据总线(dMDR与寄存器组之间交互线)
    (* keep, preserve *)output reg [7:0]  databus2, // 双向数据总线(dMDR与寄存器组之间交互线)
    (* keep, preserve *)output reg [7:0]  databus3, // 双向数据总线(dMDR与寄存器组之间交互线)
    input [7:0]  inbus // 输入数据总线(来自ALU或其他模块)
);
  
    //------------------------------------------
    // 同步写逻辑
    //------------------------------------------
    always @(negedge clk) begin
        // 目标寄存器写入(地址有效时)
        if (t_addrbus[2] == 1'b0) begin  // 最高位=0时有效
            case (t_addrbus[1:0]) // 低2位选择寄存器
                2'd0: databus0 <= inbus; // R0
                2'd1: databus1 <= inbus; // R1
                2'd2: databus2 <= inbus; // R2
                2'd3: databus3 <= inbus; // R3
            endcase
        end
    end
endmodule

工作原理

寄存器组模块包含4个8位通用寄存器(R0-R3),采用多总线架构设计:

  1. 输入接口

    • inbus:8位输入数据总线,接收来自ALU或其他模块的数据
    • t_addrbus:3位目标地址总线,用于选择要写入的寄存器
  2. 输出接口

    • databus0-3:4个8位双向数据总线,分别对应R0-R3寄存器的输出
  3. 核心逻辑

    • 寄存器写入操作在时钟下降沿(negedge clk)触发
    • 地址解码规则:
      • t_addrbus[2]为地址有效标志(0=有效)
      • t_addrbus[1:0]选择具体寄存器:
        • 2'b00:R0
        • 2'b01:R1
        • 2'b10:R2
        • 2'b11:R3
  4. 数据流向

    • 写入时:inbus数据在时钟下降沿写入选定寄存器
    • 读取时:寄存器值持续输出到对应的databus

四、指令存储器模块设计

模块设计

module inst_mem(
    input         clk,
    input  [7:0]  pc_addr,       // 程序计数器地址输入
    output  reg [7:0]  databus       // 数据总线输出
);
    // 存储器配置 - 明确为ROM
    (* preserve *)(* romstyle = "M9K" *) reg [7:0] memory [0:255];

  initial begin
    $readmemb("program.hex", memory); // 覆盖初始化
  end
  
    // 同步读取
    always @(posedge clk) begin
        databus <= memory[pc_addr]; // 输出到总线
    end
endmodule

工作原理

指令存储器模块是一个256×8位的只读存储器(ROM),用于存储程序指令:

  1. 输入接口

    • pc_addr:8位地址输入,来自程序计数器(PC)
    • clk:系统时钟信号
  2. 输出接口

    • databus:8位数据输出总线,输出当前地址对应的指令
  3. 核心逻辑

    • 存储器在时钟上升沿(posedge clk)同步读取数据
    • 根据pc_addr的值从ROM中取出对应地址的指令
    • 取出的指令通过databus输出
  4. 初始化方式

    • 使用$readmemb系统任务从”program.hex”文件加载程序
    • 存储器被综合为FPGA的M9K存储块

五、主存模块

整体架构

主存系统采用分层设计,由外层包装模块(DataMemoryWrap)和内层存储核心(DataMemory)组成,实现了一个16×8位的RAM存储器系统,具有双地址总线接口和灵活的数据通路控制。

外层包装模块(DataMemoryWrap)

module DataMemoryWrap(
    input         clk,
    // 双地址总线接口
    input  [2:0]  s_addrbus,    // 源地址总线
    input  [2:0]  t_addrbus,    // 目标地址总线
    (* keep, preserve *) output reg  [7:0]  databus,      // 双向数据总线
    input [7:0] inbus, // 输入数据总线
    // 控制信号
    input       dmdr_iin     // dMDR输入使能
);
    // [内部信号和DataMemory实例化]
endmodule
功能特点
  1. 双地址总线接口

    • s_addrbus:源地址总线(3位)
    • t_addrbus:目标地址总线(3位)
    • 地址3'b100专用于内存访问
  2. 数据通路控制

    • databus:双向数据总线(8位),作为内存数据寄存器(MDR)
    • inbus:输入数据总线(8位),来自ALU或其他模块
  3. 控制信号

    • dmdr_iin:MDR输入使能信号,控制数据加载
工作原理
  1. 内存访问识别

    wire mem_as_target = (t_addrbus == 3'b100); // 内存作为写入目标
    wire mem_as_source = (s_addrbus == 3'b100); // 内存作为读取源
  2. 内存接口连接

    assign mem_addr = inbus; // 地址来自输入总线
    assign mem_we = mem_as_target; // 写使能信号
  3. 数据流控制

    always @(negedge clk) begin
        if (dmdr_iin)
            databus <= inbus; // 从总线加载到MDR
        else if (mem_as_source && ~mem_we)
            databus <= mem_dout; // 从内存读取到MDR
    end

内层存储核心(DataMemory)

module DataMemory(
    input         clk,
    input  [7:0]  addr,
    input  [7:0]  din,
    output [7:0]  dout,
    input         we
);
    (* ramstyle = "no_rw_check, logic" *)
    (* keep, preserve *)reg [7:0] memory [0:15];
    // [初始化与读写逻辑]
endmodule
存储结构
  1. 存储容量

    • 16×8位RAM存储器
    • 使用FPGA逻辑单元实现(ramstyle属性指定)
  2. 初始化方式

    initial begin
        $readmemb("data.hex", memory); // 从文件加载初始数据
    end
读写操作
  1. 写入时序

    always @(posedge clk) begin
        if (we) begin
            memory[addr[3:0]] <= din; // 同步写入
        end
    end
  2. 读取特性

    assign dout = memory[addr[3:0]]; // 异步读取

系统协作流程

  1. 内存写入操作

    • CU设置t_addrbus=3'b100标识内存为目标
    • 地址通过inbus送入mem_addr
    • 数据通过databus送入内存
    • 时钟上升沿完成写入
  2. 内存读取操作

    • CU设置s_addrbus=3'b100标识内存为源
    • 地址通过inbus送入mem_addr
    • 数据在下一个时钟下降沿锁存到databus
  3. MDR加载操作

    • dmdr_iin有效时,inbus数据在时钟下降沿加载到databus

六、指令寄存器模块

模块设计

module IR (
    // 系统信号
    input         clk,
  
    // 双向数据总线接口
    (* keep, preserve *) output reg  [7:0]  databus,
    input  [7:0]  inbus,        // 输入数据总线(来自ALU或其他模块)
  
    // 控制信号
    input         ir_in,        // 从总线加载指令(上升沿有效)
  
    // 译码输出
    (* keep *)output [3:0]  opcode,       // 高4位操作码 -> CU(存在3位操作码)
    (* keep *)output [3:0]  operand       // 低4位操作数 -> CU(存在5位操作数为立即数)
);

工作原理

指令寄存器(IR)是CPU中用于暂存当前执行指令的关键组件,具有指令锁存和译码输出功能:

  1. 输入接口

    • inbus:8位输入数据总线,接收来自指令存储器的指令
    • ir_in:控制信号,上升沿有效时加载新指令
  2. 输出接口

    • databus:8位双向数据总线,可输出当前指令
    • opcode:4位操作码输出(指令高4位)
    • operand:4位操作数输出(指令低4位)
  3. 核心逻辑

    • ir_in信号的上升沿将inbus上的指令锁存到内部寄存器
    • 锁存的指令持续输出到opcodeoperand供控制单元(CU)译码
    • 当前指令可通过databus输出到数据总线

关键特性

  1. 指令锁存:严格在控制信号上升沿锁存指令,确保指令稳定性

  2. 实时译码:持续输出操作码和操作数字段,减少控制单元译码延迟

  3. 双向接口:既可从总线加载指令,也可将当前指令输出到总线

  4. 位宽优化:支持4位操作码和4位操作数输出,满足基本指令集需求

典型工作流程

  1. 取指阶段

    • 指令存储器将指令送至inbus
    • CU发出ir_in脉冲信号
    • ir_in上升沿,指令被锁存到IR
  2. 译码阶段

    • 锁存的指令自动分离为opcodeoperand
    • 控制单元根据opcode生成控制信号
    • operand直接用于执行单元或地址计算
  3. 指令输出

    • 当需要重新发送当前指令时,IR通过databus输出

信号时序说明

信号 触发条件 功能描述
ir_in 上升沿 锁存新指令到IR
opcode 持续输出 当前指令的高4位操作码
operand 持续输出 当前指令的低4位操作数
databus 由外部控制 输出当前指令内容

设计特点

  1. 同步加载:所有指令加载操作严格同步于控制信号上升沿

  2. 低延迟译码:操作码和操作数字段持续输出,无需额外时钟周期

  3. 灵活接口:支持指令回读功能,便于实现复杂指令流程

  4. 资源优化:仅实现必要功能,保持硬件实现简洁高效

七、CU模块

这个模块应该是整个 CPU 中最重要的模块了,需要根据指令表来译码,其中指令表如下:

编号 编码(二进制) 汇编指令 操作 备注
0 0000xxyy mov rx, ry ry <= rx
1 0001xxyy mov rx, (ry) (ry) <= rx
2 001iiiii mov i r0 <= i 隐含目的操作数r0,i的高3位补0
3 010iiiii mov (i) r0 <= (i) 隐含目的操作数r0,i的高3位补0
4 0110xxyy mov (rx), ry ry <= (rx)
5 01110000 rtn PC <= 0
6 1000xxyy and rx, ry ry <= rx & ry
7 1001xxyy or rx, ry ry <= rx | ry
8 101000xx not rx rx <= ~rx
9 1011xxyy xor rx, ry ry <= rx ^ ry
13 1100xxyy shl rx, ry ry <= ry << rx
11 1101xxyy add rx, ry ry <= ry + rx
12 1110xxyy shr rx, ry ry <= ry >> rx
12 1111xxyy sub rx, ry ry <= ry - rx

模块实现如下:

module CU (
    // 时钟与复位
    input         clk,
    input         rst,          // 全局复位信号
    
    // 指令接口
    input  [3:0]  opcode,
    input  [3:0]  operand,

    // 程序地址总线控制
    output reg      pc_reset,       // PC清零
    output reg        pc_out,         // PC地址输出使能
    output reg        pc_inc,         // PC自增
    
    // 指令寄存器总线控制
    output reg        iMDRout,        // iMDR写入总线

    // 寄存器组总线控制
    // ALU总线控制
    output reg        ALUAin,         // ALU数据A输入
    output reg        ALUBin,         // ALU数据B输入
    output reg        ALUout,         // ALU数据输出
    output reg [2:0]  ALUop,          // ALU操作选择

    // 数据存储器总线控制
    output reg        dmdr_iin,       // dMDR和总线输入使能
    output reg        dmdr_iout,      // dMDR和总线输出使能(数据总线输出)

    output reg        ir_in,          // 从总线加载指令(上升沿有效)
    output reg        imm_out,        // 立即数输出(低五位)

    output reg [2:0]  s_addrbus,      // 源地址选择
    output reg [2:0]  t_addrbus       // 目标地址选择

);
    reg [2:0] cur_sta;
    reg fetch1_wait; // 等待信号
    parameter 
        FETCH1   = 3'b000,
        FETCH2   = 3'b001,
        EXEC1    = 3'b010,
        EXEC2    = 3'b011,
        EXEC3    = 3'b100,
        EXEC4    = 3'b101;

    // reg double_clk;

    // always @(posedge clk) begin
    //     double_clk <= ~double_clk;
    // end
    
    always @(negedge clk) begin
        case (cur_sta)
            // 取指阶段
            FETCH1: begin
                pc_out <= 1;            // PC输出至数据总线
                s_addrbus <= 3'b110;    // 指令存储器读取指令
                t_addrbus <= 3'b111;    // 无目标地址
                if (fetch1_wait) begin
                    fetch1_wait <= 1'b0; // 取指等待信号复位
                    cur_sta <= FETCH1; // 保持在FETCH1状态
                end else begin
                    cur_sta <= FETCH2;    // 状态转移到FETCH2
                end
            end
            FETCH2: begin
                pc_out <= 0;            // PC停止总线输出
                s_addrbus <= 3'b111;    // 停止指令存储器读取指令
                iMDRout <= 1;           // 指令数据存储器输出数据至总线
                ir_in <= 1;             // 指令寄存器从总线中读取指令
                pc_inc <= 1;            // PC自增
                cur_sta <= EXEC1;    // 状态转移到EXEC1
            end
            EXEC1: begin
                pc_inc <= 0;            // PC停止自增
                iMDRout <= 0;           // 指令数据存储器停止输出数据至总线
                ir_in <= 0;             // 指令寄存器停止读取指令
                
                // Start
                case (opcode)
                    // move rx, ry step1(move)
                    4'b0000: begin
                        s_addrbus <= {1'b0, operand[3:2]};    // rx发送数据信息至数据总线
                        t_addrbus <= {1'b0, operand[1:0]};   // ry接受数据总线上的数据信息
                    end
                    // move rx, (ry) step1(间接寻址)
                    4'b0001: begin
                        s_addrbus <= {1'b0, operand[3:2]};    // rx发送数据信息至数据总线
                        dmdr_iin <= 1;                        // MDR接收总线数据
                    end
                    // move i step1(move)
                    4'b0010, 4'b0011: begin
                        imm_out <= 1;                        // IR输出立即数至数据总线
                        t_addrbus <= 3'b000;                 // R0存储立即数
                    end
                    // move (i) step1(间接寻址)
                    4'b0100, 4'b0101: begin
                        imm_out <= 1;                        // 立即数写入数据总线
                        s_addrbus <= 3'b100;                 // 主存根据立即数访存
                    end
                    // move (rx), ry step1(间接寻址)
                    4'b0110: begin
                        s_addrbus <= {1'b0, operand[3:2]};    // rx输出地址数据至数据总线
                    end
                    // rtn step1(rtn)
                    4'b0111: begin
                        pc_reset <= 1;                       // PC清零
                    end
                    // and/or/xor/add/sub/shl/shr rx ry step1(alua <= rx)
                    4'b1000, 4'b1001, 4'b1011, 4'b1101, 4'b1111, 4'b1100, 4'b1110: begin
                        s_addrbus <= {1'b0, operand[3:2]};
                        ALUAin <= 1;
                    end
                    default: cur_sta <= cur_sta;
                endcase
                cur_sta <= EXEC2;    // 状态转移到EXEC2

            end
            EXEC2: begin
                case (opcode)
                    // move rx, ry endstep
                    4'b0000: begin
                        s_addrbus <= 3'b111;
                        t_addrbus <= 3'b111;
                    end
                    // move rx, (ry) step2(move)
                    4'b0001: begin
                        dmdr_iin <= 0;                        // MDR停止接收总线数据
                        s_addrbus <= {1'b0, operand[1:0]};    // ry发送地址信息至数据总线
                        t_addrbus <= 3'b100;                 // 通过总线数据地址信息将对应内存数据存至dMDR
                    end
                    // move i endstep
                    4'b0010, 4'b0011: begin
                        imm_out <= 0;                        // IR停止输出立即数至数据总线
                        t_addrbus <= 3'b111;                 // R0停止存储立即数
                    end
                    // move (i) step2(move)
                    4'b0100, 4'b0101: begin
                        imm_out <= 0;                        // 停止立即数输出
                        dmdr_iout <= 1;                      // 主存输出数据至数据总线
                        t_addrbus <= 3'b000;                 // R0接受数据总线上的数据
                        s_addrbus <= 3'b100;                 // 停止主存地址发送数据
                    end
                    // move (rx), ry step2(move)
                    4'b0110: begin
                        dmdr_iout <= 1;                      // dMDR输出数据至数据总线
                        s_addrbus <= 3'b100; // 主存根据地址数据总线访存
                        t_addrbus <= {1'b0, operand[1:0]}; // ry接受数据总线上的数据
                    end
                    // rtn endstep
                    4'b0111: begin
                        pc_reset <= 0;                       // PC停止清零
                    end
                    // and rx ry step2(alub <= ry)
                    4'b1000: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b100;
                    end
                    // or rx ry step2(alub <= ry)
                    4'b1001: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b101;
                    end
                    // xor rx ry step2(alub <= ry)
                    4'b1011: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b111;
                    end
                    // not ry step2(alub <= ry)
                    4'b1010:  begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b110;
                    end
                    // shl rx, ry (alub <= ry)
                    4'b1100: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b010;
                    end
                    // and rx ry step2(alub <= ry)
                    4'b1101: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b000;
                    end
                    // shr rx ry(alub <= ry)
                    4'b1110: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b011;
                    end
                    // sub rx ry step2(alub <= ry)
                    4'b1111: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b001;
                    end
                    default: cur_sta <= cur_sta;
                endcase
                // 状态转移到EXEC3
                cur_sta <= EXEC3;
            end
            EXEC3: begin
                case (opcode)
                    // move rx, (ry) endstep
                    4'b0001: begin
                        s_addrbus <= 3'b111;                      // 停止源地址发送数据  
                        t_addrbus <= 3'b111;                 // 停止目标地址接受数据
                    end
                    // move (i) endstep
                    4'b0100, 4'b0101: begin
                        dmdr_iout <= 0;                          // 主存停止输出数据至数据总线
                        t_addrbus <= 3'b111;                     // R0停止接受数据总线上的数据
                        s_addrbus <= 3'b111;                     // 停止源地址发送数据
                    end
                    // move (rx), ry endstep
                    4'b0110: begin
                        dmdr_iout <= 0;                          // dMDR停止输出数据至数据总线
                        t_addrbus <= 3'b111;                     // ry停止接受数据总线上的数据
                        s_addrbus <= 3'b111;                     // 停止源地址发送数据
                    end
                    // and/or/xor/add/sub/shl/shr rx ry step3 (result <= databus)
                    4'b1000, 4'b1001, 4'b1010, 4'b1011, 4'b1101, 4'b1111, 4'b1100, 4'b1110: begin
                        //clear
                        s_addrbus <= 3'b111;
                        ALUBin <= 0;
                        //operate
                        ALUout <= 1;                         
                        t_addrbus <= {1'b0, operand[1:0]};        // 计算结果写入ry
                    end
                    default: cur_sta <= cur_sta;
                    endcase
                // 状态转移到EXEC4
                cur_sta <= EXEC4;
            end
            EXEC4: begin
            case (opcode)
            // (and/or/not/xor/add/sub/shl/shr rx ry) / add/sub i endstep
            4'b1000, 4'b1001, 4'b1010, 4'b1011, 4'b1100, 4'b1101, 4'b1110, 4'b1111, 4'b1100, 4'b1110: begin
                ALUout <= 0;                         
                t_addrbus <= 3'b111;
            end
            default: cur_sta <= cur_sta;
            endcase
            // 状态转移到FETCH1
            cur_sta <= FETCH1;
            end
        endcase
        
        if (rst) begin
            cur_sta <= FETCH1; // 复位时状态机回到FETCH1
            pc_reset <= 1'b0;
            pc_out <= 1'b0; // PC输出停止
            pc_inc <= 1'b0; // PC自增停止
            iMDRout <= 1'b0; // 指令存储器输出停止
            ALUAin <= 1'b0; // ALU A输入停止
            ALUBin <= 1'b0; // ALU B输入停止
            ALUout <= 1'b0; // ALU输出停止
            ALUop <= 3'b000; // ALU操作码清零
            dmdr_iin <= 1'b0; // 数据存储器输入停止
            dmdr_iout <= 1'b0; // 数据存储器输出停止
            ir_in <= 1'b0; // 指令寄存器输入停止
            imm_out <= 1'b0; // 立即数输出停止
            s_addrbus <= 3'b111; // 源地址总线置空
            t_addrbus <= 3'b111; // 目标地址总线置空
            fetch1_wait <= 1'b1; // 清除等待状态
        end
    end
    
endmodule

CU模块进行节拍控制,一个周期分为6个节拍,第一个节拍和第二个节拍用于取址,后面的节拍则用于指令处理

CU以输出信号为主,下面是对于每个信号的介绍,其中每一个信号在上面都介绍过,只不过在其他模块是作为输入信号来使用的,而
在CU模块中是作为输出信号来使用的,这个也是毋庸置疑的,因为正是 CU 模块来控制各个单元工作

其中 cur_sta 用于控制当前处于的节拍状态,一共有6个节拍状态,分别为 FETCH1,FETCH2,EXEC1,EXEC2,EXEC3,EXEC4

节拍状态 功能描述
FETCH1 取指阶段1:PC输出指令地址到总线
FETCH2 取指阶段2:指令存储器输出指令到总线并加载到IR
EXEC1 执行阶段1:指令译码并准备操作数
EXEC2 执行阶段2:执行指令核心操作
EXEC3 执行阶段3:处理指令结果
EXEC4 执行阶段4:清理状态准备下一条指令

每个节拍详解如下:

指令类型 FETCH1 FETCH2 EXEC1 EXEC2 EXEC3 EXEC4
move rx, ry PC输出地址
s_addrbus=110
加载指令到IR
PC自增
rx输出到总线
ry作为目标
无操作 无操作 无操作
move rx, (ry) PC输出地址
s_addrbus=110
加载指令到IR
PC自增
rx输出到总线
MDR接收数据
ry输出地址
内存作为目标
清理总线信号 无操作
move i (立即数) PC输出地址
s_addrbus=110
加载指令到IR
PC自增
立即数输出到总线
R0作为目标
无操作 无操作 无操作
move (i) (内存间接) PC输出地址
s_addrbus=110
加载指令到IR
PC自增
立即数输出到总线
内存作为源
内存数据输出到总线
R0作为目标
清理总线信号 无操作
move (rx), ry PC输出地址
s_addrbus=110
加载指令到IR
PC自增
rx输出地址到总线 内存数据输出到总线
ry作为目标
清理总线信号 无操作
rtn PC输出地址
s_addrbus=110
加载指令到IR
PC自增
PC清零 无操作 无操作 无操作
and/or/xor rx, ry PC输出地址
s_addrbus=110
加载指令到IR
PC自增
rx输出到ALU A输入 ry输出到ALU B输入
设置ALU操作码
ALU结果输出到总线
ry作为目标
清理ALU输出
not ry PC输出地址
s_addrbus=110
加载指令到IR
PC自增
无操作 ry输出到ALU B输入
设置ALU操作码
ALU结果输出到总线
ry作为目标
清理ALU输出
add/sub rx, ry PC输出地址
s_addrbus=110
加载指令到IR
PC自增
rx输出到ALU A输入 ry输出到ALU B输入
设置ALU操作码
ALU结果输出到总线
ry作为目标
清理ALU输出
shl/shr rx, ry PC输出地址
s_addrbus=110
加载指令到IR
PC自增
rx输出到ALU A输入 ry输出到ALU B输入
设置ALU操作码
ALU结果输出到总线
ry作为目标
清理ALU输出

关键控制信号变化

取指阶段(FETCH1-FETCH2)
信号 FETCH1 FETCH2
pc_out 1 0
s_addrbus 110(指令存储器) 111(无)
iMDRout 0 1
ir_in 0 1
pc_inc 0 1
通用执行阶段(EXEC1-EXEC4)
信号 EXEC1 EXEC2 EXEC3 EXEC4
ALUAin 算术指令:1 0 0 0
ALUBin 0 算术指令:1 0 0
ALUout 0 0 算术指令:1 0
dmdr_iin 内存指令:1 0 0 0
dmdr_iout 0 内存指令:1 0 0

指令执行特点

  1. 统一取指周期:所有指令共享相同的FETCH1和FETCH2阶段
  2. 内存访问优化:内存操作需要额外节拍处理地址和数据
  3. 算术指令标准化:所有算术逻辑指令采用相同的三节拍执行流程
  4. 立即数处理:立即数移动指令可在两个节拍内完成

顶层模块

最后的任务就是将所有的模块联系起来,放在顶层模块:

代码如下:

module core8(
    input clk,
    input n_rst,
    inout [7:0] databus, // 统一数据总线
    (* keep, preserve *) // 保持信号,防止优化掉
    output r00,
    output r01,
    output r02,
    output r03,
    output r04,
    output r05,
    output r06,
    output r07,
    // 串口通信接口
    (* keep, preserve *)input rx, // 串口接收
    (* keep, preserve *)output tx = 1 // 串口发送
);
    (* keep, preserve *)wire rst = ~n_rst; // 低电平复位信号
    
    (* keep, preserve *)wire alua_in;
    (* keep, preserve *)wire alub_in;
    (* keep, preserve *)wire alu_out;
    (* keep, preserve *)wire[2:0] sel;
    (* keep, preserve *)wire cf;
    (* keep, preserve *)wire zf;
    (* keep, preserve *)wire pc_inc; // 程序计数器自增
    (* keep, preserve *)wire pc_jump; // 程序计数器跳转
    (* keep, preserve *)wire pc_out; // 程序计数器输出到总线
    (* keep, preserve *)wire pc_reset; // 程序计数器复位
    (* keep, preserve *)wire[2:0] s_addrbus; // 源寄存器地址总线
    (* keep, preserve *)wire[2:0] t_addrbus; // 目标寄存器地址总线 
    (* keep, preserve *)wire iMDRout;
    (* keep, preserve *)wire dmdr_iin; // 数据存储器输入
    (* keep, preserve *)wire dmdr_iout; // 数据存储器输出、
    (* keep, preserve *)wire ir_in; // 指令寄存器输入
    (* keep, preserve *)wire imm_out; // 立即数输出
    (* keep, preserve *)wire[3:0] opcode; // 操作码
    (* keep, preserve *)wire[3:0] operand; // 操作数
    (* keep, preserve *)wire[7:0] cnt;
    
    reg clk_2 = 1; // 二分频时钟输出
    reg clk_4 = 1; // 四分频时钟输出
    
    // 各部件数据总线
    wire [7:0] alu_bus;
    wire [7:0] pc_bus;
    wire [7:0] ir_bus;
    wire [7:0] reg_bus[0:3];
    wire [7:0] imem_bus;
    wire [7:0] dmem_bus;
    (* keep, preserve *)reg [3:0] cur_send_sta = 4'b0000; // 当前发送状态寄存器

    // 四分频
    always @(posedge clk)
    begin
        clk_2 <= ~clk_2; // 反转时钟信号实现二分频
    end
    always @(posedge clk_2)
    begin
        clk_4 <= ~clk_4; // 反转时钟信号实现四分频
    end
    // 对databus多路复用
    assign databus = 
        (pc_out) ? pc_bus :          // 程序计数器输出
        (iMDRout) ? imem_bus :      // 指令存储器输出
        (dmdr_iout) ? dmem_bus :    // 数据存储器输出
        (imm_out) ? ir_bus :          // 指令寄存器输入
        (alu_out) ? alu_bus :       // ALU输出
        s_addrbus[2] == 1'b0 ? reg_bus[{s_addrbus[1:0]}] : // 源寄存器组输出
        8'bzzzz_zzzz;                    // 寄存器组输出

    // Instantiate ALU
    ALU alu_inst(
        .databus(alu_bus),
        .inbus(databus),
        .clk(clk_4),
        .alua_in(alua_in),
        .alub_in(alub_in),
        .sel(sel),
        .cf(cf),
        .zf(zf) // 零标志(ZF)
    );
    assign r00 = reg_bus[0][0];
    assign r01 = reg_bus[0][1];
    assign r02 = reg_bus[0][2];
    assign r03 = reg_bus[0][3];
    assign r04 = reg_bus[0][4];
    assign r05 = reg_bus[0][5];
    assign r06 = reg_bus[0][6];
    assign r07 = reg_bus[0][7];

    //----------------------------------
    // 子模块实例化
    //----------------------------------
    // 程序计数器
    PC u_PC (
        .clk(clk_4),
        .pc_inc(pc_inc),
        .pc_jump(pc_jump),
        .databus(pc_bus),
        .inbus(databus), // 输入数据总线(来自ALU或其他模块)
        .reset(pc_reset | rst)
    );
    // 通用寄存器
    reg_file u_reg_file (
        .clk(clk_4),
        .t_addrbus(t_addrbus),
        .databus0(reg_bus[0]),
        .databus1(reg_bus[1]),
        .databus2(reg_bus[2]),
        .databus3(reg_bus[3]),
        .inbus(databus) 
    );

    // 指令存储器
    inst_mem u_inst_mem (
        .clk(clk_4),
        .pc_addr(databus),      // 从总线获取地址
        .databus(imem_bus)      // 输出到总线
    );

    // 数据存储器
    DataMemoryWrap u_data_memory (
        .clk(clk_4),
        .s_addrbus(s_addrbus),  // 源地址总线
        .t_addrbus(t_addrbus),  // 目标地址总线
        .databus(dmem_bus),      // 数据总线
        .inbus(databus), // 输入数据总线(来自ALU或其他模块)
        .dmdr_iin(dmdr_iin)    // 输入使能
    );

    // 指令寄存器
    IR u_IR (
        .clk(clk_4),
        .databus(ir_bus),
        .inbus(databus), // 输入数据总线(来自ALU或其他模块)
        .ir_in(ir_in),
        .opcode(opcode),              // 内部连接至CU
        .operand(operand)              // 内部连接至CU
    );

        // 控制单元
    CU u_CU (
        .clk(clk_4),
        .rst(rst),         // 全局复位信号
        .opcode(opcode),   // 直接连接IR内部信号
        .operand(operand),
        .pc_reset(pc_reset),       // 共用复位
        .pc_out(pc_out),
        .pc_inc(pc_inc),
        .pc_jump(pc_jump), // PC跳转
        .iMDRout(iMDRout),
        .ALUAin(alua_in),
        .ALUBin(alub_in),
        .ALUout(alu_out),
        .ALUop(sel),
        .cf(cf),
        .zf(zf), // 零标志(ZF)
        .dmdr_iin(dmdr_iin),
        .dmdr_iout(dmdr_iout),
        .ir_in(ir_in),
        .imm_out(imm_out),
        .s_addrbus(s_addrbus),
        .t_addrbus(t_addrbus),
        .cnt(cnt)
    );
endmodule

模块架构概述

core8模块是整个8位CPU的顶层封装,负责协调所有子模块的工作,并通过统一的数据总线实现各组件间的通信。该设计采用剑桥架构,指令和数据使用不同的储存器,访存指令仅能访问数据储存器。

主要接口信号

信号类别 信号名称 位宽 方向 功能描述
系统信号 clk 1 输入 系统时钟
rst 1 输入 全局复位
数据总线 databus 8 双向 统一数据总线
调试输出 r00-r07 1 输出 R0寄存器各位状态

地址总线仲裁逻辑

源地址总线(s_addrbus)控制
wire [7:0] reg_bus[0:3]; // 寄存器组输出总线

// 寄存器组输出选择
assign databus = ... s_addrbus[2] == 1'b0 ? reg_bus[{s_addrbus[1:0]}] : ...

源地址编码表:

s_addrbus[2:0] 选择的源设备
000-011 寄存器R0-R3
100 数据存储器
110 指令存储器
其他 无源设备
目标地址总线(t_addrbus)控制
// 在reg_file模块中实现
if (t_addrbus[2] == 1'b0) begin // 目标地址有效
    case (t_addrbus[1:0])
        2'd0: databus0 <= inbus; // R0
        2'd1: databus1 <= inbus; // R1
        2'd2: databus2 <= inbus; // R2
        2'd3: databus3 <= inbus; // R3
    endcase
end

目标地址编码表:

t_addrbus[2:0] 选择的目标设备
000-011 寄存器R0-R3
100 数据存储器
其他 无目标设备

数据总线仲裁逻辑

总线多路复用器设计
assign databus = 
    (pc_out) ? pc_bus :          // 程序计数器输出
    (iMDRout) ? imem_bus :       // 指令存储器输出
    (dmdr_iout) ? dmem_bus :     // 数据存储器输出
    (imm_out) ? ir_bus :         // 指令寄存器立即数输出
    (alu_out) ? alu_bus :        // ALU输出
    s_addrbus[2] == 1'b0 ? reg_bus[{s_addrbus[1:0]}] : // 寄存器组输出
    8'bzzzz_zzzz;                // 高阻态
总线优先级顺序
  1. 程序计数器输出(pc_out)
  2. 指令存储器输出(iMDRout)
  3. 数据存储器输出(dmdr_iout)
  4. 立即数输出(imm_out)
  5. ALU输出(alu_out)
  6. 寄存器组输出(s_addrbus选择)
  7. 高阻态(无设备驱动)

关键子模块交互

取指周期数据流
  1. CU置pc_out=1,PC地址输出到databus
  2. 指令存储器读取databus上的地址
  3. CU置iMDRout=1,指令数据输出到databus
  4. CU置ir_in=1,指令锁存到IR
执行周期数据流示例(MOV R0, R1)
  1. CU设置s_addrbus=001(选择R1)
  2. R1数据自动输出到databus
  3. CU设置t_addrbus=000(目标R0)
  4. 数据在时钟下降沿写入R0

设计特点

  1. 统一总线架构:所有数据传输通过共享的8位databus完成
  2. 严格时序控制:由CU精确控制各设备的输出使能
  3. 优先级仲裁:避免总线冲突,确保关键数据优先传输
  4. 同步设计:所有状态变化发生在时钟边沿
  5. 调试支持:提供R0寄存器各位状态输出

典型工作流程

sequenceDiagram
    participant CU
    participant PC
    participant IMEM
    participant IR
    participant REG
    participant ALU
    participant DMEM
  
    CU->>PC: pc_out=1
    PC->>databus: 地址
    CU->>IMEM: iMDRout=1
    IMEM->>databus: 指令
    CU->>IR: ir_in=1
    IR->>CU: opcode/operand
    CU->>REG: s_addrbus=src
    REG->>databus: 数据
    CU->>ALU: alua_in=1
    ALU->>databus: 结果
    CU->>REG: t_addrbus=dst

该设计通过精心设计的总线仲裁机制,在有限的硬件资源下实现了高效的指令流水线执行。

最后进行综合测试:

测试数据如下:

地址 二进制指令 操作码 操作数 汇编指令 功能描述
0x00 01001010 0100 1010 mov (i) R0 <= (0x0A)
0x01 01000000 0100 0000 mov (i) R0 <= (0x00)
0x02 00000001 0000 0001 mov r0, r1 R1 <= R0
0x03 00100010 0010 0010 mov i R0 <= 0x02
0x04 11110001 1111 0001 sub r0, r1 R1 <= R1 - R0
0x05 00100011 0010 0011 mov i R0 <= 0x03
0x06 11010001 1101 0001 add r0, r1 R1 <= R1 + R0
0x07 11000001 1100 0001 shl r0, r1 R1 <= R1 << R0
0x08 11100001 1110 0001 shr r0, r1 R1 <= R1 >> R0
0x09 01001010 0100 1010 mov (i) R0 <= (0x0A)

测试结果如下(Quartus仿真):

CPU

MedelSim仿真:

CPU

两者仿真结果一样,注意到 Quartus 仿真时出现了bug:

CPU

展开数据可以看到结果应当为48,无需在意

附录
注意,因为第四问需要使用到移位指令以及跳转指令,CU 模块中对部分指令进行了修改,详细修改如下:

指令表:

编号 编码(二进制) 汇编指令 操作 备注
0 0000xxyy mov rx, ry ry <= rx
1 0001xxyy mov rx, (ry) (ry) <= rx
2 001iiiii mov i r0 <= i 隐含目的操作数r0,i的高3位补0
3 010000yy jmp ry PC <= ry 无条件跳转
3 010001yy jc ry PC <= ry 进位时跳转
3 010010yy jz ry PC <= ry 结果为0时跳转
4 0110xxyy mov (rx), ry ry <= (rx)
5 01110000 rtn PC <= 0
6 1000xxyy and rx, ry ry <= rx & ry
7 1001xxyy or rx, ry ry <= rx | ry
8 101000xx not rx rx <= ~rx
9 1011xxyy xor rx, ry ry <= rx ^ ry
13 1100xxyy shl rx, ry ry <= ry << rx
11 1101xxyy add rx, ry ry <= ry + rx
12 1110xxyy shr rx, ry ry <= ry >> rx
12 1111xxyy sub rx, ry ry <= ry - rx

CU 部分代码修改后如下:

module CU (
    // 时钟与复位
    input         clk,
    input         rst,          // 全局复位信号
    
    // 指令接口
    input  [3:0]  opcode,
    input  [3:0]  operand,

    // 程序地址总线控制
    output reg      pc_reset,       // PC清零
    output reg        pc_out,         // PC地址输出使能
    output reg        pc_inc,         // PC自增
    output reg        pc_jump,        // PC跳转
    
    // 指令寄存器总线控制
    output reg        iMDRout,        // iMDR写入总线

    // 寄存器组总线控制
    // ALU总线控制
    output reg        ALUAin,         // ALU数据A输入
    output reg        ALUBin,         // ALU数据B输入
    output reg        ALUout,         // ALU数据输出
    output reg [2:0]  ALUop,          // ALU操作选择
    input             cf,             // 进位标志(CF)
    input             zf,             // 零标志(ZF)

    // 数据存储器总线控制
    output reg        dmdr_iin,       // dMDR和总线输入使能
    output reg        dmdr_iout,      // dMDR和总线输出使能(数据总线输出)

    output reg        ir_in,          // 从总线加载指令(上升沿有效)
    output reg        imm_out,        // 立即数输出(低五位)

    output reg [2:0]  s_addrbus,      // 源地址选择
    output reg [2:0]  t_addrbus,      // 目标地址选择
    output reg [7:0]  cnt,
    output     [7:0]  next_cnt    

);
    reg [2:0] cur_sta;
    reg fetch1_wait; // 等待信号
    parameter 
        FETCH1   = 3'b000,
        FETCH2   = 3'b001,
        EXEC1    = 3'b010,
        EXEC2    = 3'b011,
        EXEC3    = 3'b100,
        EXEC4    = 3'b101;

    Incrementer cnt_inc(
        .op(cnt),
        .next_op(next_cnt)
    );
    always @(negedge clk) begin
        case (cur_sta)
            // 取指阶段
            FETCH1: begin
                pc_out <= 1;            // PC输出至数据总线
                s_addrbus <= 3'b110;    // 指令存储器读取指令
                t_addrbus <= 3'b111;    // 无目标地址
                if (fetch1_wait) begin
                    fetch1_wait <= 1'b0; // 取指等待信号复位
                    cur_sta <= FETCH1; // 保持在FETCH1状态
                end else begin
                    cur_sta <= FETCH2;    // 状态转移到FETCH2
                end
            end
            FETCH2: begin
                pc_out <= 0;            // PC停止总线输出
                s_addrbus <= 3'b111;    // 停止指令存储器读取指令
                iMDRout <= 1;           // 指令数据存储器输出数据至总线
                ir_in <= 1;             // 指令寄存器从总线中读取指令
                pc_inc <= 1;            // PC自增
                cur_sta <= EXEC1;    // 状态转移到EXEC1
            end
            EXEC1: begin
                pc_inc <= 0;            // PC停止自增
                iMDRout <= 0;           // 指令数据存储器停止输出数据至总线
                ir_in <= 0;             // 指令寄存器停止读取指令
                
                // Start
                case (opcode)
                    // move rx, ry step1(move)
                    4'b0000: begin
                        s_addrbus <= {1'b0, operand[3:2]};    // rx发送数据信息至数据总线
                        t_addrbus <= {1'b0, operand[1:0]};   // ry接受数据总线上的数据信息
                    end
                    // move rx, (ry) step1(间接寻址)
                    4'b0001: begin
                        s_addrbus <= {1'b0, operand[3:2]};    // rx发送数据信息至数据总线
                        dmdr_iin <= 1;                        // MDR接收总线数据
                    end
                    // move i step1(move)
                    4'b0010, 4'b0011: begin
                        imm_out <= 1;                        // IR输出立即数至数据总线
                        t_addrbus <= 3'b000;                 // R0存储立即数
                    end
                    // jump
                    4'b0100: begin
                        case (operand[3:2])
                            // ry
                            2'b00: begin
                                pc_jump <= 1;               // PC跳转
                                s_addrbus <= {1'b0, operand[1:0]}; // 直接跳转到地址
                            end
                            // jc ry
                            2'b01: begin
                                if (cf) begin
                                    pc_jump <= 1;           // PC跳转
                                    s_addrbus <= {1'b0, operand[1:0]}; // 直接跳转到地址
                                end else begin
                                    pc_jump <= 0;           // PC不跳转
                                end
                            end
                            // jz ry
                            2'b10: begin
                                if (zf) begin
                                    pc_jump <= 1;           // PC跳转
                                    s_addrbus <= {1'b0, operand[1:0]}; // 直接跳转到地址
                                end else begin
                                    pc_jump <= 0;           // PC不跳转
                                end
                            end
                        endcase
                    end
                    // move (rx), ry step1(间接寻址)
                    4'b0110: begin
                        s_addrbus <= {1'b0, operand[3:2]};    // rx输出地址数据至数据总线
                    end
                    // rtn step1(rtn)
                    4'b0111: begin
                        pc_reset <= 1;                       // PC清零
                    end
                    // and/or/xor/add/sub/shl/shr rx ry step1(alua <= rx)
                    4'b1000, 4'b1001, 4'b1011, 4'b1101, 4'b1111, 4'b1100, 4'b1110: begin
                        s_addrbus <= {1'b0, operand[3:2]};
                        ALUAin <= 1;
                    end
                    default: cur_sta <= cur_sta;
                endcase
                cur_sta <= EXEC2;    // 状态转移到EXEC2

            end
            EXEC2: begin
                case (opcode)
                    // move rx, ry endstep
                    4'b0000: begin
                        s_addrbus <= 3'b111;
                        t_addrbus <= 3'b111;
                    end
                    // move rx, (ry) step2(move)
                    4'b0001: begin
                        dmdr_iin <= 0;                        // MDR停止接收总线数据
                        s_addrbus <= {1'b0, operand[1:0]};    // ry发送地址信息至数据总线
                        t_addrbus <= 3'b100;                 // 通过总线数据地址信息将对应内存数据存至dMDR
                    end
                    // move i endstep
                    4'b0010, 4'b0011: begin
                        imm_out <= 0;                        // IR停止输出立即数至数据总线
                        t_addrbus <= 3'b111;                 // R0停止存储立即数
                    end
                    // jump step2(jump)
                    4'b0100: begin
                        pc_jump <= 0;                       // PC停止跳转
                        s_addrbus <= 3'b111;                // 停止源地址发送数据
                        t_addrbus <= 3'b111;                // 停止目标地址接受数据
                    end
                    // move (rx), ry step2(move)
                    4'b0110: begin
                        dmdr_iout <= 1;                      // dMDR输出数据至数据总线
                        s_addrbus <= 3'b100; // 主存根据地址数据总线访存
                    end
                    // rtn endstep
                    4'b0111: begin
                        pc_reset <= 0;                       // PC停止清零
                    end
                    // and rx ry step2(alub <= ry)
                    4'b1000: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b100;
                    end
                    // or rx ry step2(alub <= ry)
                    4'b1001: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b101;
                    end
                    // xor rx ry step2(alub <= ry)
                    4'b1011: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b111;
                    end
                    // not ry step2(alub <= ry)
                    4'b1010:  begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b110;
                    end
                    // shl rx, ry (alub <= ry)
                    4'b1100: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b010;
                    end
                    // and rx ry step2(alub <= ry)
                    4'b1101: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b000;
                    end
                    // shr rx ry(alub <= ry)
                    4'b1110: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b011;
                    end
                    // sub rx ry step2(alub <= ry)
                    4'b1111: begin
                        s_addrbus <= {1'b0, operand[1:0]};
                        ALUAin <= 0;
                        ALUBin <= 1;
                        ALUop  <= 3'b001;
                    end
                    default: cur_sta <= cur_sta;
                endcase
                // 状态转移到EXEC3
                cur_sta <= EXEC3;
            end
            EXEC3: begin
                case (opcode)
                    // move rx, (ry) endstep
                    4'b0001: begin
                        s_addrbus <= 3'b111;                      // 停止源地址发送数据  
                        t_addrbus <= 3'b111;                 // 停止目标地址接受数据
                    end
                    // move (i) endstep
                    4'b0100, 4'b0101: begin
                        dmdr_iout <= 0;                          // 主存停止输出数据至数据总线
                        t_addrbus <= 3'b111;                     // R0停止接受数据总线上的数据
                        s_addrbus <= 3'b111;                     // 停止源地址发送数据
                    end
                    // move (rx), ry endstep
                    4'b0110: begin
                        dmdr_iout <= 0;                          // dMDR停止输出数据至数据总线
                        t_addrbus <= 3'b111;                     // ry停止接受数据总线上的数据
                        s_addrbus <= 3'b111;                     // 停止源地址发送数据
                    end
                    // and/or/xor/add/sub/shl/shr rx ry step3 (result <= databus)
                    4'b1000, 4'b1001, 4'b1010, 4'b1011, 4'b1101, 4'b1111, 4'b1100, 4'b1110: begin
                        //clear
                        s_addrbus <= 3'b111;
                        ALUBin <= 0;
                        //operate
                        ALUout <= 1;                         
                        t_addrbus <= {1'b0, operand[1:0]};        // 计算结果写入ry
                    end
                    default: cur_sta <= cur_sta;
                    endcase
                // 状态转移到EXEC4
                cur_sta <= EXEC4;
            end
            EXEC4: begin
            case (opcode)
            // (and/or/not/xor/add/sub/shl/shr rx ry) / add/sub i endstep
            4'b1000, 4'b1001, 4'b1010, 4'b1011, 4'b1100, 4'b1101, 4'b1110, 4'b1111, 4'b1100, 4'b1110: begin
                ALUout <= 0;                         
                t_addrbus <= 3'b111;
            end
            default: cur_sta <= cur_sta;
            endcase
            // 状态转移到FETCH1
            cur_sta <= FETCH1;
            if(cnt[3] & cnt[4] & cnt[6] & cnt[7]) cnt <= 0;
            else cnt <= next_cnt;
            end
        endcase
        
        if (rst) begin
            cur_sta <= FETCH1; // 复位时状态机回到FETCH1
            pc_reset <= 1'b0;
            pc_out <= 1'b0; // PC输出停止
            pc_inc <= 1'b0; // PC自增停止
            iMDRout <= 1'b0; // 指令存储器输出停止
            ALUAin <= 1'b0; // ALU A输入停止
            ALUBin <= 1'b0; // ALU B输入停止
            ALUout <= 1'b0; // ALU输出停止
            ALUop <= 3'b000; // ALU操作码清零
            dmdr_iin <= 1'b0; // 数据存储器输入停止
            dmdr_iout <= 1'b0; // 数据存储器输出停止
            ir_in <= 1'b0; // 指令寄存器输入停止
            imm_out <= 1'b0; // 立即数输出停止
            s_addrbus <= 3'b111; // 源地址总线置空
            t_addrbus <= 3'b111; // 目标地址总线置空
            fetch1_wait <= 1'b1; // 清除等待状态
            cnt <= 0;
        end
    end
    
endmodule

经测试,需要80ns的时钟周期才能解决延迟导致的数据异常问题,因此顶层模块添加了一个四分频(默认时钟周期为20ns)

// 四分频
    always @(posedge clk)
    begin
        clk_2 <= ~clk_2; // 反转时钟信号实现二分频
    end
    always @(posedge clk_2)
    begin
        clk_4 <= ~clk_4; // 反转时钟信号实现四分频
    end

将这部分代码加入到顶层模块,然后将各个部件的时钟信号传入换成clk_4即可