[8位二進制CPU的設計和實現] CPU指令系統的實現


CPU指令系統的實現

本文是對B站UP躊躇月光出的8位二進制CPU的設計和實現的文字教程復現第三部分 CPU指令系統的實現
相關 github 地址:https://github.com/StevenBaby/computer
PS:有錯誤的地方請指正,謝謝!共同學習,一起進步!

指令系統

如第二部分所述,我們實現了一些自動運算的電路部分。但是對於一個CPU來說,這些功能還不算完全。
因此,這一部分,我們將從一個整體的角度去考慮如何設計CPU的指令系統和相應的電路。

寄存器

以下是CPU需要使用的寄存器,我們需要設計電路來控制這些寄存器

  • PC 程序計數器

  • ALU

  • PSW/FLAG 程序狀態字

  • A 寄存器

  • B 寄存器

  • C 寄存器

  • D 寄存器

  • IR 指令寄存器

  • DST 目的操作數寄存器

  • SRC 目的操作數寄存器

  • MSR 存儲器段寄存器

  • MAR 存儲器地址寄存器

  • MDR 存儲器數據寄存器

  • MC 內存寄存器

  • SP 堆棧指針寄存器

  • BP 基址寄存器

  • SI 源變址寄存器

  • DI 目的變址寄存器

  • CS 代碼段寄存器

  • DS 數據段寄存器

  • SS 堆棧段寄存器

  • ES 附加段寄存器

  • TMP 臨時寄存器若干

指令系統

除了寄存器,還有一套控制指令

  • 二操作數:3bit 最高位為1
    • mov dst , src
    • add dst , src
    • sub dst , src
    • cmp dst , src
    • and dst , src
    • or dst , src
    • xor dst , src
  • 一操作數:此最高位為1
    • inc src // 可用add實現
    • dec src // 可用sub實現
    • not src
    • call src
    • jmp dst
    • jo dst
    • jno dst
    • jz dst
    • jnz dst
    • push src
    • pop src
    • int dst
  • 零操作數:
    • nop
    • hlt
    • ret
    • iret

尋址方式 2 x 3bit

  • 立即尋址 MOV A , 5
  • 寄存器尋址 MOV A , B
  • 直接尋址 MOV A , [5]
  • 寄存器間接尋址 MOV A , [B]

程序狀態字 4bit

  • OF 溢出標志
  • ZF 零標志
  • PF 奇偶標志
  • IF 中斷標志

時鍾周期 4bit

寄存器控制器

參考工程:23 寄存器控制器

PC計數器改造

因為ALU經常變動,這里將PC計數器里的自增單元從ALU改為8位加法器

在前一部分,我們控制寄存器采用的方案是將16位進行分割,這樣假設全是控制寄存器也只能控制8個寄存器,就算是用32位也只能控制16個,而我們需要控制的光是寄存器就達到了二十多個,顯然是不夠用的。

因為每次數據交換只需要使用兩個器件,所以我們在這里采用壓縮編碼的方式:采用5-32譯碼器來通過5位控制32位,10位可以控制64位!!這樣就夠我們使用了

我們創建兩個五位輸入端,一個是W,也就是寫端,還有一個是R,也就是讀,這樣可以分開控制兩個器件,讓一個寫一個讀即可完成數據交換。(寄存器也是兩個功能:寫和讀)

532譯碼器和32位異或門

利用和38譯碼器同樣的方式,我們制作一個532譯碼器

import os

dirname = os.path.direname(__file__)
filename = os.path.join(dirname,'532decoder.bin')

with open(filename,'wb') as file:
    for var in range(32):
        value = 1<<var
        result = value.to_bytes(4,byteorder='little')
        file.write(result)

為了防止單個器件同時讀和寫的情況,這里給 W 和 R 加上一個異或門(相同則輸出0)

寄存器控制器

寄存器控制器設計電路如下

可以看一下,第三個是MDR,第九個是B,寄存器的兩位分別是WE和CS,讀是WE=0,CS=1;寫是WE=1,CS=1;

CPU框架

參考工程:24 CPU框架

這一節我們將所有寄存器,運算單元和計數器全部放到總線上

首先將 寄存器的WECS匯總成兩位,CS為高位,WE為低位

寄存器連接總線通用方法:除了特殊的,一律是 DIDO 接數據總線,CP接時鍾線,Clear接復位線,Pre不接,IO接CPU控制單元,S不接(接探針)

  • MDRCDCSDSSSESDISISPBPVECT1T2寄存器全部按照通用方法接線,程序計數器PC也按照通用方法接線

  • MSRMAR合並為16位地址進行RAM尋址

  • 寄存器AB接到ALU的輸入端

  • 寄存器IRDSTSRC的S接到控制單元,這個后面會提到

以上就是寄存器掛 到總線的方式(注意特殊的地方)

CPU控制器

參考工程:25 CPU控制器

這一節比較復雜,一定要認真研究,先看一下總覽圖

寄存器讀寫控制器

前一節CPU框架介紹了寄存器讀寫總線是如何建立的,讀寫控制器的作用是三選一

取指系統

將CPU控制器接入整個系統

取指令微程序

參考工程:26 取指令微程序

回顧第二部分我們搭建的那個小型自動電路用來完成某項任務所寫的微程序

現在我們 為上面這樣一個完整的CPU來寫一個微程序完成以下任務:

任務描述:將RAM中0地址 1地址 2地址數據分別取出放入IR DST SRC寄存器中。

指令設計

# coding=utf-8

MSR = 1
MAR = 2
MDR = 3
RAM = 4
IR = 5
DST = 6
SRC = 7
A = 8
B = 9
C = 10
D = 11
DI = 12
SI = 13
SP = 14
BP = 15
CS = 16
DS = 17
SS = 18
ES = 19
VEC = 20
T1 = 21
T2 = 22

MSR_OUT = MSR
MAR_OUT = MAR
MDR_OUT = MDR
RAM_OUT = RAM
IR_OUT = IR
DST_OUT = DST
SRC_OUT = SRC
A_OUT = A
B_OUT = B
C_OUT = C
D_OUT = D
DI_OUT = DI
SI_OUT = SI
SP_OUT = SP
BP_OUT = BP
CS_OUT = CS
DS_OUT = DS
SS_OUT = SS
ES_OUT = ES
VEC_OUT = VEC
T1_OUT = T1
T2_OUT = T2

_DST_SHIFT = 5

MSR_IN = MSR << _DST_SHIFT
MAR_IN = MAR << _DST_SHIFT
MDR_IN = MDR << _DST_SHIFT
RAM_IN = RAM << _DST_SHIFT
IR_IN = IR << _DST_SHIFT
DST_IN = DST << _DST_SHIFT
SRC_IN = SRC << _DST_SHIFT
A_IN = A << _DST_SHIFT
B_IN = B << _DST_SHIFT
C_IN = C << _DST_SHIFT
D_IN = D << _DST_SHIFT
DI_IN = DI << _DST_SHIFT
SI_IN = SI << _DST_SHIFT
SP_IN = SP << _DST_SHIFT
BP_IN = BP << _DST_SHIFT
CS_IN = CS << _DST_SHIFT
DS_IN = DS << _DST_SHIFT
SS_IN = SS << _DST_SHIFT
ES_IN = ES << _DST_SHIFT
VEC_IN = VEC << _DST_SHIFT
T1_IN = T1 << _DST_SHIFT
T2_IN = T2 << _DST_SHIFT

SRC_R = 2 ** 10
SRC_W = 2 ** 11
DST_R = 2 ** 12
DST_W = 2 ** 13

PC_WE = 2 ** 14
PC_CS = 2 ** 15
PC_EN = 2 ** 16

PC_OUT = PC_CS
PC_IN = PC_CS | PC_WE
PC_INC = PC_CS | PC_WE | PC_EN


HLT = 2 ** 31

OUT指讀寄存器即將寄存器數據送到總線,IN指寫寄存器,W處於高五位,R處於低五位

程序設計

# coding=utf-8

import pin

FETCH = [
    pin.PC_OUT | pin.MAR_IN,
    pin.RAM_OUT | pin.IR_IN | pin.PC_INC,
    pin.PC_OUT | pin.MAR_IN,
    pin.RAM_OUT | pin.DST_IN | pin.PC_INC,
    pin.PC_OUT | pin.MAR_IN,
    pin.RAM_OUT | pin.SRC_IN | pin.PC_INC,
]

  • pin.PC_OUT | pin.MAR_IN 程序計數器PC的數送到存儲器地址寄存器MAR
  • pin.RAM_OUT | pin.IR_IN | pin.PC_INC 存儲器地址寄存器MAR指向RAM地址的數據送到指令寄存器IR,程序計數器PC+1
  • pin.PC_OUT | pin.MAR_IN 程序計數器PC的數送到存儲器地址寄存器MAR
  • pin.RAM_OUT | pin.DST_IN | pin.PC_INC存儲器地址寄存器MAR指向RAM地址的數據送到目的數寄存器DST,程序計數器PC+1
  • pin.PC_OUT | pin.MAR_IN 程序計數器PC的數送到存儲器地址寄存器MAR
  • pin.RAM_OUT | pin.SRC_IN | pin.PC_INC存儲器地址寄存器MAR指向RAM地址的數據送到目的數寄存器SRC,程序計數器PC+1

編譯下載

# coding=utf-8

import os
import pin
import assembly as ASM

dirname = os.path.dirname(__file__)
filename = os.path.join(dirname, 'micro.bin')

micro = [pin.HLT for _ in range(0x10000)] # 在ROM里寫滿HLT指令

for addr in range(0x10000):
    ir = addr >> 8            # 從地址中取出IR即指令信息
    psw = (addr >> 4) & 0xf   # 從地址中取出PSW即狀態字信息
    cyc = addr & 0xf          # 從地址中取出系統時鍾周期信息

    if cyc < len(ASM.FETCH):  # 如果將指令一個個填充進去
        micro[addr] = ASM.FETCH[cyc]

with open(filename, 'wb') as file: # 轉換成二進制
    for var in micro:
        value = var.to_bytes(4, byteorder='little')
        file.write(value)

print('Compile micro instruction finish!!!')


將二進制文件下載進ROM里

啟動運行

取指令功能實現

指令集

我們知道存放程序(ROM)的地址有16位,高八位代表指令,中間四位代表狀態字,后四位代表微程序的周期,即

| 指令 IR 8位 | 程序狀態字PSW | 微程序周期 |

  • 指令 IR 8位:最高支持256個指令
  • 程序狀態字PSW
  • 微程序周期 4位:單個指令的周期為16,就是支持16個微指令,不能超過16

關於指令集,再說幾點

  • 上面的ROM里存放的就是指令集
  • 指令集最高支持256個指令,每個指令支持16個微指令
  • 而我們的程序是寫在RAM里的,RAM里存放的是指令,指令集里每個指令的前面幾個微指令都會有把RAM里指令讀出來的功能。

MOV指令

參考工程:27 MOV 指令

假設指令為

MOV A , 5:該指令為兩種尋址方式:寄存器尋址立即尋址


總共表示四種尋址方式:

  • 立即尋址 MOV A , 5
  • 寄存器尋址 MOV A , B
  • 直接尋址 MOV A , [5]
  • 寄存器間接尋址 MOV A , [B]

我們做以下編碼

  • 立即尋址 = 0
  • 寄存器尋址 = 1
  • 直接尋址 = 2
  • 寄存器間接尋址 = 3

對於不同長度的指令:

  • 二地址指令 MOV A,B
  • 一地址指令 NOT A
  • 零地址指令 HLT

我們做以下編碼

  • 二地址指令
    • 1xxx [aa] [bb] # 這里aa,bb均代表尋址方式
  • 一地址指令
    • 01xxxx[aa] # 這里aa代表尋址方式
  • 零地址指令
    • 00xxxxxx

根據以上編碼原理,我們寫下編譯器預定義程序


CYC = 2 ** 30
HLT = 2 ** 31

... #省略控制定義

## 指令長度定義

ADDR2 = 1 << 7 # 二地址指令 1xxx xxxx
ADDR1 = 1 << 6 # 一地址指令 01xx xxxx

ADDR2_SHIFT = 4 # 二地址指令偏移
ADDR1_SHIFT = 2 # 一地址指令偏移

## 尋址方式定義

AM_INS = 0 # 立即尋址編號
AM_REG = 1 # 寄存器尋址編號
AM_DIR = 2 # 直接尋址編號
AM_RAM = 3 # 寄存器間接尋址編號


通過上面我們可以定義匯編指令


MOV = 0 | pin.ADDR2                       # MOV指令定義位1000 xxxx
ADD = (1 << pin.ADDR2_SHIFT) | pin.ADDR2  # ADD指令定義為 1001 xxxx

NOP = 0     # NOP指令定義為 0000 0000
HLT = 0x3f  # HLT指令定義為 0011 1111

然后我們對指令的尋址方式進行定義


INSTRUCTIONS = {
    2: { # 二操作數指令列表
        MOV: { # MOV指令尋址方式列表
            (pin.AM_REG, pin.AM_INS): [  # (寄存器尋址,立即尋址) 例如 MOV A,5
                pin.DST_W | pin.SRC_OUT, # 微指令:DST寄存器寫,SRC讀,SRC->DST,這里DST是控制寄存器寫
            ]
        }
    },
    1: {},# 一操作數指令列表
    0: {  # 零操作數指令列表
        NOP: [
            pin.CYC, # 讓指令周期清零,跳過這次指令
        ],
        HLT: [
            pin.HLT, # 指令停止
        ]
    }
}

生成指令集

在看這段程序之前,我再強調一下幾點概念
首先是指令,上面我們所做的事情都是在對指令進行編碼,如MOV,我們編碼為1000 xxxxMOV 寄存器,立即數 我們編碼為1000 0100,這樣的編碼是為了方便在將指令放在RAM中(同時在ROM指令集里尋找到相應的微指令),我們將匯編語言如MOV 寄存器,立即數翻譯成1000 0100稱作編譯,但是編譯出來的1000 0100這樣的機器語言仍然只是編碼。真正的指令是存放在CPU內部的ROM指令集里的,我們需要用這個機器語言去找到對應的微指令。而這個微指令才是真正控制CPU里的所有資源的指令。

好了,接下來我們來看生成這樣的指令集的程序

# coding=utf-8

import os
import pin
import assembly as ASM

dirname = os.path.dirname(__file__)
filename = os.path.join(dirname, 'micro.bin')

micro = [pin.HLT for _ in range(0x10000)] # 將程序停止指令放滿整個指令集


def compile_addr2(addr, ir, psw, index): # 處理二操作數的指令
    global micro

    op = ir & 0xf0 # 取出操作指令
    amd = (ir >> 2) & 3 # 取出目標操作數的尋址方式
    ams = ir & 3 # 取出源操作數的尋址方式

    INST = ASM.INSTRUCTIONS[2] # 取出二操作數的所有指令的列表
    if op not in INST:         # 遍歷二操作數的所有指令看存不存在,如果不存在
        micro[addr] = pin.CYC # 跳過該指令
        return
    am = (amd, ams) # 目的操作數和源操作數合起來
    if am not in INST[op]: # 遍歷該指令下的所有尋址方式,如果不存在
        micro[addr] = pin.CYC # 跳過該指令
        return

    EXEC = INST[op][am] # 假設指令和尋址方式都找到了,則拷貝出對應的微指令
    if index < len(EXEC): # 把指令補到后面
        micro[addr] = EXEC[index]
    else:
        micro[addr] = pin.CYC


def compile_addr1(addr, ir, psw, index): # 處理一操作數的指令
    pass


def compile_addr0(addr, ir, psw, index): # 處理零操作數的指令
    global micro

    op = ir # 取出操作指令

    INST = ASM.INSTRUCTIONS[0] # 取出零操作數的所有指令的列表
    if op not in INST: # 遍歷二操作數的所有指令看存不存在,如果不存在
        micro[addr] = pin.CYC # 跳過該指令
        return

    EXEC = INST[op] # 假設指令找到了,則拷貝出對應的微指令
    if index < len(EXEC): # 把指令補到后面
        micro[addr] = EXEC[index]
    else:
        micro[addr] = pin.CYC


for addr in range(0x10000): # 對整個指令集依次處理
    ir = addr >> 8            # 取出表示指令的一段
    psw = (addr >> 4) & 0xf   # 取出表示狀態字的一段
    cyc = addr & 0xf          # 取出微指令周期的一段

    if cyc < len(ASM.FETCH): # 這里是將一段取值微程序放到所有指令中
        micro[addr] = ASM.FETCH[cyc]
        continue

    addr2 = ir & (1 << 7)    # 取出表示二操作數指令的位
    addr1 = ir & (1 << 6)    # 取出表示一操作數指令的位

    index = cyc - len(ASM.FETCH) # ASM.FETCH已經有6個指令

    if addr2: # 對操作數不同的指令分情況處理
        compile_addr2(addr, ir, psw, index)
    elif addr1:
        compile_addr1(addr, ir, psw, index)
    else:
        compile_addr0(addr, ir, psw, index)


with open(filename, 'wb') as file:
    for var in micro:
        value = var.to_bytes(4, byteorder='little')
        file.write(value)

print('Compile micro instruction finish!!!')


將編譯完后的指令集下載到ROM里

現在我們手動往RAM里寫程序

MOV A,5 機器碼 84 08 05
HLT 機器碼 3F

啟動運行

但是這里還沒有實現匯編的編譯器,所以看下一節

匯編編譯器

參考工程:28 匯編編譯器

本節來寫一下編譯器,實現將匯編指令翻譯成機器語言


# coding=utf-8

import os
import re

import pin
import assembly as ASM

dirname = os.path.dirname(__file__)

inputfile = os.path.join(dirname, 'program.asm') # 讀入匯編文件
outputfile = os.path.join(dirname, 'program.bin')

annotation = re.compile(r"(.*?);.*") # 正則匹配

codes = []

OP2 = { # 二操作數指令列表
    'MOV': ASM.MOV
}

OP1 = { # 一操作數指令列表

}

OP0 = { # 零操作數指令列表
    'NOP': ASM.NOP,
    'HLT': ASM.HLT,
}

OP2SET = set(OP2.values()) 
OP1SET = set(OP1.values())
OP0SET = set(OP0.values())

REGISTERS = { # 可操作寄存器
    "A": pin.A,
    "B": pin.B,
    "C": pin.C,
    "D": pin.D,
}


class Code(object): # Code對象

    def __init__(self, number, source):
        self.numer = number # 行號
        self.source = source.upper() # 源代碼
        self.op = None
        self.dst = None
        self.src = None
        self.prepare_source() # 調用預處理源代碼

    def get_op(self): # 獲取指令
        if self.op in OP2:
            return OP2[self.op]
        if self.op in OP1:
            return OP1[self.op]
        if self.op in OP0:
            return OP0[self.op]
        raise SyntaxError(self)

    def get_am(self, addr): # 獲取目的 操作數和源操作數
        if not addr: # 如果啥都沒有,返回0
            return 0, 0
        if addr in REGISTERS: # 如果是寄存器,列表中存在返回寄存器編碼
            return pin.AM_REG, REGISTERS[addr]
        if re.match(r'^[0-9]+$', addr): # 如果是數字,返回立即數
            return pin.AM_INS, int(addr)
        if re.match(r'^0X[0-9A-F]+$', addr): # 如果是十六進制數,返回十六進制立即數
            return pin.AM_INS, int(addr, 16)

        raise SyntaxError(self)

    def prepare_source(self): # 預處理源代碼
        tup = self.source.split(',') # 用逗號分割代碼
        if len(tup) > 2: # 如果分割出來長度大於2 說明語法錯誤
            raise SyntaxError(self)
        if len(tup) == 2: # 如果分割出來等於二
            self.src = tup[1].strip() # 把逗號后面的分配給源操作數

        tup = re.split(r" +", tup[0]) # 用正則空格來分割
        if len(tup) > 2: # 如果分割出來長度大於2 說明語法錯誤
            raise SyntaxError(self)
        if len(tup) == 2: # 如果等於二
            self.dst = tup[1].strip() # 將后面的分配給目的操作數

        self.op = tup[0].strip() # 前面的分配給指令

    def compile_code(self):
        op = self.get_op() # 獲取指令

        amd, dst = self.get_am(self.dst) # 獲取目的操作數編碼
        ams, src = self.get_am(self.src) # 獲取源操作數編碼

        if op in OP2SET: # 獲取指令編碼
            ir = op | (amd << 2) | ams
        elif op in OP1SET:
            ir = op | amd
        else:
            ir = op

        return [ir, dst, src]

    def __repr__(self): # 打印的時候顯示
        return f'[{self.numer}] - {self.source}' # 顯示行號+源代碼


class SyntaxError(Exception): # 語法錯誤

    def __init__(self, code: Code, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.code = code


def compile_program():
    with open(inputfile, encoding='utf8') as file: # 讀入匯編文件
        lines = file.readlines() # 記錄行號

    for index, line in enumerate(lines):
        source = line.strip() # 將兩端的空格去掉
        if ';' in source: # 將;后面的去掉
            match = annotation.match(source)
            source = match.group(1)
        if not source: # 如果沒有代碼跳過
            continue
        code = Code(index + 1, source) # 生成Code類對源代碼進行處理
        codes.append(code)

    with open(outputfile, 'wb') as file:
        for code in codes:
            values = code.compile_code() # 獲得 編碼
            for value in values:
                result = value.to_bytes(1, byteorder='little')
                file.write(result)


def main():
    compile_program()
    # try:
    #     compile_program()
    # except SyntaxError as e:
    #     print(f'Syntax error at {e.code}')
    #     return

    print('compile program.asm finished!!!')


if __name__ == '__main__':
    main()


asm文件里寫入以下程序

MOV A, 5; this is annotation

MOV A, 5

MOV B, 10;

MOV C, 11;

MOV A, 0x30;

MOV D, 0x30;

hlt;

啟動編譯器,我們得到bin文件

84 08 05 84 08 05 84 09 0A 84 0A 0B 84 08 30 84 0B 30 3F 00 00

  • 84 08 05MOV A, 5

    • 84 = 1000 0100MOV 寄存器尋址,立即數尋址
    • 08:寄存器A的編碼
    • 05:立即數5
  • 84 08 05

  • 84 09 0AMOV B, 10

    • 84 = 1000 0100MOV 寄存器尋址,立即數尋址
    • 09:寄存器B的編碼
    • 0A:立即數10
  • 84 0A 0BMOV C, 11

    • 84 = 1000 0100MOV 寄存器尋址,立即數尋址
    • 0A:寄存器C的編碼
    • 0B:立即數11
  • 84 08 30MOV A, 0x30

    • 84 = 1000 0100MOV 寄存器尋址,立即數尋址
    • 08:寄存器A的編碼
    • 30:立即數十六進制0x30
  • 84 08 30MOV A, 0x30

    • 84 = 1000 0100MOV 寄存器尋址,立即數尋址
    • 08:寄存器A的編碼
    • 30:立即數十六進制0x30
  • 84 0B 30MOV A, 0x30

    • 84 = 1000 0100MOV 寄存器尋址,立即數尋址
    • 0B:寄存器D的編碼
    • 30:立即數十六進制0x30
  • 3F:指令hlt的編號

將匯編編譯過后的bin文件導入,運行

數據傳送指令

參考工程:29 數據傳送指令

這一節我們繼續實現MOV指令的其他尋址方式

  • 寄存器尋址,立即尋址
  • 寄存器尋址,寄存器尋址
  • 寄存器尋址,直接尋址
  • 寄存器尋址,寄存器間接尋址
  • 直接尋址,立即尋址
  • 直接尋址,寄存器尋址
  • 直接尋址,直接尋址
  • 直接尋址,寄存器間接尋址
  • 寄存器間接尋址,立即數尋址
  • 寄存器間接尋址,寄存器尋址
  • 寄存器間接尋址,直接尋址
  • 寄存器間接尋址,寄存器間接尋址

注:因為指令例如MOV A,5 的三個分別是讀到IRDSTSRC三個寄存器里的,因此對於后兩個寄存器DSTSRC有兩個功能,一是直接讀寫寄存器的數據(這里我稱為寄存器讀/寫),二是通過寄存器里的數據代號間接讀取或寫入其他寄存器里的數值(這里我稱為讀/寫寄存器)

INSTRUCTIONS = {
    2: {
        MOV: {
            (pin.AM_REG, pin.AM_INS): [ # (寄存器尋址,立即尋址) 例如 MOV A,5
                pin.DST_W | pin.SRC_OUT, # 微指令:DST寫寄存器,SRC寄存器讀,SRC->DST,讀取SRC寄存器里數據送入DST指定的寄存器中
            ],
            (pin.AM_REG, pin.AM_REG): [ # (寄存器尋址,寄存器尋址) 例如 MOV A,B
                pin.DST_W | pin.SRC_R,  # 微指令:DST寫寄存器,SRC讀寄存器,SRC->DST,這里DST,SRC是控制其他寄存器寫和讀
            ],
            (pin.AM_REG, pin.AM_DIR): [ # (寄存器尋址,直接尋址) 例如 MOV A,[5]
                pin.SRC_OUT | pin.MAR_IN, # 從SRC寄存器讀,送到RAM地址線上
                pin.DST_W | pin.RAM_OUT # 從RAM指定地址讀,DST寫寄存器
            ],
            (pin.AM_REG, pin.AM_RAM): [ # (寄存器尋址,寄存器間接尋址) 例如 MOV A,[B]
                pin.SRC_R | pin.MAR_IN, # SRC讀寄存器(數據),送到RAM地址線上
                pin.DST_W | pin.RAM_OUT # 從RAM指定地址讀數據,DST寫寄存器
            ],
            (pin.AM_DIR, pin.AM_INS): [ # (直接尋址,立即尋址) 例如 MOV [5],5
                pin.DST_OUT | pin.MAR_IN, # 從DST寄存器讀數據,送到RAM地址線上
                pin.RAM_IN | pin.SRC_OUT # 從SRC寄存器讀數據,往RAM里寫
            ],
            (pin.AM_DIR, pin.AM_REG): [ # (直接尋址,寄存器尋址) 例如 MOV [5],A
                pin.DST_OUT | pin.MAR_IN, # DST寄存器讀,送到地址線上
                pin.RAM_IN | pin.SRC_R, # SRC讀寄存器,往RAM里寫
            ],
            (pin.AM_DIR, pin.AM_DIR): [ # (直接尋址,直接尋址) 例如 MOV [5],[2]
                pin.SRC_OUT | pin.MAR_IN, # SRC寄存器讀到地址線上
                pin.RAM_OUT | pin.T1_IN, # 從RAM里讀出來,寫到T1寄存器里
                pin.DST_OUT | pin.MAR_IN, # 再把DST寄存器讀到地址線上
                pin.RAM_IN | pin.T1_OUT, # 把T1寄存器的值寫到RAM里
            ],
            (pin.AM_DIR, pin.AM_RAM): [ # (直接尋址,寄存器間接尋址) 例如 MOV [5],[A]
                pin.SRC_R | pin.MAR_IN, # SRC讀寄存器到地址線上
                pin.RAM_OUT | pin.T1_IN, # 把RAM數據讀到T1上
                pin.DST_OUT | pin.MAR_IN, # DST寄存器讀到地址線上
                pin.RAM_IN | pin.T1_OUT, # 把T1寫到RAM里
            ],

            (pin.AM_RAM, pin.AM_INS): [ # (寄存器間接尋址,立即數尋址) 例如 MOV [A],5
                pin.DST_R | pin.MAR_IN, # DST讀寄存器到地址線上
                pin.RAM_IN | pin.SRC_OUT # SRC寄存器數據寫到RAM里
            ],
            (pin.AM_RAM, pin.AM_REG): [ # (寄存器間接尋址,寄存器尋址) 例如 MOV [A],B
                pin.DST_R | pin.MAR_IN, # DST讀寄存器到地址線上
                pin.RAM_IN | pin.SRC_R, # SRC讀寄存器到RAM里
            ],
            (pin.AM_RAM, pin.AM_DIR): [ # (寄存器間接尋址,直接尋址) 例如 MOV [A],[5]
                pin.SRC_OUT | pin.MAR_IN, # SRC寄存器的數據送到地址線上
                pin.RAM_OUT | pin.T1_IN, # RAM里數據讀到T1里
                pin.DST_R | pin.MAR_IN, # DST讀寄存器到地址總線上
                pin.RAM_IN | pin.T1_OUT, # 把T1讀到RAM里
            ],
            (pin.AM_RAM, pin.AM_RAM): [ # (寄存器間接尋址,寄存器間接尋址) 例如 MOV [A],[B]
                pin.SRC_R | pin.MAR_IN, # SRC讀寄存器到地址線上
                pin.RAM_OUT | pin.T1_IN, # RAM輸出到T1
                pin.DST_R | pin.MAR_IN, #  DST讀寄存器到地址線上
                pin.RAM_IN | pin.T1_OUT, # T1輸出到RAM
            ]
        }
    },
    1: {},
    0: {
        NOP: [
            pin.CYC,
        ],
        HLT: [
            pin.HLT,
        ]
    }
}

將控制器編譯完后載入到ROM中

編譯器大致上差不多


# coding=utf-8

import os
import re

import pin
import assembly as ASM

dirname = os.path.dirname(__file__)

inputfile = os.path.join(dirname, 'program.asm')
outputfile = os.path.join(dirname, 'program.bin')

annotation = re.compile(r"(.*?);.*")

codes = []

OP2 = {
    'MOV': ASM.MOV
}

OP1 = {

}

OP0 = {
    'NOP': ASM.NOP,
    'HLT': ASM.HLT,
}

OP2SET = set(OP2.values())
OP1SET = set(OP1.values())
OP0SET = set(OP0.values())

REGISTERS = {
    "A": pin.A,
    "B": pin.B,
    "C": pin.C,
    "D": pin.D,
}


class Code(object):

    def __init__(self, number, source):
        self.numer = number
        self.source = source.upper()
        self.op = None
        self.dst = None
        self.src = None
        self.prepare_source()

    def get_op(self):
        if self.op in OP2:
            return OP2[self.op]
        if self.op in OP1:
            return OP1[self.op]
        if self.op in OP0:
            return OP0[self.op]
        raise SyntaxError(self)

    def get_am(self, addr):
        if not addr:
            return 0, 0
        if addr in REGISTERS:
            return pin.AM_REG, REGISTERS[addr]
        if re.match(r'^[0-9]+$', addr):
            return pin.AM_INS, int(addr)
        if re.match(r'^0X[0-9A-F]+$', addr):
            return pin.AM_INS, int(addr, 16)
        match = re.match(r'^\[([0-9]+)\]$', addr)
        if match:
            return pin.AM_DIR, int(match.group(1))
        match = re.match(r'^\[(0X[0-9A-F]+)\]$', addr)
        if match:
            return pin.AM_DIR, int(match.group(1), 16)
        match = re.match(r'^\[(.+)\]$', addr)
        if match and match.group(1) in REGISTERS:
            return pin.AM_RAM, REGISTERS[match.group(1)]
        raise SyntaxError(self)

    def prepare_source(self):
        tup = self.source.split(',')
        if len(tup) > 2:
            raise SyntaxError(self)
        if len(tup) == 2:
            self.src = tup[1].strip()

        tup = re.split(r" +", tup[0])
        if len(tup) > 2:
            raise SyntaxError(self)
        if len(tup) == 2:
            self.dst = tup[1].strip()

        self.op = tup[0].strip()

    def compile_code(self):
        op = self.get_op()

        amd, dst = self.get_am(self.dst)
        ams, src = self.get_am(self.src)

        if src and (amd, ams) not in ASM.INSTRUCTIONS[2][op]:
            raise SyntaxError(self)
        if not src and dst and amd not in ASM.INSTRUCTIONS[1][op]:
            raise SyntaxError(self)
        if not src and not dst and op not in ASM.INSTRUCTIONS[0]:
            raise SyntaxError(self)

        if op in OP2SET:
            ir = op | (amd << 2) | ams
        elif op in OP1SET:
            ir = op | amd
        else:
            ir = op

        return [ir, dst, src]

    def __repr__(self):
        return f'[{self.numer}] - {self.source}'


class SyntaxError(Exception):

    def __init__(self, code: Code, *args, **kwargs):
        super().__init__(*args, **kwargs)
        self.code = code


def compile_program():
    with open(inputfile, encoding='utf8') as file:
        lines = file.readlines()

    for index, line in enumerate(lines):
        source = line.strip()
        if ';' in source:
            match = annotation.match(source)
            source = match.group(1)
        if not source:
            continue
        code = Code(index + 1, source)
        codes.append(code)

    with open(outputfile, 'wb') as file:
        for code in codes:
            values = code.compile_code()
            for value in values:
                result = value.to_bytes(1, byteorder='little')
                file.write(result)


def main():
    try:
        compile_program()
    except SyntaxError as e:
        print(f'Syntax error at {e.code}')
        return

    print('compile program.asm finished!!!')


if __name__ == '__main__':
    main()


寫下測試指令

; MOV C, 5;

; MOV D, C;

; MOV D, [5];

; MOV A, 6;

; MOV D, [A]


; MOV [0x2f], 5;

; MOV C, 0x18;
; MOV [0x2f], C;

; MOV [0x2e], 18;

; MOV [0x2f], [0x2e];

; MOV [0x18], 0xfe

; MOV C, 0x18;

; MOV D, 0x33;

; MOV [C], D;


MOV [0x30], 0xee;


MOV D, 0x30

MOV C, 0x18;

MOV [C], [0x30];

hlt;

編譯,載入到RAM中運行

算術運算指令

參考工程:30 算術運算指令

我們需要給ALU添加加1和減1的運算,改進 ALU如下:

然后我們定義指令集里 所有運算微指令

_OP_SHIFT = 17

OP_ADD = 0
OP_SUB = 1 << _OP_SHIFT
OP_INC = 2 << _OP_SHIFT
OP_DEC = 3 << _OP_SHIFT
OP_AND = 4 << _OP_SHIFT
OP_OR = 5 << _OP_SHIFT
OP_XOR = 6 << _OP_SHIFT
OP_NOT = 7 << _OP_SHIFT

ALU_OUT = 1 << 20
ALU_PSW = 1 << 21

匯編指令的編號和實現

MOV = (0 << pin.ADDR2_SHIFT) | pin.ADDR2
ADD = (1 << pin.ADDR2_SHIFT) | pin.ADDR2
SUB = (2 << pin.ADDR2_SHIFT) | pin.ADDR2

INC = (0 << pin.ADDR1_SHIFT) | pin.ADDR1
DEC = (1 << pin.ADDR1_SHIFT) | pin.ADDR1


        ADD: {
            (pin.AM_REG, pin.AM_INS): [ # (寄存器尋址,直接尋址) 例如ADD A,5
                pin.DST_R | pin.A_IN, # DST讀寄存器數據到A寄存器中
                pin.SRC_OUT | pin.B_IN, # SRC寄存器讀數據到B寄存器中
                pin.OP_ADD | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW #選擇加法 ,ALU輸出,DST寫寄存器,ALU狀態字
            ],
            (pin.AM_REG, pin.AM_REG): [ # (寄存器尋址,寄存器尋址) 例如ADD A,B
                pin.DST_R | pin.A_IN, # DST讀寄存器到A寄存器
                pin.SRC_R | pin.B_IN, # SRC讀寄存器到B寄存器
                pin.OP_ADD | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW # 加法,ALU輸出,DST寫寄存器,ALU狀態字
            ],
        },
        SUB: {
            (pin.AM_REG, pin.AM_INS): [ # (寄存器尋址,直接尋址) 例如 SUB A,5
                pin.DST_R | pin.A_IN, # DST讀寄存器到A寄存器
                pin.SRC_OUT | pin.B_IN, # SRC寄存器讀數據到B寄存器
                pin.OP_SUB | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW # 減法,ALU輸出,DST寫寄存器 ,ALU狀態字
            ],
            (pin.AM_REG, pin.AM_REG): [ # (寄存器尋址,寄存器尋址) 例如 SUB A,B
                pin.DST_R | pin.A_IN, # DST讀寄存器到A寄存器
                pin.SRC_R | pin.B_IN, # SRC讀寄存器到B寄存器
                pin.OP_SUB | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW # 減法,ALU輸出,DST寫寄存器,ALU狀態字
            ],
        },
        INC: {
            pin.AM_REG: [
                pin.DST_R | pin.A_IN, # DST讀寄存器到A寄存器
                pin.OP_INC | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW # +1,ALU輸出,DST寫寄存器,ALU狀態字
            ],
        },
        DEC: {
            pin.AM_REG: [
                pin.DST_R | pin.A_IN, # DST讀寄存器到A寄存器
                pin.OP_DEC | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW # -1,ALU輸出,DST寫寄存器,ALU狀態字
            ],
        },

寫入匯編程序

MOV D, 0;

INC D;
INC D;
INC D;
INC D;
INC D;

DEC D;
DEC D;
DEC D;
DEC D;
DEC D;

HLT

運行

邏輯運算指令

參考工程:31 邏輯運算指令

CMP = (3 << pin.ADDR2_SHIFT) | pin.ADDR2
AND = (4 << pin.ADDR2_SHIFT) | pin.ADDR2
OR = (5 << pin.ADDR2_SHIFT) | pin.ADDR2
XOR = (6 << pin.ADDR2_SHIFT) | pin.ADDR2

NOT = (1 << pin.ADDR1_SHIFT) | pin.ADDR1

        CMP: {
            (pin.AM_REG, pin.AM_INS): [
                pin.DST_R | pin.A_IN,
                pin.SRC_OUT | pin.B_IN,
                pin.OP_SUB | pin.ALU_PSW
            ],
            (pin.AM_REG, pin.AM_REG): [
                pin.DST_R | pin.A_IN,
                pin.SRC_R | pin.B_IN,
                pin.OP_SUB | pin.ALU_PSW
            ],
        },
        AND: {
            (pin.AM_REG, pin.AM_INS): [
                pin.DST_R | pin.A_IN,
                pin.SRC_OUT | pin.B_IN,
                pin.OP_AND | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW
            ],
            (pin.AM_REG, pin.AM_REG): [
                pin.DST_R | pin.A_IN,
                pin.SRC_R | pin.B_IN,
                pin.OP_AND | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW
            ],
        },
        OR: {
            (pin.AM_REG, pin.AM_INS): [
                pin.DST_R | pin.A_IN,
                pin.SRC_OUT | pin.B_IN,
                pin.OP_OR | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW
            ],
            (pin.AM_REG, pin.AM_REG): [
                pin.DST_R | pin.A_IN,
                pin.SRC_R | pin.B_IN,
                pin.OP_OR | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW
            ],
        },
        XOR: {
            (pin.AM_REG, pin.AM_INS): [
                pin.DST_R | pin.A_IN,
                pin.SRC_OUT | pin.B_IN,
                pin.OP_XOR | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW
            ],
            (pin.AM_REG, pin.AM_REG): [
                pin.DST_R | pin.A_IN,
                pin.SRC_R | pin.B_IN,
                pin.OP_XOR | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW
            ],
        },
        NOT: {
            pin.AM_REG: [
                pin.DST_R | pin.A_IN,
                pin.OP_NOT | pin.ALU_OUT | pin.DST_W | pin.ALU_PSW
            ],
        },

標記轉移指令

參考工程:32 標記轉移指令

JMP 跳轉到標記點

重點是編譯器的實現

條件轉移指令

參考工程:33 條件轉移指令

堆棧操作指令

參考工程:34 堆棧操作指令

函數調用指令

參考工程:35 函數調用指令

內中斷指令

參考工程:36 內中斷指令

勘誤-奇偶校驗位

參考工程:Other


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM