計算機組成原理Ⅱ課程設計 PA2.1


PA 2.1

目錄

@

思考題

  1. 增加了多少?

    操作碼

    源操作數1 或/和 源操作數2(立即數、寄存器編號、存儲地址)

    目的操作數地址(寄存器編號、存儲地址)

  2. 是什么類型?

    opcode_table每個表項的類型是opcode_entry

    opcode_entry.width記錄了操作數長度信息

    opcode_entry.decode是一個函數指針,記錄了譯碼函數

    opcode_entry.execute是一個函數指針,記錄了執行函數

  3. 操作數結構體的實現

    typedef struct {
      uint32_t type; //操作數的類型
      int width; //操作數的寬度
      union {
        uint32_t reg; //寄存器編號
        rtlreg_t addr; //操作數的地址
        uint32_t imm; //立即數
        int32_t simm; //帶符號立即數
      };
      rtlreg_t val;//操作數的值
      char str[OP_STR_SIZE];//指令的解釋
    } Operand;
    

    一般在譯碼過程中,都會根據操作碼的類型先行標記操作數的類型,再根據操作數類型對共同體內的對應變量賦值。val一般存儲該操作數的值,str記錄了要顯示的指令信息

  4. 復現宏定義

    • void exec_mov (vaddr_t *eip)

      #define make_EHelper(name) void concat(exec_, name) (vaddr_t *eip)
      

      concat連接為exec_name的形式

    • void exec_push (vaddr_t *eip)

      同上

    • void decode_I2r (vaddr_t *eip)

      #define make_DHelper(name) void concat(decode_, name) (vaddr_t *eip)
      

      concat連接為decode_name的形式

    • {decode_I2a, exec_cmp, 0}

      #define IDEXW(id, ex, w)   {concat(decode_, id), concat(exec_, ex), w}
      #define IDEX(id, ex)       IDEXW(id, ex, 0)
      
    • {((void *)0), exec_nop, 0}

      #define NULL ((void *)0)
      #define EXW(ex, w)  {NULL, concat(exec_, ex), w}
      #define EX(ex)  EXW(ex, 0)
      
    static inline void rtl_and (rtlreg_t* dest, const rtlreg_t* src1, const rtlreg_t* src2) { 	  *dest = ((*src1) & (*src2)); 
    } 
    static inline void rtl_andi (rtlreg_t* dest, const rtlreg_t* src1, int imm) { 
    	*dest = ((*src1) & (imm)); 
    }
    
    #define make_rtl_arith_logic(name) \
      static inline void concat(rtl_, name) (rtlreg_t* dest, const rtlreg_t* src1, const rtlreg_t* src2) { \
        *dest = concat(c_, name) (*src1, *src2); \
      } \
      static inline void concat3(rtl_, name, i) (rtlreg_t* dest, const rtlreg_t* src1, int imm) { \
        *dest = concat(c_, name) (*src1, imm); \
      }
    
  5. ⽴即數背后的故事

    要注意機器是大端機還是小端機。

    因為大小端機字節保存的順序相反。

    使用前先判斷大小端機,根據類型使用不同的字節讀取方式。

  6. 神奇的 eflags

    溢出表示超過最大可表示范圍。

    不能替換,進位不能代表溢出。

    OF=Cn⊕Cn-1。

  7. git branchgit log 截圖(最新的,⼀張即可)

實驗內容

實現標志寄存器


1.實現eflags

查詢i386手冊可知,eflags寄存器結構如下,根據講義介紹,我們只需實現CFZFSFIFOF

通過位域與無名位域適當組合,來組織出eflags的結構

其中value是用來給eflags賦初值時使用

union {
    uint32_t value;//用於賦初值
    struct  {
      uint32_t CF:1;
      uint32_t :5;//無名位域
      uint32_t ZF:1;
      uint32_t SF:1;
      uint32_t :1;
      uint32_t IF:1;
      uint32_t :1;
      uint32_t OF:1;
    };
  }eflags;

2.eflags設置初值

查詢i386手冊,發現Chapter 10 Initialization第十章與初始化相關。通過閱讀可知需要把eflags初始化為0x2

ics2020\nemu\src\monitor\monitor.c找到restart()函數,並在其中添加

cpu.eflags.value=0x2;//eflags賦初始值0x2

3.實現所有指令對標志位的設置

已有6條指令中,通過查詢i386手冊,可知subxor需要修改標志位

對於sub,模仿sbb,除了不需要減去CF外,其余的均相同

  //更新ZF,SF  rtl_update_ZFSF(&t2, id_dest->width);  //更新CF  rtl_sltu(&t0, &id_dest->val, &t2);  rtl_or(&t0, &t3, &t0);  rtl_set_CF(&t0);  //更新OF  rtl_xor(&t0, &id_dest->val, &id_src->val);  rtl_xor(&t1, &id_dest->val, &t2);  rtl_and(&t0, &t0, &t1);  rtl_msb(&t0, &t0, id_dest->width);  rtl_set_OF(&t0);

對於xor,需要更新SF ZFOF=0CF=0

  rtl_li(&t0,0);  rtl_set_CF(&t0);//CF=0  rtl_set_OF(&t0);//OF=0  rtl_update_ZFSF(&id_dest->val,id_dest->width);//更新ZF、SF

實現所有RTL指令


1.rtl_mv

按照注釋,簡單賦值即可

static inline void rtl_mv(rtlreg_t* dest, const rtlreg_t *src1)
    // dest <- src1  
	*dest = *src1;
}

2.rtl_not

按照注釋,簡單取非即可

static inline void rtl_not(rtlreg_t* dest) 
{  
	// dest <- ~dest  
	*dest = ~ (*dest);
}

3.rtl_sext

函數參數中有width,考慮要對不同大小的數進行符號擴展。我們可以通過帶符號整數的算數右移來實現符號擴展,則先左移一定位數使得要求符號處於最高位,再算數右移回原位置,即可實現符號擴展。

static inline void rtl_sext(rtlreg_t* dest, const rtlreg_t* src1, int width) {
  // dest <- signext(src1[(width * 8 - 1) .. 0])
  int32_t t = *src1;//轉為帶符號整數
  if (width == 1) {//若為1字節
    *dest = (t<<24)>>24;//符號擴展
  }
  else if (width == 2) {//2字節
    *dest = (t<<16)>>16;
  }
  else if (width ==4) {//4字節
    *dest = t;
  }
  else {
    assert(0);
  }
}

4.rtl_push

根據注釋,使用rtl實現取寄存器值、減法、存儲等功能

static inline void rtl_push(const rtlreg_t* src1) {
  // esp <- esp - 4
  // M[esp] <- src1
  rtl_lr(&t0,R_ESP,4);//獲取寄存器ESP的值
  rtl_subi(&t0,&t0,4);//ESP-4
  rtl_sr(R_ESP,4,&t0);//存儲ESP
  rtl_sm(&t0,4,src1);//存儲要push的內容
}

5.rtl_pop

實現思路與rtl_push基本相同

static inline void rtl_pop(rtlreg_t* dest) {
  // dest <- M[esp]
  // esp <- esp + 4
  rtl_lr(&t0,R_ESP,4);//獲取寄存器ESP的值
  rtl_lm(dest,&t0,4);//獲取ESP指定位置的內存的值
  rtl_addi(&t0,&t0,4);//ESP+4
  rtl_sr(R_ESP,4,&t0);//存儲ESP
}

6.rtl_eq0

判斷src1是否為0,若是,則給dest賦值1,否則賦值0

static inline void rtl_eq0(rtlreg_t* dest, const rtlreg_t* src1) {
  // dest <- (src1 == 0 ? 1 : 0)
  *dest = *src1 == 0 ? 1 : 0;
}

7.rtl_eqi

判斷操作數src1與立即數imm是否相等。若相等,則給dest賦值1,否則賦值0

static inline void rtl_eqi(rtlreg_t* dest, const rtlreg_t* src1, int imm) {
  // dest <- (src1 == imm ? 1 : 0)
  *dest = *src1 == imm ? 1 : 0;
}

8.rtl_neq0

判斷操作數是否為0

static inline void rtl_neq0(rtlreg_t* dest, const rtlreg_t* src1) {
  // dest <- (src1 != 0 ? 1 : 0)
  *dest = *src1 != 0 ? 1 : 0;
}

9.rtl_msb

獲取最高有效位,因為src1是無符號整數,可以通過邏輯右移獲取其最高有效位

static inline void rtl_msb(rtlreg_t* dest, const rtlreg_t* src1, int width) {
  // dest <- src1[width * 8 - 1]
  rtl_shri(dest, src1, width*8 - 1);//邏輯右移
}

10.make_rtl_setget_eflags

此宏定義實現了rtl_set_XF()rtl_get_XF()的功能,我們只需要實現對eflags寄存器相應位置的存取即可

#define make_rtl_setget_eflags(f) \
  static inline void concat(rtl_set_, f) (const rtlreg_t* src) { \
    cpu.eflags.f = *src; \
  } \
  static inline void concat(rtl_get_, f) (rtlreg_t* dest) { \
    *dest = cpu.eflags.f; \
  }

11.rtl_update_ZF

更新ZF,根據注釋,我們只需判斷從width*8-1...0這些位是否全為0。我們可以通過邏輯左移把原來width*8-1移到最高位,末尾補0。若移位后的數值為0,則ZF=1否則ZF=0

static inline void rtl_update_ZF(const rtlreg_t* result, int width) {
  // eflags.ZF <- is_zero(result[width * 8 - 1 .. 0])
  t0 = *result;
  rtl_shli(&t0,&t0,32-width*8);//邏輯左移,去掉無用位
  if (t0 == 0)//若整體為0,即所有位都是0
    t1=1;
  else
    t1=0;
  rtl_set_ZF(&t1);//設置ZF
}

12.rtl_update_SF

更新SF,獲取result符號即可。

static inline void rtl_update_SF(const rtlreg_t* result, int width) {
  // eflags.SF <- is_sign(result[width * 8 - 1 .. 0])
  rtl_msb(&t0, result, width);//獲取result符號
  rtl_set_SF(&t0);//設置SF
}

實現6條x86指令


1.call

運行dummy發現e8指令沒有實現,查詢i386手冊可知需要實現callA表明操作數是一個立即數,v表明操作數大小可能為2或4字節

據此,我們可以選定譯碼函數make_DHelper(J),但在此之前,我們還需要實現make_DopHelper(SI),其功能是譯碼一個帶符號立即數。

op->simm=instr_fetch(eip,op->width);//讀取指定長度信息

譯碼函數make_DHelper(J)功能是獲取一個帶符號立即數,並decoding.jmp_eip 指向id_dest->simm + *eip的位置

實現執行函數make_EHelper(call),由於譯碼函數已經完成了跳轉位置的設定,這里我們只需標志跳轉並入棧eip即可

make_EHelper(call) {
  decoding.is_jmp=1;//標識跳轉
  rtl_push(eip);//入棧eip
  print_asm("call %x", decoding.jmp_eip);
}

最后填寫opcode_table

IDEXW(J,call,4)

執行結果如下

2.push

運行dummy發現55指令沒有實現,查詢i386手冊可知需要實現push

選取譯碼函數make_DHelper(r),其功能是讀取操作碼中的寄存器信息。

選取執行函數make_EHelper(push)

使用rtl_push指令把取到的操作數入棧

make_EHelper(push) {
  rtl_push(&id_dest->val);
  print_asm_template1(push);
}

最后填寫opcode_table

IDEX(r,push)

執行結果如下

3.pop

運行dummy發現5d指令沒有實現,查詢i386手冊可知需要實現pop

選取譯碼函數make_DHelper(r),其功能是讀取操作碼中的寄存器信息。

選取執行函數make_EHelper(pop)

使用rtl_pop指令把棧頂內容存入id_dest

make_EHelper(pop) {
  rtl_pop(&id_dest->val);
  operand_write(id_dest, &id_dest->val);
  print_asm_template1(pop);
}

最后填寫opcode_table

IDEX(r,pop)

執行結果如下

4.sub

運行dummy發現83指令沒有實現,查詢反匯編結果可知需要實現sub

發現opcode_table中要使用了grp1,查表可知grp1[5]應填寫執行函數make_EHelper(sub)

選取執行函數make_EHelper(sub),若src的位數少於dest,則需要位擴展,相減之后的結果存入dest即可

make_EHelper(sub) {
  rtl_sub(&t2, &id_dest->val, &id_src->val);
  rtl_sltu(&t3, &id_dest->val, &t2);//t3記錄是否借位,0表示借位
  
  operand_write(id_dest, &t2);//最終結果寫入對應寄存器或內存

  rtl_update_ZFSF(&t2, id_dest->width);//更新ZF,SF

  rtl_sltu(&t0, &id_dest->val, &t2);//與減去借位后再比
  rtl_or(&t0, &t3, &t0);
  rtl_set_CF(&t0);

  rtl_xor(&t0, &id_dest->val, &id_src->val);
  rtl_xor(&t1, &id_dest->val, &t2);
  rtl_and(&t0, &t0, &t1);
  rtl_msb(&t0, &t0, id_dest->width);
  rtl_set_OF(&t0);

  print_asm_template2(sub);
}

填寫gpr1

make_group(gp1,
    EMPTY, EMPTY, EMPTY, EMPTY,
    EMPTY, EX(sub), EMPTY, EMPTY)

執行結果如下

5.xor

運行dummy發現31指令沒有實現,查詢i386手冊可知需要實現xor

選取譯碼函數make_DHelper(G2E) Ev <- Gv

選取執行函數make_EHelper(xor),只需要把兩個源操作數異或后的結果存入目的操作數即可。

make_EHelper(xor) {

  rtl_xor(&id_dest->val,&id_src->val,&id_src2->val);//異或
  operand_write(id_dest,&id_dest->val);//賦值

  rtl_li(&t0,0);
  rtl_set_CF(&t0);//CF=0
  rtl_set_OF(&t0);//OF=0
  rtl_update_ZFSF(&id_dest->val,id_dest->width);//更新ZF、SF

  print_asm_template2(xor);
}

最后填寫opcode_table

IDEX(G2E,xor)

執行結果如下

6.ret

運行dummy發現c3指令沒有實現,查詢i386手冊可知需要實現ret

由上圖可知ret無譯碼函數

選取make_EHelper(ret)為執行函數,由於需要跳轉回上次call指令的下條指令處,需要設置跳轉標志,並出把要跳轉的位置出棧賦給decoding.jmp_eip

make_EHelper(ret) {
  decoding.is_jmp=1;//設置跳轉
  rtl_pop(&decoding.jmp_eip);//獲取跳轉位置
  print_asm("ret");
}

最后填寫opcode_table

EX(ret)

執行結果如下

成功運行dummy


實現Diff-test


nemu/include/common.h中定義宏

#define DIFF_TEST

nemu/src/monitor/diff-test/diff-test.c中修改difftest_step(),實現寄存器的對比。

先定義一個宏定義,用來判斷r.regcpu.reg的內容是否相等,若不相等,則令diff=false

#define test_reg(reg) \
	if (r.reg != cpu.reg) { \
	    diff = true; \
	    Log("reg error NEMU.reg=0x%08x QEMU.reg=0x%08x\n",cpu.reg,r.reg); \
  }

difftest_step()添加下面代碼,實現對8個通用寄存器以及eip的檢測

  test_reg(eax);
  test_reg(ecx);
  test_reg(edx);
  test_reg(ebx);
  test_reg(ebp);
  test_reg(esp);
  test_reg(edi);
  test_reg(esi);
  test_reg(eip);

開啟diff-test,成功運行dummy


免責聲明!

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



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