找回密碼
 立即注冊

QQ登錄

只需一步,快速開始

搜索
查看: 2171|回復(fù): 0
打印 上一主題 下一主題
收起左側(cè)

32位和16位指令集模式自動切換機制

[復(fù)制鏈接]
跳轉(zhuǎn)到指定樓層
樓主
ID:105323 發(fā)表于 2016-3-4 14:21 | 只看該作者 回帖獎勵 |倒序瀏覽 |閱讀模式
    我們都知道MIPS架構(gòu)體系是32位精簡指令集(MIPS32),事實上MIPS在進入控制器市場時還推出了MIPS16e指令集模式,號稱能夠使編譯后的代碼減少30%左右,類似于ARM架構(gòu)中是arm32指令(32位)和thumb(16位)指令。內(nèi)存資源緊缺型系統(tǒng)一般會使用MIPS16e模式進行編譯,以縮減內(nèi)存使用量,達到降低成本的目的。
    ISAMODE是指令集運行模式的指示位,但這個比特位并不是固定在某個寄存器中。調(diào)試時可以發(fā)現(xiàn),該比特位對應(yīng)32位運行地址的最低比特位。為1時代表此時芯片運行的是MIPS16e模式,0即意味著MIPS32模式。指令運行過程中,兩種指令集的切換是自動、無縫地完成的。在ARM架構(gòu)中是arm指令和thumb指令切換。
    本文說明兩種指令模式的切換機制,并用實例來詳細說明兩者之間是如何進行切換的。一、必須跑MIPS32指令集模式的場景
這是MIPS體系結(jié)構(gòu)決定的:  
1. 中斷/異常。CPU響應(yīng)中斷/異常之后就自動進入mips32模式。
  2. “sync”指令必須在mips32模式下使用。
3. 協(xié)處理器cp0的讀寫必須在mips32模式下。
4. 因為性能的考慮,某些算法有時追求高效率,應(yīng)該運行在mips32模式下。


二、運行模式切換
1. 異常/中斷

    MIPS異常和中斷發(fā)生時會自動轉(zhuǎn)到32位模式,這時EPC寄存器記錄的是發(fā)生異常時的地址(該地址如果之前運行于MIPS16e模式,則是16位對齊,否則是32位對齊),其最低位就對應(yīng)ISAMODE bit。中斷/異常處理過程可以保存EPC并切換到16e模式運行。
   在中斷/異常返回時,即調(diào)用ERET指令時,可以返回到EPC中保存的地址,并把EPC的最低位賦值給ISAMODE,恢復(fù)到原來的運行模式。
    2. 函數(shù)調(diào)用和返回

    運行模式協(xié)同工作的原理主要是在調(diào)用時將代表當前運行模式的ISAMODE BIT和返回地址一起放到RA或者某個指定的寄存器。因為mips16e/32的返回地址至少是2字節(jié)對齊,所以其最低位一定是0,ISA MODE BIT占用bit 0不會造成返回地址混亂。
    在返回時,根據(jù)RA或者RX的bit 0恢復(fù)運行模式,接著返回。
    1)JAL
    這條指令調(diào)用后,代碼運行模式不變,而且其是在256M的范圍內(nèi)進行調(diào)用。在同樣的編譯模式下,調(diào)用函數(shù)時會產(chǎn)生JAL指令。
    2)JALX
    這條指令調(diào)用后,代碼運行模式反轉(zhuǎn),同樣是在256M的范圍內(nèi)進行調(diào)用。如果在16e的函數(shù)中調(diào)用32的函數(shù),編譯器就會產(chǎn)生JALX調(diào)用;同樣,如果在32的函數(shù)中調(diào)用16e的函數(shù),會產(chǎn)生JALX。
    3)JALR
    這條指令可以調(diào)用不同的運行模式的代碼,其可以在4G的范圍內(nèi)進行調(diào)用。RX保護目標地址的運行模式和地址內(nèi)容。
    4) JR
    利用這條指令返回,根據(jù)RA或者RX的bit0恢復(fù)運行模式,接著返回。
    3. 使用總結(jié)
    1) 雖然是用MIPS16E編譯,但代碼中同樣會出現(xiàn)32位擴展指令,上面這些跳轉(zhuǎn)指令和save 、restore等指令都是32位編碼的。CPU運行在16位模式時,如果碰到這些指令,也是先取16bit內(nèi)容,但譯碼時發(fā)現(xiàn)其是32位指令,即停止譯碼,并把后面16bit取進來一起執(zhí)行。其實16e模式的16bit指令在CPU內(nèi)部一樣會翻譯為32bit指令。
    2) 開發(fā)人員應(yīng)該明確自己負責(zé)開發(fā)的代碼以何種模式編譯,我們有兩種選擇決定函數(shù)的編譯模式。一種是命令行選項,一種是添加函數(shù)的屬性。
   編譯選項如下:
   GCC_MIPS16e = -g -c -G0 -Wall-Wno-unknown-pragmas $(INCLUDE) -msoft-float -fsigned-char -mtune=m4k-mips16e-nostdinc -nostdlib -fno-builtin $(OFORMAT) $(BUILD_MACRO)
   GCC_MIPS32 = -g -c -G0 -Wall-Wno-unknown-pragmas $(INCLUDE) -msoft-float -fsigned-char -mtune=m4k -mips32r2-nostdinc -nostdlib -fno-builtin $(OFORMAT) $(BUILD_MACRO)
   定義函數(shù)時添加屬性聲明__attribute__((mips16))是告訴編譯器以mips16e的模式去編譯該函數(shù)。__attribute__((nomips16))即表示以mips32的方式去編譯。對于C文件,我們一般用命令行選項去決定其使用何種模式進行編譯。但如果在一個文件中某個函數(shù)希望以另外的模式編譯時就要添加函數(shù)的屬性。這時命令行的編譯模式選項是不起作用的。
    匯編文件頭部一定要注明這個編譯屬性。Gcc命令行選項的mips16/mips32對匯編文件不起作用。
    3) 在調(diào)用外部函數(shù)時,一般要extern聲明被調(diào)用的函數(shù),這時不需說明該函數(shù)的編譯屬性,說明了也不起作用。鏈接階段會根據(jù)函數(shù)定義時的編譯屬性生成相應(yīng)的調(diào)用指令。

三、例證
    無論是同種模式的調(diào)用還是不同模式之間的調(diào)用,在256M空間內(nèi)(默認為near)一律編譯出來為jal,在鏈接階段才確定是否需要修改為jalx。在超過256M的空間內(nèi)(聲明被調(diào)用函數(shù)為far)一律編譯出來是jalr或者jalrc,在鏈接階段才確定是否要修改指令。

1. mips32模式函數(shù)調(diào)用(mips16e或者mips32)模式函數(shù)

1) 使用命令行選項CC_OPTS_BASE進行編譯。
2) 舉例代碼:
//該函數(shù)在不同的文件中定義,鏈接地址在256M范圍內(nèi),并以mips16e模式編譯。
extern int test_16_fun_near(int a);
//該函數(shù)在不同的文件中定義,鏈接地址超過256M范圍內(nèi),并以mips16e模式編譯。
extern int test_16_fun_far(int a) __attribute__((far));
//該函數(shù)在本文件中定義,但以mips16e模式編譯。
int __attribute__((mips16))test_16_fun_inline(int a);
//該函數(shù)在本文件中定義,并以mips32編譯。
int test_32_fun_inline(int a);
//調(diào)用函數(shù)示例
int test_32_fun(int a)
{
   int b,c,d,e;
    b= test_32_fun_inline(88);
    d= test_16_fun_inline(77);
    c= test_16_fun_near(99);
    e= test_16_fun_far(66);
   return a + 100 + b + d;
}
3) 編譯及鏈接如下:
可見在編譯階段都是產(chǎn)生jal和jalr 指令,在鏈接階段才修正相關(guān)指令。
調(diào)用test_32_fun_inline最終生成jal指令,ISA MODE不變。
調(diào)用test_16_fun_inline最終生成jalx指令,ISA MODE反轉(zhuǎn)。
調(diào)用test_16_fun_near最終生成jalx指令,ISA MODE反轉(zhuǎn)
調(diào)用test_16_fun_far最終生成jalr指令,v0寄存器的值是
0x80000000+0x61(97是十進制)= 0x80000061,而test_16_fun_far的實際地址是0x80000060,bit0代表test_16_fun_far運行的是1,即mips16e的模式。
2. mips16e模式函數(shù)調(diào)用(mips16e或者mips32)模式函數(shù)
1)使用命令行選項CC_OPTS_BASE_16進行編譯.
2)舉例代碼:
//另外文件定義,32編譯,超過256M。
extern int test_32_fun_far(int a)__attribute__((far));
//另外文件定義,32編譯,256M范圍內(nèi)。
extern int test_32_fun_near(int a);
//本文件定義,但以32編譯。
int __attribute__((nomips16))test_32_fun_inline_1(int a) ;
//本文定義,16e編譯。
int test_16_fun_inline(int a);
//調(diào)用示例代碼:
int test_16_main(int a)
{
   int b,c,d,e;
    b= test_16_fun_inline(88);
    d= test_32_fun_inline_1(77);
    c= test_32_fun_near(99);
    e= test_32_fun_far(66);
   return a + 100 + b + d;
}
3) 編譯和鏈接如下:

可見在編譯階段都是產(chǎn)生jal和jalrc 指令,在鏈接階段才修正相關(guān)指令。

調(diào)用test_16_fun_inline最終生成jal指令,ISA MODE不變。
調(diào)用test_32_fun_inline_1最終生成jalx指令,ISA MODE反轉(zhuǎn)。
調(diào)用test_32_fun_near最終生成jalx指令,ISA MODE反轉(zhuǎn)
調(diào)用test_32_fun_far最終生成jalrc,其寄存器所放的值是間接跳轉(zhuǎn)的。這條指令是根據(jù)對應(yīng)的寄存器的值 來進行跳轉(zhuǎn)的。v0的值是0x80000284內(nèi)存的值,即0x80000168。其bit0是0,所以jalrc到該地址后,其ISA MODE 為0,即從16e轉(zhuǎn)變?yōu)?2模式。

對間接跳轉(zhuǎn),我們進行特別的分析:
mips32調(diào)用mips16e:調(diào)用test_16_fun_far時,jalr v0前的指令是lui v0, 0x8000;
add v0,v0,XX。即對v0賦值是使用lui這種利用直接數(shù)進行賦值的指令。編譯階段是add v0,v0,0;鏈接階段 是add v0,v0,97進行了一次重定位。
mips16調(diào)用mips32:調(diào)用test_32_fun_far時,jalrc v0 前的指令是lw v0, 84 (b208),而且其編譯和鏈接 階段的指令碼都沒有變,變的是0x80000284這個內(nèi)存。編譯階段是全0,鏈接后是test_32_fun_far的實際地址0x 80000168。Lw指令跟lui指令不同,其是取內(nèi)存的值到v0,而不是把地址值直接賦給v0.
為什么在0x80000266的地方的指令碼為b208,取的內(nèi)存卻是0x80000284呢?

0x80000284 = 0x80000266 & 0xfffffffc0 + 8 << 2

分享到:  QQ好友和群QQ好友和群 QQ空間QQ空間 騰訊微博騰訊微博 騰訊朋友騰訊朋友
收藏收藏 分享淘帖 頂 踩
回復(fù)

使用道具 舉報

您需要登錄后才可以回帖 登錄 | 立即注冊

本版積分規(guī)則

手機版|小黑屋|51黑電子論壇 |51黑電子論壇6群 QQ 管理員QQ:125739409;技術(shù)交流QQ群281945664

Powered by 單片機教程網(wǎng)

快速回復(fù) 返回頂部 返回列表