以内存为中心的CPU模型

参考资料:Operating Systems Foundations with Linux on the Raspberry Pi

CPU模型

  • ARM CPU可知,我们可以对CPU进行仿真
    • 计算和信息可知,计算对应于CPU、处理器,信息对应于内存、存储器,并且计算的本质是利用物理规律,进行信息处理
    • 因此,我们可以将内存、存储器作为中心,同时将CPU、处理器作为改变内存、存储器状态的计算单元
  • 我们有如下的存储层次结构。在这里,我们只考虑寄存器、内存
    • 寄存器(Register)
    • 缓存(Cache)
    • 内存(Memory)
    • 外存(Storage)

指令集架构和微架构

  • 从数字电路到CPU中,我们对ARM CPU的部分指令进行硬实现。在这里,我们对ARM CPU的部分指令进行软实现
  • 硬件/软件接口(指令集架构)
    • 操作码(Opcode)
      • opcode和ARM指令集一致
      • 为了方便起见,指令格式和ARM指令集不一致
        • 第0~7位 –> opcode
        • 第8~15位 –> argument1
        • 第16~23位 –> argument2
        • 第24~31位 –> argument3
    • 寄存器文件(Register File)
      • ARM指令集的寄存器文件包含16个寄存器,其中R0~R12是通用寄存器,R13是栈指针(Stack Pointer,SP),R14是链接寄存器(Link Register,LR),R15是程序计数器(Program Counter,PC)
  • 硬件(微架构)
    • 配置(Configuration)
      • 为了方便起见,存储的单位是指令,而不是字节
      • 因为一条指令为32位 = 4B,所以内存大小为1024 * 4B = 4KB
      • 程序代码的加载地址为CODE_ADDRESS = 0
    • 指令周期(Instruction Cycle)
      • 取指(Fetch)
        • 从寄存器文件,读取PC
        • 从内存,读取指令
        • PC加1
      • 译码(Decode)
        • MOV、MVN包含opcode、argument1、argument2
        • 其他指令包含opcode、argument1、argument2、argument3
      • 执行(Execute)
        • MOV、MVN只支持寄存器-立即数(Register-Immediate)
        • 其他指令只支持寄存器-寄存器-寄存器(Register-Register-Register)
// ----- Hardware/Software Interface (Instruction Set Architecture) -----
    * Opcode
    * Register File

// Opcode
    * And, Exclusive Or, Subtraction, Reverse Subtraction, Addition
    * Or, Move, Bit Clear, Move Not
AND = 0; EOR = 1; SUB = 2; RSB = 3; ADD = 4
ORR = 12; MOV = 13; BIC = 14; MVN = 15

// Register File
    * General Purpose
    * Stack Pointer (SP)
    * Link Register (LR)
    * Program Counter (PC)
R0 = 0; R1 = 1; R2 = 2; R3 = 3; R4 = 4; R5 = 5; R6 = 6; R7 = 7; R8 = 8; R9 = 9; R10 = 10; R11 = 11; R12 = 12
SP = 13
LR = 14
PC = 15

REGISTER_FILE_SIZE = 16
register_file = [0] * REGISTER_FILE_SIZE



// ----- Hardware (Micro-Architecture) -----
    * Configuration
    * instruction_cycle()
        * fetch()
        * decode()
        * execute()

// Configuration
MEMORY_SIZE = 1024
memory = [0] * MEMORY_SIZE

CODE_ADDRESS = 0

// (register_file, memory) -> ()
def instruction_cycle(register_file, memory):
    // Fetch
    instruction = fetch(register_file, memory)

    // Decode
    (opcode, arguments) = decode(instruction)

    // Execute
    execute(opcode, arguments, register_file)

// (register_file, memory) -> (instruction)
def fetch(register_file, memory):
    instruction = 0

    // Get program counter
    program_counter = register_file[PC]

    // Get instruction
    instruction = memory[CODE_ADDRESS + program_counter]

    print("[Fetch]")
    print(f"instruction: 0x{instruction:08x}")
    print()

    // Add program counter by 1
    register_file[PC] += 1

    return instruction

// (instruction) -> (opcode, arguments)
def decode(instruction):
    opcode = 0
    arguments = ()

    // Get opcode
    opcode = instruction & 0xFF

    print("[Decode]")
    print(f"opcode: 0x{opcode:02x}")

    // Get arguments
    if (opcode == MOV) or (opcode == MVN):
        argument1 = (instruction >> 8) & 0xFF
        argument2 = (instruction >> 16) & 0xFF

        print(f"argument1: 0x{argument1:02x}")
        print(f"argument2: 0x{argument2:02x}")
        print()

        arguments = (argument1, argument2, None)
    else:
        argument1 = (instruction >> 8) & 0xFF
        argument2 = (instruction >> 16) & 0xFF
        argument3 = (instruction >> 24) & 0xFF

        print(f"argument1: 0x{argument1:02x}")
        print(f"argument2: 0x{argument2:02x}")
        print(f"argument3: 0x{argument3:02x}")
        print()

        arguments = (argument1, argument2, argument3)

    return (opcode, arguments)

// (opcode, arguments, register_file) -> ()
def execute(opcode, arguments, register_file):
    // Register-Immediate
    if (opcode == MOV) or (opcode == MVN):
        (rd, imm, _) = arguments

        if opcode == MOV:
            register_file[rd] = imm
        else:
            register_file[rd] = ~imm

    // Register-Register-Register
    else:
        (rd, rn, rm) = arguments

        op1 = register_file[rn]
        op2 = register_file[rm]

        if opcode == AND:
            register_file[rd] = op1 & op2
        elif opcode == EOR:
            register_file[rd] = op1 ^ op2
        elif opcode == SUB:
            register_file[rd] = op1 - op2
        elif opcode == RSB:
            register_file[rd] = op2 - op1
        elif opcode == ADD:
            register_file[rd] = op1 + op2
        elif opcode == ORR:
            register_file[rd] = op1 | op2
        else:
            register_file[rd] = op1 & (~op2)

运行程序

  • 软件(程序)
    • 汇编器(Assembler)
      • 直接将指令翻译为二进制,不支持伪指令、标号
    • 加载器(Loader)
      • 直接将二进制加载到CODE_ADDRESS = 0,不支持多个程序的链接
    • 运行(Run)
      • 程序(Program)
      • 汇编(Assemble)
      • 加载(Load)
      • 启动(Boot)
      • 打印(Print)
  • 运行结果
    • 程序有3条指令,打印结果为R3 = 42
      • MOV, R1, 21
      • MOV, R2, 21
      • ADD, R3, R1, R2
    • 取指、译码结果如下,它类似于小端存储
      • 0x 00 (None) — 15 (21) — 01 (R1) — 0d (MOV)
      • 0x 00 (None) — 15 (21) — 02 (R2) — 0d (MOV)
      • 0x 02 (R2) — 01 (R1) — 03 (R3) — 04 (ADD)
// ----- Software (Program) -----
    * run()
        * assembler()
        * loader()
        * instruction_cycle() --> Hardware (Micro-Architecture)

// (register_file, memory) -> ()
def run(register_file, memory):
    // Program
    program_assembly = [
        [MOV, R1, 21],
        [MOV, R2, 21],
        [ADD, R3, R1, R2]
    ]

    // Assemble
    program_binary = assembler(program_assembly)

    // Load
    loader(program_binary, memory)

    // Boot
    register_file[PC] = 0

    while register_file[PC] < len(program_assembly):
        instruction_cycle(register_file, memory)

    // Print
    print("The result is " + str(register_file[R3]))

// (program_assembly) -> (program_binary)
def assembler(program_assembly):
    program_binary = []

    for instruction in program_assembly:
        // Get opcode
        opcode = instruction[0]

        // Get arguments
        if (opcode == MOV) or (opcode == MVN):
            argument1 = instruction[1]
            argument2 = instruction[2]
            argument3 = 0
        else:
            argument1 = instruction[1]
            argument2 = instruction[2]
            argument3 = instruction[3]

        instruction_binary = opcode + (argument1 << 8) + (argument2 << 16) + (argument3 << 24)

        program_binary.append(instruction_binary)

    return program_binary

// (program_binary, memory) -> ()
def loader(program_binary, memory):
    program_counter = 0

    for instruction_binary in program_binary:
        memory[CODE_ADDRESS + program_counter] = instruction_binary
        program_counter += 1

// Run program
run(register_file, memory)



// ----- Run Program -----

$ python ./bare_bones_model.py
[Fetch]
instruction: 0x0015010d

[Decode]
opcode: 0x0d
argument1: 0x01
argument2: 0x15

[Fetch]
instruction: 0x0015020d

[Decode]
opcode: 0x0d
argument1: 0x02
argument2: 0x15

[Fetch]
instruction: 0x02010304

[Decode]
opcode: 0x04
argument1: 0x03
argument2: 0x01
argument3: 0x02

The result is 42