天天看點

北郵2021計導大作業丨C語言實作馮諾依曼式計算機CPU模拟器(一):單核版一、前言二、課題要求三、指令集分析四、實作五、後記

文章目錄

  • 一、前言
  • 二、課題要求
    • (一)任務概述
    • (二)程式的大緻步驟
    • (三)輸入輸出方式
      • 1. 輸入方式
      • 2. 輸出方式
      • 3.輸入輸出樣例:
  • 三、指令集分析
    • (一)指令集(單核版)
    • (二)指令分析
      • 1.指令結構
      • 2.指令種類及作用
        • (1)資料傳送指令
        • (2)算術運算指令
        • (3)邏輯運算指令
        • (4)比較指令
        • (5)跳轉指令
        • (6)輸入輸出指令
    • (三)指令部分總結
  • 四、實作
    • (一)頂層設計
      • 1.全局常量定義
      • 2.全局資料結構定義
      • 3.函數聲明
    • (二)函數定義及調用關系
    • (三)完整代碼
  • 五、後記

一、前言

多核版見我的另一篇文章北郵2021計導大作業丨C語言實作馮諾依曼式計算機CPU模拟器(二):多核版

終于下決心來寫這篇文章。對我來說北郵這個大作業實在是太頂了,做的過程中我也确實學到了很多東西(與之相比複旦C語言課程的大作業就實在是太水了)。

該代碼為一種可能的實作思路,已經在OJ上送出并且全部AC,僅供大家參考、交流。如有後來者(或許有2022的朋友?)參考此代碼,切記不要直接複制粘貼送出!就算是對變量名或函數名稱等進行修改,查重系統也是會查到的。還是希望大家隻是把這篇文章當做參考,以此啟發自己,獨立地完成大作業。不要妄自菲薄,不要認定做不出來,你一定能想出自己的思路,盡管它可能是複雜的,盡管它可能不是最好的,但它是你自己的,這一點最重要。

二、課題要求

(一)任務概述

  1. 模拟一個簡易的馮諾依曼式計算機CPU的工作。
  2. 該CPU字長為16位,共11個寄存器,其中3個系統寄存器,分别為程式計數器,指令寄存器,标志寄存器;8個通用寄存器,即寄存器1、2、3、4(資料寄存器),寄存器5、6、7、8(位址寄存器)。該CPU至多支援32K記憶體。記憶體分兩部分,一部分為代碼段,從位址0開始。另一部分為資料段,從位址16384開始。
  3. 該CPU所支援的指令集見word文檔。每條指令固定由32位(由左至右依次編号為0到31)二進制數組成,其中第0到7位為操作碼,代表CPU要執行哪種操作;第8到15位為操作對象,如寄存器,記憶體位址等;第16到31位為立即數。該CPU有一個輸入端口和一個輸出端口。輸入端口的資料由标準輸入裝置(鍵盤)輸入,輸出端口的資料輸出到标準輸出裝置(顯示器)上。

(二)程式的大緻步驟

  1. 程式開始時要從指定檔案中讀入一段用給定指令集寫的程式至記憶體(從位址0開始順序儲存),程式計數器初始值也為0。此功能為指令加載。
  2. 指令加載完成後程式就開始不斷重複取指令、分析指令和執行指令的過程。程式每執行一條指令就要輸出CPU目前的狀态,如各寄存器的值等。當執行到停機指令時,程式按要求輸出後就結束了。
  3. 取指令:要求讀取程式計數器PC内的指令位址,根據這個位址将指令從記憶體中讀入,并儲存在指令寄存器中,同時程式計數器内容加4,指向下一個條指令。(因為我們所有的指令長度固定為4個位元組,是以加4)。
  4. 分析指令:是指對指令寄存器中的指令進行解碼,分析出指令的操作碼,所需操作數的存放位置等資訊等。
  5. 執行指令:完成相關計算并将結果寫到相應位置。

(三)輸入輸出方式

1. 輸入方式

以檔案的方式輸入,該檔案為一個以停機指令為結尾的指令序列。如:

00001011000100000000000000000000
00000001010100000100000000000000
00000001010100010000000000000000
00001011000100000000000000000000
00000010000101010000000000000000
00001100000100000000000000000000
00000000000000000000000000000000
           

2. 輸出方式

  1. 每執行一條指令後都要輸出各寄存器狀态,格式見樣例
  2. 當執行到輸入指令時在使用者輸入前要輸出:

    in :\n

  3. 當執行到輸出時輸出前要先輸出:

    out :

  4. 輸出指令結束後要輸出一個換行符。
  5. 所有指令執行完後要輸出目前記憶體内容 先輸出代碼段:
    1. 每四個位元組一輸出(也就是每條指令當成一個整數輸出),每行輸出8條指令,共輸出16行
    2. 然後輸出資料段: 每兩個位元組當成一個整數輸出,每行輸出16個整數,共
  6. 輸出16行。 具體格式見樣例。

3.輸入輸出樣例:

  1. 輸入指令:(注意in:後的換行符)
in:
20
           
  1. 輸出指令:
out:30
           
  1. 寄存器狀态:
ip = 56
flag = 1
ir = 3104
ax1 = 1 ax2 = 2 ax3 = 3 ax4 = 2
ax5 = 0 ax6 = 16396 ax7 = 0 ax8 = 0

           
  1. 代碼段記憶體
codeSegment :
185597952 22036480 22085632 23085058 17825792 23134208 152371200 167903260
36044800 34603009 20316160 36700161 23265280 167804956 203423744 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0

           
  1. 資料段記憶體
dataSegment :
5 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0

           

三、指令集分析

(一)指令集(單核版)

類别 指令 說明
停機指令 00000000000000000000000000000000 停止程式執行
資料傳送指令 00000001000100000000000000000000 将一個立即數(綠色部分)傳送至寄存器
資料傳送指令 00000001000101010000000000000000 将寄存器5(5、6、7、8号寄存器為位址寄存器)中位址所指向的記憶體單元(2個位元組)的内容傳送至寄存器1
資料傳送指令 00000001010100010000000000000000 将寄存器1的内容傳送至寄存器5中位址所指向的記憶體單元(2個位元組)5、6、7、8号寄存器為位址寄存器)。
算術運算指令 00000010000100000000000000000000 将寄存器1内的數與一個立即數(綠色部分)相加,結果儲存至寄存器1
算術運算指令 00000010000101010000000000000000 将寄存器1内的數與寄存器5中位址所指向的記憶體單元(2個位元組)裡存的數相加,結果儲存至寄存器1
算術運算指令 00000011000100000000000000000000 将寄存器1内的數減去一個立即數(綠色部分),結果儲存至寄存器1
算術運算指令 00000011000101010000000000000000 将寄存器1内的數減去寄存器5中位址所指向的記憶體單元(2個位元組)裡存的數,結果儲存至寄存器1
算術運算指令 00000100000100000000000000000000 将寄存器1内的數與一個立即數(綠色部分)相乘,結果儲存至寄存器1
算術運算指令 00000100000101010000000000000000 将寄存器1内的數與寄存器5中位址所指向的記憶體單元(2個位元組)裡存的數相乘,結果儲存至寄存器1
算術運算指令 00000101000100000000000000000000 将寄存器1内的數除以(C語言的整數除法)一個立即數(綠色部分),結果儲存至寄存器1
算術運算指令 00000101000101010000000000000000 将寄存器1内的數除以(C語言的整數除法)寄存器5中位址所指向的記憶體單元(2個位元組)裡存的數,結果儲存至寄存器1
邏輯運算指令 00000110000100000000000000000000 将寄存器1内的數與一個立即數(綠色部分)做邏輯與,結果儲存至寄存器1(如果結果為真則儲存1,否則儲存0)
邏輯運算指令 00000110000101010000000000000000 将寄存器1内的數與寄存器5中位址所指向的記憶體單元(2個位元組)裡存的數做邏輯與,結果儲存至寄存器1(如果結果為真則儲存1,否則儲存0)
邏輯運算指令 00000111000100000000000000000000 将寄存器1内的數與一個立即數(綠色部分)做邏輯或,結果儲存至寄存器1(如果結果為真則儲存1,否則儲存0)
邏輯運算指令 00000111000101010000000000000000 将寄存器1内的數與寄存器5中位址所指向的記憶體單元(2個位元組)裡存的數做邏輯或,結果儲存至寄存器1(如果結果為真則儲存1,否則儲存0)
邏輯運算指令 00001000000100000000000000000000 将寄存器1内的數做邏輯非,結果儲存至寄存器1(如果結果為真則儲存1,否則儲存0)
邏輯運算指令 00001000000001010000000000000000 将寄存器5中位址所指向的記憶體單元(2個位元組)裡存的數做邏輯非,結果仍儲存至寄存器5中位址所指向的記憶體單元(如果結果為真則儲存1,否則儲存0)
比較指令 00001001000100000000000000000000 将寄存器1内的數與一個立即數(綠色部分)比較,如兩數相等,則标志寄存器被修置為0,如寄存器1大,則标志寄存器被置為1,如寄存器1小,則标志寄存器被置為-1。
比較指令 00001001000101010000000000000000 将寄存器1内的數與寄存器5中位址所指向的記憶體單元(2個位元組)裡存的數比較,如兩數相等,則标志寄存器被置為0,如寄存器1大,則标志寄存器被置為1,如寄存器1小,則标志寄存器被置為-1。
跳轉指令 00001010000000000000000000000000 無條件跳轉指令,轉移至程式計數器加一個立即數(綠色部分)處執行。也就是說要修改程式計數器。
跳轉指令 00001010000000010000000000000000 如果标志寄存器内的值為0則轉移至程式計數器加一個立即數(綠色部分)處執行。也就是說要修改程式計數器。
跳轉指令 00001010000000100000000000000000 如果标志寄存器内的值為1則轉移至程式計數器加一個立即數(綠色部分)處執行。也就是說要修改程式計數器。
跳轉指令 00001010000000110000000000000000 如果标志寄存器内的值為-1則轉移至程式計數器加一個立即數(綠色部分)處執行。也就是說要修改程式計數器。
輸入輸出指令 00001011000100000000000000000000 從輸入端口讀入一個整數并儲存在寄存器1中。也就是從鍵盤讀一個整數到寄存器1中。
輸入輸出指令 00001100000100000000000000000000 将寄存器1中的數輸出到輸出端口。也就是将寄存器1中的數以整數的形式輸出到顯示器上,同時輸出一個換行符。

(二)指令分析

1.指令結構

所有的指令都是32位二進制數字,具體可拆解為以下四個部分(以下位數均為從左到右):

  1. 1-8位:指明任務類型
  2. 9-12位:操作對象1
  3. 13-16位:操作對象2
  4. 17-32位:立即數

以指令

00000001001001100000000000000000

為例:

  1. 1-8位:

    00000001

    ,對應資料傳送指令
  2. 9-12位:

    0010

    ,,對應寄存器2(資料寄存器)
  3. 13-16位:

    0110

    ,對應寄存器6(位址寄存器)
  4. 17-32位:

    0000000000000000

    ,轉換為十進制即為 。因為該條指令的兩個操作對象都是寄存器,不涉及立即數的運算,是以為0。

2.指令種類及作用

分析單核版指令集,可以發現指令總體可分為六大類(停機指令此處不予讨論):

  • 資料傳送指令
  • 算術運算指令
  • 邏輯運算指令
  • 比較指令
  • 跳轉指令
  • 輸入輸出指令

下面具體分析這六類指令。

(1)資料傳送指令

根據指令集的描述,資料傳送指令的前1-8為均為

00000001

,即十進制的

1

。根據操作對象1(9-12位)與操作對象2(13-16位)的不同,可以分為三大類:

  1. 操作對象2為

    0000

    ,這表明要将立即數傳入操作對象1指代的寄存器内。
  2. 操作對象1轉換為十進制為1-4(資料寄存器),操作對象2轉換為十進制為5-8(位址寄存器),這表明要将操作對象2(5、6、7、8号寄存器)中位址所指向的記憶體單元(2個位元組)的内容傳送至操作對象1(1、2、3、4号寄存器)。
  3. 操作對象1轉換為十進制為5-8(位址寄存器),操作對象2轉換為十進制為1-4(資料寄存器),這表明将操作對象2(1,2,3,4寄存器)的内容傳送至操作對象1(5,6,7,8寄存器)中位址所指向的記憶體單元(2個位元組)。

資料傳送指令即為指派運算。

(2)算術運算指令

當指令的1-8位轉換為10進制為2-5時,對應算術運算指令:

  1. 00000010

    :十進制的2,加法運算
  2. 00000011

    :十進制的3,減法運算
  3. 00000100

    :十進制的4,乘法運算
  4. 00000101

    :十進制的5,除法運算

根據操作對象1與操作對象2的不同可分為兩大類:

  1. 操作對象1為1-4,操作對象2為0,表明對資料寄存器與立即數進行運算,将操作對象1(資料寄存器)内的數與一個立即數運算(加減乘除),結果儲存至操作對象1(資料寄存器)。
  2. 操作對象1為1-3,操作對象2為5-8,表明對資料寄存器與位址寄存器進行運算,将操作對象1(資料寄存器)内的數與操作對象2(位址寄存器)中位址所指向的記憶體單元(2個位元組)裡存的數運算(加減乘除),結果儲存至操作對象1(資料寄存器)。

算術運算指令即為四則運算。

(3)邏輯運算指令

當指令的1-8位轉換為十進制為6-8時,對應邏輯運算指令:

  1. 00000110

    :十進制的6,邏輯與
  2. 00000111

    :十進制的7,邏輯或
  3. 00001000

    :十進制的8,邏輯非

對于邏輯與、邏輯或運算指令,按操作對象1與操作對象2的不同可分為以下兩類:

  1. 操作對象1為1-4,操作對象2為0,表示用操作對象1(資料寄存器)中的數與立即數做邏輯與(邏輯或)運算,将結果儲存在操作對象1(資料寄存器)中(如果結果為真儲存為1,結果為假儲存為0)。
  2. 操作對象1位1-4,操作對象2為5-8,表示用操作對象1(資料寄存器)中的數與操作對象2(位址寄存器)中位址所指向的記憶體單元(兩個位元組)中的數做邏輯與(邏輯或)運算,結果儲存在操作對象1(資料寄存器)中(如果結果為真則儲存為1,結果為假儲存為0)。

對于邏輯非運算指令,按操作對象1與操作對象2的不同也可分為兩類:

  1. 操作對象1為1-4,操作對象2為0,表明對操作對象1(資料寄存器)中的數做邏輯非,運算結果儲存在操作對象1中(若結果為真則儲存為1,結果為假儲存為0)。
  2. 操作對象1為0,操作對象2為5-8,表明對操作對象2(位址寄存器)中位址所指向的記憶體單元(兩個位元組)中的數做邏輯非,運算結果儲存至該記憶體單元(兩個位元組)(如果結果為真儲存為1,結果為假儲存為0).

邏輯運算指令即為與或非。

(4)比較指令

當指令1-8位為9,對應邏輯運算指令。按操作對象1與操作對象2的不同,可分為兩類:

  1. 操作對象1為1-4,操作對象2為0,表示将操作對象1(資料寄存器)内的數與一個立即數比較,如兩數相等,則标志寄存器被修置為0,如操作對象1大,則标志寄存器被置為1,如操作對象1小,則标志寄存器被置為-1。
  2. 操作對象1位1-4,操作對象2為5-8,表示将操作對象1(資料寄存器)内的數與操作對象2(位址寄存器)中位址指向的記憶體單元(兩個位元組)比較,如兩數相等,則标志寄存器被修置為0,如操作對象1大,則标志寄存器被置為1,如操作對象1小,則标志寄存器被置為-1。

比較指令即為比較運算。

(5)跳轉指令

當指令1-8位為10,對應跳轉指令。根據操作位2的不同可分四類:

  1. 操作對象2為0,即

    0000

    ,無條件跳轉指令,轉移至程式計數器加一個立即數處執行。也就是說要修改程式計數器。
  2. 操作對象2為1,即

    0001

    ,如果标志寄存器内的值為0則轉移至程式計數器加一個立即數處執行。也就是說要修改程式計數器。
  3. 操作對象2為2,即

    0010

    ,如果标志寄存器内的值為1則轉移至程式計數器加一個立即數處執行。也就是說要修改程式計數器。
  4. 操作,對象2為3,即

    0011

    ,如果标志寄存器内的值為-1則轉移至程式計數器加一個立即數處執行。也就是說要修改程式計數器。

跳轉指令用來實作循環、選擇、switch、goto語句等。

(6)輸入輸出指令

當指令1-8位為11或12,對應輸入輸出指令:

  1. 00001011

    :十進制為11,對應輸入指令。從輸入端口讀入一個整數并儲存在操作對象1(資料寄存器)中。也就是從鍵盤讀一個整數到寄存器1中。
  2. 00001100

    :十進制為12,對應輸出指令。将操作對象1(資料寄存器)中的數輸出到輸出端口。也就是将寄存器1中的數以整數的形式輸出到顯示器上,同時輸出一個換行符。

輸入輸出指令對應printf()及scanf()等輸入輸出函數。

(三)指令部分總結

不難想到,六種指令互相結合,可以實作諸如

if-else

語句、循環語句等C語言基本語句,進而實作一些簡單的C語言程式。但是,仔細思考不難發現,指令集中的指令隻是一部分,并不全,如資料傳送指令部分隻涉及從立即數到資料寄存器、從資料寄存器到位址寄存器、從位址寄存器到資料寄存器三種,而沒有考慮從資料寄存器之間或是位址寄存器之間的傳送。當然,既然是模拟CPU,也沒有必要搞得那麼複雜。

四、實作

(一)頂層設計

1.全局常量定義

#include <stdio.h>
#include <stdlib.h>
#define Max (16384*2) //記憶體大小
#define N 16384 //資料段起始下标
           

2.全局資料結構定義

用一個結構體表示CPU,其中定義一個字元型數組,用來表示記憶體,其中下标0-18363為代碼段,18364-36727表示資料段,總共32K。

由于要求模拟的是16位CPU,是以再定義一個short型數組,模拟各個寄存器。

struct _cpu{
	char code_data[Max]; //模拟代碼段和資料段  
	short ax[12]; //模拟寄存器 ,0閑置,1-8通用寄存器,9程式計數器(ip),10指令寄存器(ir),11标志寄存器 (flag)
};
typedef struct _cpu* cpuPtr;
           

3.函數聲明

cpuPtr create_cpu(void);
void process(cpuPtr cpu);
void transfer(cpuPtr cpu,short ir[]);
void calculate(cpuPtr cpu,short ir[]);
void logic(cpuPtr cpu,short ir[]);
void comparation(cpuPtr cpu,short ir[]);
void skip(cpuPtr cpu,short ir[]);
void ioput(cpuPtr cpu,short ir[]);
void output(cpuPtr cpu);
void end_output(cpuPtr cpu);
short bin(cpuPtr cpu,int start);
void bin_to_char(cpuPtr cpu,int start,short number);
           

(二)函數定義及調用關系

main()

函數中,首先調用

create_cpu()

函數建立一個結構體指針

cpu

,然後将其作為參數傳入

process()

函數,完成程式任務。

int main(void)
{
	cpuPtr cpu=create_cpu();
	process(cpu);
	return 0;
}
           

create_cpu()

函數中,還要完成指令加載任務:

cpuPtr create_cpu(void)
{
	char ch;
	int i,j;
	int temp;
	FILE *fPtr = fopen("dict.txt","r");
	cpuPtr new_cpu = NULL;
	new_cpu = (cpuPtr)malloc(sizeof(struct _cpu));
	for(i=0;i<Max;i++)
	{
		new_cpu->code_data[i] = 0;
	}
	for(i=0;i<12;i++)
	{
		new_cpu->ax[i] = 0;
	}
	
	for(j=0;!feof(fPtr);)
	{
		for(i=0;i<8;i++)      //因為代碼段code為char型數組
		{                     //是以一條指令需要占用四個數組空間
			ch = fgetc(fPtr); //每次讀取8位二進制值并将其轉換為char存入數組
			if(ch=='\n')
			{                 //如果讀取到換行符,則将其丢棄重新讀取 
				i--;
				continue;
			}
			temp = temp*2 + ch -'0'; //每次從檔案中讀到的其實是'1'或'0'對應的ASCII碼,要将其轉換為數值類型 
		}
		new_cpu->code_data[j++] = temp; //寫入代碼段 
		temp = 0;
	}
	new_cpu->code_data[j-1] = 0; //最後一次在代碼段寫入了EOF(即-1),是以要重新将其改寫為預設值0 
	return new_cpu;	
}
           

process()

函數中,完成取指令、分析指令、執行指令、輸出等任務:

void process(cpuPtr cpu)
{
	short ir[4] = {0};  //拆解指令,分别為指令類型,操作位1,操作位2,立即數 
	/*循環,不斷取指令分析執行,直到遇到停機指令*/ 
	do{
		/*代碼段中每條指令占四個位元組,取出前兩個位元組并拼接寫入ir(指令寄存器)*/
		cpu->ax[10] = cpu->code_data[(cpu->ax[9])++]&0xff; 
		cpu->ax[10] = ((cpu->ax[10]<<8)&0xff00)+(cpu->code_data[(cpu->ax[9])++]&0xff);
		
		/*取出後兩個位元組并拼接,即為立即數,指派給 ir[3]*/ 
		ir[3] = cpu->code_data[cpu->ax[9]++]&0xff;
		ir[3] = ((ir[3]<<8)&0xff00)+(cpu->code_data[cpu->ax[9]++]&0xff);
		
		/*指令寄存器儲存16位指令,前8位對應操作類型,通過位運算取出指派給 ir[0]*/ 
		ir[0] = cpu->ax[10]>>8;
		
		/*指令寄存器9-12位、13-16位對應操作位1和操作位2,位運算取出指派給 ir[1]、ir[2]*/ 
		ir[1] = ((cpu->ax[10]<<8)>>12)&0xf;
		ir[2] = ((cpu->ax[10]<<12)>>12)&0xf;
		
		/*分析指令操作類型,調用對應函數*/ 
		switch(ir[0])
		{
			case 1: transfer(cpu,ir);break; //資料傳送指令 
			case 2:case 3:case 4:case 5: calculate(cpu,ir);break; //算數運算指令 
			case 6:case 7:case 8: logic(cpu,ir);break;        //邏輯運算指令 
			case 9: comparation(cpu,ir);break; //比較運算指令 
			case 10: skip(cpu,ir);break; //跳轉指令 
			case 11:case 12: ioput(cpu,ir);break; //輸入輸出指令 
		}
		
		/*每條指令執行完輸出各寄存器狀态*/ 
		output(cpu);
		
	}while(ir[0]);//ir[0]為0則為停機指令,跳出循環 
	
	/*最後輸出代碼段和資料段資訊*/ 
	end_output(cpu);
}
           

(三)完整代碼

該代碼為一種可能的實作思路,已經在OJ上送出并且全部AC,僅供大家參考、交流。如有後來者(或許有2022的朋友?)參考此代碼,切記不要直接複制粘貼送出!就算是對變量名或函數名稱等進行修改,查重系統也是會查到的。還是希望大家隻是把這篇文章當做參考,以此啟發自己,獨立地完成大作業。不要妄自菲薄,不要認定做不出來,你一定能想出自己的思路,盡管它可能是複雜的,盡管它可能不是最好的,但它是你自己的,這一點最重要。

#include <stdio.h>
#include <stdlib.h>
#define Max (16384*2)
#define N 16384

struct _cpu{
	char code_data[Max]; //模拟代碼段和資料段  
	short ax[12]; //模拟寄存器 ,0閑置,1-8通用寄存器,9程式計數器(ip),10指令寄存器(ir),11标志寄存器 (flag)
};
typedef struct _cpu* cpuPtr;

cpuPtr create_cpu(void);
void process(cpuPtr cpu);
void transfer(cpuPtr cpu,short ir[]);
void calculate(cpuPtr cpu,short ir[]);
void logic(cpuPtr cpu,short ir[]);
void comparation(cpuPtr cpu,short ir[]);
void skip(cpuPtr cpu,short ir[]);
void ioput(cpuPtr cpu,short ir[]);
void output(cpuPtr cpu);
void end_output(cpuPtr cpu);
short bin(cpuPtr cpu,int start);
void bin_to_char(cpuPtr cpu,int start,short number);
int main(void)
{
	cpuPtr cpu=create_cpu();
	process(cpu);
}

cpuPtr create_cpu(void)
{
	char ch;
	int i,j;
	int temp;
	FILE *fPtr = fopen("dict.txt","r");
	cpuPtr new_cpu = NULL;
	new_cpu = (cpuPtr)malloc(sizeof(struct _cpu));
	for(i=0;i<Max;i++)
	{
		new_cpu->code_data[i] = 0;
	}
	for(i=0;i<12;i++)
	{
		new_cpu->ax[i] = 0;
	}
	
	for(j=0;!feof(fPtr);)
	{
		for(i=0;i<8;i++)      //因為代碼段code為char型數組
		{                     //是以一條指令需要占用四個數組空間
			ch = fgetc(fPtr); //每次讀取8位二進制值并将其轉換為char存入數組
			if(ch=='\n')
			{                 //如果讀取到換行符,則将其丢棄重新讀取 
				i--;
				continue;
			}
			temp = temp*2 + ch -'0'; //每次從檔案中讀到的其實是'1'或'0'對應的ASCII碼,要将其轉換為數值類型 
		}
		new_cpu->code_data[j++] = temp; //寫入代碼段 
		temp = 0;
	}
	new_cpu->code_data[j-1] = 0; //最後一次在代碼段寫入了EOF(即-1),是以要重新将其改寫為預設值0 
	return new_cpu;	
}

void process(cpuPtr cpu)
{
	short ir[4] = {0};  //拆解指令,分别為指令類型,操作位1,操作位2,立即數 
	/*循環,不斷取指令分析執行,直到遇到停機指令*/ 
	do{
		/*代碼段中每條指令占四個位元組,取出前兩個位元組并拼接寫入ir(指令寄存器)*/
		cpu->ax[10] = cpu->code_data[(cpu->ax[9])++]&0xff; 
		cpu->ax[10] = ((cpu->ax[10]<<8)&0xff00)+(cpu->code_data[(cpu->ax[9])++]&0xff);
		
		/*取出後兩個位元組并拼接,即為立即數,指派給 ir[3]*/ 
		ir[3] = cpu->code_data[cpu->ax[9]++]&0xff;
		ir[3] = ((ir[3]<<8)&0xff00)+(cpu->code_data[cpu->ax[9]++]&0xff);
		
		/*指令寄存器儲存16位指令,前8位對應操作類型,通過位運算取出指派給 ir[0]*/ 
		ir[0] = cpu->ax[10]>>8;
		
		/*指令寄存器9-12位、13-16位對應操作位1和操作位2,位運算取出指派給 ir[1]、ir[2]*/ 
		ir[1] = ((cpu->ax[10]<<8)>>12)&0xf;
		ir[2] = ((cpu->ax[10]<<12)>>12)&0xf;
		
		/*分析指令操作類型,調用對應函數*/ 
		switch(ir[0])
		{
			case 1: transfer(cpu,ir);break; //資料傳送指令 
			case 2:case 3:case 4:case 5: calculate(cpu,ir);break; //算數運算指令 
			case 6:case 7:case 8: logic(cpu,ir);break;        //邏輯運算指令 
			case 9: comparation(cpu,ir);break; //比較運算指令 
			case 10: skip(cpu,ir);break; //跳轉指令 
			case 11:case 12: ioput(cpu,ir);break; //輸入輸出指令 
		}
		
		/*每條指令執行完輸出各寄存器狀态*/ 
		output(cpu);
		
	}while(ir[0]);//ir[0]為0則為停機指令,跳出循環 
	
	/*最後輸出代碼段和資料段資訊*/ 
	end_output(cpu);
}

void output(cpuPtr cpu)
{
	printf("ip = %d\n",cpu->ax[9]);
	printf("flag = %d\n",cpu->ax[11]);
	printf("ir = %d\n",cpu->ax[10]);
	printf("ax1 = %d ax2 = %d ax3 = %d ax4 = %d\n",cpu->ax[1],cpu->ax[2],cpu->ax[3],cpu->ax[4]);
	printf("ax5 = %d ax6 = %d ax7 = %d ax8 = %d\n",cpu->ax[5],cpu->ax[6],cpu->ax[7],cpu->ax[8]);
}

void end_output(cpuPtr cpu)
{
	int temp;
	int i,j,k=0;
	printf("\ncodeSegment :\n");
	for(i=0;i<16;i++) //共輸出16行 
	{
		for(j=0;j<7;j++) //每行前7個數後要跟一個空格 
		{
			/*代碼段中每條指-令存為4個char,通過位運算将其轉換為32位整數并輸出*/ 
			temp = (cpu->code_data[k++]<<24)&0xff000000;
			temp += (cpu->code_data[k++]<<16)&0xff0000;
			temp += (cpu->code_data[k++]<<8)&0xff00;
			temp += cpu->code_data[k++]&0xff;
			printf("%d ",temp);
		}
		//每行最後一個數後要跟一個換行符 
		temp = (cpu->code_data[k++]<<24)&0xff000000;
		temp += (cpu->code_data[k++]<<16)&0xff0000;
		temp += (cpu->code_data[k++]<<8)&0xff00;
		temp += cpu->code_data[k++]&0xff;
		printf("%d\n",temp);
	}
	
	k=N;
	printf("\ndataSegment :\n");
	for(i=0;i<16;i++) //共輸出16行 
	{
		for(j=0;j<15;j++) //每行前15個數後要跟一個空格 
		{
			printf("%d ",bin(cpu,k)); //調用bin()将資料段中相鄰兩個char轉換為一個整數(short)輸出 
			k += 2;
		}
		//每行最後一個數後要跟一個換行符 
		printf("%d\n",bin(cpu,k));
		k += 2;
	}
}

short bin(cpuPtr cpu,int start)
{
	return (cpu->code_data[start]&0xff)*256+(cpu->code_data[start+1]&0xff); //通過位運算将兩個char合并成一個整數(short) 
}

/*将一個整數(short)拆分為兩個char并寫入資料段*/ 
void bin_to_char(cpuPtr cpu,int start,short number)
{
	cpu->code_data[start++] = (char)(number>>8); //強制類型轉換,short強制轉換為char是低位截斷 
	cpu->code_data[start] = (char)(number);
}

/*資料傳送指令,其實就是指派運算*/ 
void transfer(cpuPtr cpu,short ir[])
{
	if(!ir[2])
		cpu->ax[ir[1]] = ir[3]; //把立即數指派給資料寄存器 
	else if(ir[2]<=4)
		//把資料寄存器中的數指派給位址寄存器中位址指向的位置,即寫入資料段 
		bin_to_char(cpu,cpu->ax[ir[1]],cpu->ax[ir[2]]); 
	else
		//把位址寄存器中位址指向的位置中存放的數指派給資料寄存器 
		cpu->ax[ir[1]] = bin(cpu,cpu->ax[ir[2]]);
}

/*代數運算指令,加減乘除*/ 
void calculate(cpuPtr cpu,short ir[])
{
	if(ir[2]){
		//操作位1 (一定是資料寄存器)中的數與操作位2(一定是位址寄存器)指向的數進行運算 
		switch(ir[0])
			{
				case 2:cpu->ax[ir[1]] += bin(cpu,cpu->ax[ir[2]]);break; //加法 
				case 3:cpu->ax[ir[1]] -= bin(cpu,cpu->ax[ir[2]]);break; //減法 
				case 4:cpu->ax[ir[1]] *= bin(cpu,cpu->ax[ir[2]]);break; //乘法 
				case 5:cpu->ax[ir[1]] /= bin(cpu,cpu->ax[ir[2]]);break; //除法				
			}
	}else{
		//操作位1對應的寄存器(一定是資料寄存器)中的數與立即數進行運算
		switch(ir[0])
			{
				case 2:cpu->ax[ir[1]] += ir[3];break; //加法 
				case 3:cpu->ax[ir[1]] -= ir[3];break; //減法 
				case 4:cpu->ax[ir[1]] *= ir[3];break; //乘法 
				case 5:cpu->ax[ir[1]] /= ir[3];break; //除法 
			}
	}	
}

/*邏輯運算,與或非*/ 
void logic(cpuPtr cpu,short ir[])
{
	if(!ir[2]) //如果操作位2是0,則操作位1(一定是資料寄存器)與立即數運算 
	{
		switch(ir[0]) 
		{
			case 6: //邏輯與 
				cpu->ax[ir[1]] = cpu->ax[ir[1]]&&ir[3];break;
			case 7: //邏輯或 
				cpu->ax[ir[1]] = cpu->ax[ir[1]]||ir[3];break;
			case 8: //邏輯非 
				cpu->ax[ir[1]] = !cpu->ax[ir[1]];break;
		}
	}
	else  //操作位2不為0,則操作位1(一定是資料寄存器)中的數與操作位2(一定是位址寄存器)指向的數運算 
	{
		switch(ir[0])
		{
			case 6: //邏輯與 
				cpu->ax[ir[1]] = cpu->ax[ir[1]]&&bin(cpu,cpu->ax[ir[2]]);break;
			case 7: //邏輯或 
				cpu->ax[ir[1]] = cpu->ax[ir[1]]||bin(cpu,cpu->ax[ir[2]]);break;
			case 8: //邏輯非 
				bin_to_char(cpu,cpu->ax[ir[2]],!bin(cpu,cpu->ax[ir[2]]));break;
		}
	}
}

/*比較運算*/ 
void comparation(cpuPtr cpu,short ir[])
{
	if(!ir[2]){ 
		//操作位2為0,則操作位1(一定是資料寄存器)中的數與立即數比較 
		if(cpu->ax[ir[1]]>ir[3])
			cpu->ax[11] = 1;
		else if(cpu->ax[ir[1]]==ir[3])
			cpu->ax[11] = 0;
		else
			cpu->ax[11] = -1;	
	}else{ 
		//操作位2不為0,則操作位1(一定是資料寄存器)中的數與操作位2(一定是位址寄存器)指向的數比較 
		if(cpu->ax[ir[1]]>bin(cpu,cpu->ax[ir[2]]))
			cpu->ax[11] = 1;
		else if(cpu->ax[ir[1]]==bin(cpu,cpu->ax[ir[2]]))
			cpu->ax[11] = 0;
		else
			cpu->ax[11] = -1;
	}
}

/*跳轉指令*/ 
void skip(cpuPtr cpu,short ir[])
{
	switch(ir[2])
	{
		case 0:cpu->ax[9] += ir[3]-4;break; //操作位2為0,強制跳轉,更新程式計數器ip 
		case 1: //操作位2為1,如果标志寄存器flag為0才跳轉 
			if(cpu->ax[11]==0){
				cpu->ax[9] += ir[3]-4;
			}break;
		case 2: //操作位2為2,如果标志寄存器flag為1才跳轉 
			if(cpu->ax[11]==1){
				cpu->ax[9] += ir[3]-4;
			}break;
		case 3:	//操作位2為3,如果标志寄存器flag為-1才跳轉 
			if(cpu->ax[11]==-1){
				cpu->ax[9] += ir[3]-4;
			}break;
	}
}

/*輸入輸出指令*/ 
void ioput(cpuPtr cpu,short ir[])
{
	if(ir[0]==11) //task為11,對應輸入指令 
	{
		printf("in:\n");
		scanf("%hd",&cpu->ax[ir[1]]);
	}
	if(ir[0]==12) //task為12,對應輸出指令 
	{
		printf("out: ");
		printf("%d\n",cpu->ax[ir[1]]);
	}
}
           

五、後記

寫到這裡,算上代碼一萬六千字了。期末季的大晚上不睡覺不肝ddl跑來寫文章也是服了我自己。不過确實挺有成就感的。

作為一個藥學專業的外校學生,我這學期才開始學習C語言,北郵的計導大作業對我來說也确實太頂了。記得剛看到課題描述時我花了一兩個星期來了解課題的意思、了解馮諾依曼體系;記得我數次通宵修改代碼,隻為了用不同的思路來完成代碼實作課題;記得我花費了一個周末兩天的時間,為了debug幾乎想破了腦袋,看着OJ上40%,0%,0%,0%的資料一點一點變成100%,100%,100%,100%。這個過程着實艱難,全靠自己摸索的我差點自閉,然而當最後我看到四個綠色的100%時,那種成就感與滿足感太爽了。

感謝某魚,感謝北郵,讓我學到了這麼多東西!

繼續閱讀