<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > 跟我寫ARM處理器之二:主體結(jié)構(gòu)的確定

          跟我寫ARM處理器之二:主體結(jié)構(gòu)的確定

          作者: 時(shí)間:2016-11-10 來源:網(wǎng)絡(luò) 收藏
          好了,上一節(jié)定義了端口,基本功能大慨大家已經(jīng)了然于胸了,現(xiàn)在來確定一下主體結(jié)構(gòu)。我舉幾個(gè)指令執(zhí)行的例子吧。

          第一個(gè)是MLA R1,R2,R3,R0。它的意思是:R1=R2*R3 + R0。如果我們要實(shí)現(xiàn)這一條指令的話,一個(gè)32×32的乘法器需要,一個(gè)32+32的加法器是跑不了的?,F(xiàn)在定義幾個(gè)節(jié)點(diǎn):Rm = R2; Rs=R3; sec_operand(第二操作數(shù)的意思)=mult_rm_rs[31:0](mult_rm_rs的低32位);Rn=R0;則結(jié)果等于:Rn + sec_operand。

          本文引用地址:http://www.ex-cimer.com/article/201611/317304.htm

          第二個(gè)是:SUB R1,R0, R2, LSL #2。它的意思是:R1=R0 - R2<<2??戳宋仪懊嫖恼碌闹?,這個(gè)指令同樣可以像前面一樣套入:Rm=R2; Rs=32b100; sec_operand=mult_rm_rs[31:0];Rn=R0;結(jié)果等于:Rn - sec_operand。

          第三個(gè)是:LDR R1,[R0,R2,LSR #2]!。這是一條取RAM的數(shù)據(jù)進(jìn)入寄存器的指令,取地址是:R0+R2>>2。并把取地址保存回R0?,F(xiàn)在比較難計(jì)算的是: R0+R2>>2。但是這個(gè)同樣也可以往前兩個(gè)模式一樣靠:Rm=R2; Rs=32b0100_0000_0000_0000_0000_0000_0000_0000,那么sec_operand = mult_rm_rs[63:32]正好等于:R2>>2。如果Rn=R0,取地址就等于:Rn+sec_operand。這個(gè)地址還要送入R0中。

          看到這,大家明白了本核的核心結(jié)構(gòu)了吧。網(wǎng)友先別贊我眼光如炬,目光如神,一眼看出核心所在。實(shí)際上我在寫第一版的時(shí)候,絕沒想到把移位交給乘法器來完成,也是傻傻地參考別人文檔寫了一個(gè)桶形移位器。但后來靈光一現(xiàn),覺得既然乘法器避免不了,如果只讓他在MUL指令的時(shí)候使用,其他指令的時(shí)候閑著,那多么沒意思呀。這樣乘法器復(fù)用起來,讓它參與了大部分指令運(yùn)算。

          好了,我們要做的事是這樣的。指令到來,準(zhǔn)備Rm, Rs, Rn,為生成sec_operand產(chǎn)生控制信號(hào),決定Rn和sec_operand之間是加還是減,那么最后生成的結(jié)果要么送入寄存器組,要么作為地址參與讀寫操作。就這么簡單!

          前面的這一套完成了,我想ARM核也就成功了大半了。

          上面解決了做什么的問題,隨之而來的是怎么做的問題。可能大家首先想到的是三級(jí)流水線。為什么是三級(jí)呢?為什么不是兩級(jí)呢?兩級(jí)有什么不好?我告訴你們,兩級(jí)同樣可以,無非是關(guān)鍵路徑長一點(diǎn)。我接下來,就要做兩級(jí),沒有什么能束縛我們!實(shí)際上,很多項(xiàng)目用不到30、40MHz的速度,10M,20M也是可以接受,100ns,50ns內(nèi),我那一套乘加結(jié)構(gòu)同樣能滿足??谡f無憑,看看我代碼中是如何生成:Rm,Rs, sec_operand,Rn的:

          注:以下非正式代碼,講解舉例所用

          /*

          always @ ( * )

          if ( code_is_ldrh1|code_is_ldrsb1|code_is_ldrsh1 )

          code_rm ={code[11:7],code[3:0]};

          else if ( code_is_b )

          code_rm ={{6{code[23]}},code[23:0],2b0};

          else if ( code_is_ldm )

          case( code[24:23] )

          2d0 : code_rm ={(code_sum_m - 1b1),2b0};

          2d1 : code_rm =0;

          2d2 : code_rm ={code_sum_m,2b0};

          2d3 : code_rm =3b100;

          endcase

          else if ( code_is_swp )

          code_rm =0;

          else if ( code_is_ldr0 )

          code_rm =code[11:0];

          else if ( code_is_msr1|code_is_dp2 )

          code_rm =code[7:0];

          else if ( code_is_multl & code[22] & code_rma[31] )

          code_rm =~code_rma + 1b1;

          else if ( ( (code[6:5]==2b10) & code_rma[31] ) & (code_is_dp0|code_is_dp1|code_is_ldr1))

          code_rm =~code_rma;

          else

          code_rm =code_rma;

          always @ ( * )

          case ( code[3:0] )

          4h0 : code_rma =r0;

          4h1 : code_rma =r1;

          4h2 : code_rma =r2;

          4h3 : code_rma =r3;

          4h4 : code_rma =r4;

          4h5 : code_rma =r5;

          4h6 : code_rma =r6;

          4h7 : code_rma =r7;

          4h8 : code_rma =r8;

          4h9 : code_rma =r9;

          4ha : code_rma =ra;

          4hb : code_rma =rb;

          4hc : code_rma =rc;

          4hd : code_rma =rd;

          4he : code_rma =re;

          4hf : code_rma =rf;

          endcase

          */

          我有if else這個(gè)法寶,你不管來什么指令,我都給你準(zhǔn)備好Rm。這就像一臺(tái)脫粒機(jī),你只要在送貨口送東西即可。你送麥子脫麥子,你送玉米脫玉米。你的Rm來自于寄存器組,那好我用code_rma來給你選中,送入Rm這個(gè)送貨口。你的Rm來自代碼,就是一套立即數(shù),那我就把code[11:0]送入Rm,下面的程式有了正確的輸入,你只要把最后的正確結(jié)果,送給寄存器組即可。

          再看看Rs的生成:

          注:以下非正式代碼,講解舉例所用

          /*

          always @ ( * )

          if ( code_is_dp0|code_is_ldr1 )

          code_rot_num =( code[6:5] == 2b00 ) ? code[11:7] : ( ~code[11:7]+1b1 );

          else if ( code_is_dp1 )

          code_rot_num =( code[6:5] == 2b00 ) ? code_rsa[4:0] : ( ~code_rsa[4:0]+1b1 );

          else if ( code_is_msr1|code_is_dp2 )

          code_rot_num ={ (~code[11:8]+1b1),1b0 };

          else

          code_rot_num =5b0;

          always @ ( * )

          if ( code_is_multl )

          if ( code[22] & code_rsa[31] )

          code_rs =~code_rsa + 1b1;

          else

          code_rs =code_rsa;

          else if ( code_is_mult )

          code_rs =code_rsa;

          else begin

          code_rs =32b0;

          code_rs[code_rot_num] = 1b1;

          end

          always @ ( * )

          case ( code[11:8] )

          4h0 : code_rsa =r0;

          4h1 : code_rsa =r1;

          4h2 : code_rsa =r2;

          4h3 : code_rsa =r3;

          4h4 : code_rsa =r4;

          4h5 : code_rsa =r5;

          4h6 : code_rsa =r6;

          4h7 : code_rsa =r7;

          4h8 : code_rsa =r8;

          4h9 : code_rsa =r9;

          4ha : code_rsa =ra;

          4hb : code_rsa =rb;

          4hc : code_rsa =rc;

          4hd : code_rsa =rd;

          4he : code_rsa =re;

          4hf : code_rsa =rf;

          endcase

          */

          Sec_operand的例子就不用舉了吧,無非是根據(jù)指令選擇符合該指令的要求,來送給下一級(jí)的加/減法器。

          所以說,這樣的兩級(jí)流水線我們同樣可以完成?,F(xiàn)在使用三級(jí)流水線,關(guān)鍵路徑是26ns。如果使用兩級(jí)流水線,絕對在50 ns以內(nèi)。工作在20MHz的ARM,同樣也是受低功耗用戶們歡迎的。有興趣的,在看完我的文章后,把ARM核改造成兩級(jí)流水線。

          現(xiàn)在要轉(zhuǎn)換一個(gè)觀念。以前的說法:第一級(jí)取代碼;第二級(jí)解釋代碼,第三級(jí)執(zhí)行代碼?,F(xiàn)在要轉(zhuǎn)換過來,只有兩級(jí),第一級(jí):取代碼;第二級(jí)執(zhí)行代碼。而現(xiàn)在我做成第三級(jí),是因?yàn)橐患?jí)執(zhí)行不完,所以要分兩級(jí)執(zhí)行。所以是:第一級(jí)取代碼;第二級(jí)執(zhí)行代碼階段一(主要是乘法);第三級(jí)執(zhí)行代碼階段二(主要是加/減法)。

          也許有人要問,那解釋代碼為什么不安排一級(jí)?是因?yàn)槲矣X得解釋代碼太簡單,根本不需要安排一級(jí),這一點(diǎn),我在下一節(jié)會(huì)講到。

          既然這個(gè)核是三級(jí)流水線,還是從三級(jí)流水線講起。我把三級(jí)流水線的每一級(jí)給了一個(gè)標(biāo)志信號(hào),分別是:rom_en, code_flag, cmd_flag。rom_en對應(yīng)第一級(jí)取代碼,如果rom_en==1b1表示需要取代碼,那這個(gè)代碼其實(shí)還處在ROM內(nèi),我們命名為“胎兒”;如果code_flag==1b1表示對應(yīng)的code處于執(zhí)行階段一,可以命名為“嬰兒”;如果cmd_flag==1b1,表示對應(yīng)的code處于執(zhí)行階段二,命名為“小孩”。當(dāng)這個(gè)指令最終執(zhí)行結(jié)束,可以認(rèn)為它死去了,命名為“幽靈”。

          rom_encode_flagcmd_flag

          -----------------

          |胎兒|嬰兒小孩-->幽靈

          -----------------

          現(xiàn)在,我們模擬一下這個(gè)執(zhí)行過程吧。一般ROM里面從0開始的前幾條指令都是跳轉(zhuǎn)指令,以hello這個(gè)例程為例,存放的是:LDR PC,[PC,#0x0018];連續(xù)五條都是這樣的。

          剛上電時(shí),rom_en==1b1,表示要取number 0號(hào)指令:

          rom_en==1b1code_flagcmd_flag

          (addr=0)

          -----------------

          |胎兒|嬰兒小孩-->幽靈

          -----------------

          LDR PC,[PC,#0x0018]

          第一個(gè)clock后;第一條指令LDR PC,[PC,#0x0018]到了嬰兒階段。

          rom_en==1b1code_flagcmd_flag

          (addr=4)

          -----------------

          |胎兒|嬰兒小孩-->幽靈

          -----------------

          LDR PC,[PC,#0x0018]LDR PC,[PC,#0x0018]

          第二個(gè)clock后,第一條指令LDR PC,[PC,#0x0018]到了小孩階段。

          rom_en==1b1code_flagcmd_flag

          (addr=8)

          -----------------

          |胎兒|嬰兒小孩-->幽靈

          -----------------

          (addr=8)(addr=4)(addr=0)

          LDR PC,[PC,#0x0018]LDR PC,[PC,#0x0018]LDR PC,[PC,#0x0018]

          當(dāng)“小孩”== LDR PC,[PC,#0x0018]時(shí),不能再取addr==8的指令了。因?yàn)閍ddr=0時(shí)的LDR PC,[PC,#0x0018]更改了PC的值,不僅不能取新的code,連處于嬰兒階段的code也不能執(zhí)行了。如果執(zhí)行的話,那就是錯(cuò)誤執(zhí)行。為了避免addr=4的LDR PC,[PC,#0x0018]執(zhí)行,我們可以給每一個(gè)階段打一個(gè)標(biāo)簽tag,比如code_flag對應(yīng)嬰兒,cmd_flag對應(yīng)小孩。只有在cmd_flag==1b1時(shí),指令才執(zhí)行。如下圖所示。

          rom_en==1b0code_flagcmd_flag

          (addr=8)0-->0 -->

          -----------------

          |胎兒|嬰兒小孩-->幽靈

          -----------------

          (addr=8)(addr=4)(addr=0)

          LDR PC,[PC,#0x0018]LDR PC,[PC,#0x0018]LDR PC,[PC,#0x0018]

          (修改PC)

          發(fā)出讀指令

          一旦有修改PC,那么rom_en立即賦值為1b0。code_flag, cmd_flag在下一個(gè)時(shí)鐘賦給1b0。表示在下一個(gè)時(shí)鐘“嬰兒”和“小孩”都是非法的,不能執(zhí)行。但是新的PC值不是立即得到的,因?yàn)長DR指令是要從RAM取數(shù)據(jù),在小孩階段只能發(fā)出讀指令,在一個(gè)時(shí)鐘,新的PC值才出現(xiàn)在ram_rdata,但還沒有出現(xiàn)在R15里面,所以要等一個(gè)時(shí)鐘。

          rom_en==1b0code_flag==1b0cmd_flag==1b0

          (addr=8)

          -----------------

          |胎兒|嬰兒小孩-->幽靈

          -----------------

          (addr=8)(addr=8)(addr=4)(addr=0 )

          XLDR PC,[PC,#0x0018]LDR PC,[PC,#0x0018]LDR PC,[PC,#0x0018]

          ram_rdata=NEW PC

          在空閑的這個(gè)周期內(nèi),為了讓指令不執(zhí)行,只要賦值:rom_en, code_flag, cmd_flag為1b0就達(dá)到目的了。

          rom_en, code_flag, cmd_flag在一般情況下都是1b1,但是如果PC值一改變,那么就需要同時(shí)被賦值給1b0。不過rom_en和code_flag,cmd_flag有區(qū)別: rom_en是立即生效,code_flag/cmd_flag要在下一個(gè)時(shí)鐘生效。rom_en下一個(gè)時(shí)鐘是要有效的,因?yàn)橐x新的PC值。

          改變PC有三種情況:

          1,中斷發(fā)生:我們命名為:int_all。只要中斷發(fā)生,PC要么等于0,4,8,10,1C等等。

          2,從寄存器里給PC賦值:一般情況是:MOV PC,R0。在小孩階段,已經(jīng)可以給出新的PC值了,這個(gè)和中斷類似。我們命名為:to_rf_vld。

          3,從RAM里面取值給PC賦值:一般是LDR PC [PC,#0x0018],那么在小孩階段,發(fā)出讀指令,我們命名為:cha_rf_vld;在幽靈階段,新的PC出現(xiàn),但還沒寫入PC(R15),這時(shí),也是不能執(zhí)行任何指令的,我們命名為:go_rf_vld。

          下面是我寫的rom_en, code_flag, cmd_flag賦值語句,可以對照體會(huì)一下。發(fā)揚(yáng)古人“格”物“格”竹子的精神,設(shè)想一下,是不是那么回事!

          wire rom_en;

          assign rom_en =cpu_en & ( ~(int_all | to_rf_vld | cha_rf_vld | go_rf_vld | wait_en | hold_en ) );

          regcode_flag;

          always @ ( posedge clk or posedge rst )

          if ( rst )

          code_flag <= #`DEL 1d0;

          else if ( cpu_en )

          if ( int_all | to_rf_vld | cha_rf_vld | go_rf_vld | ldm_rf_vld )

          code_flag <= #`DEL0;

          else

          code_flag <= #`DEL1;

          else;

          reg cmd_flag;

          always @ ( posedge clk or posedge rst )

          if ( rst )

          cmd_flag <= #`DEL 1d0;

          else if ( cpu_en )

          if ( int_all )

          cmd_flag <= #`DEL0;

          else if ( ~hold_en )

          if ( wait_en | to_rf_vld | cha_rf_vld | go_rf_vld )

          cmd_flag <= #`DEL0;

          else

          cmd_flag <= #`DELcode_flag;

          else;

          else;

          ldm_rf_vld是在執(zhí)行LDM指令時(shí),改變R15的情況,這個(gè)情況比較特殊,以后再講。

          除了這個(gè),還有wait_en和hold_en。我還是舉例子說明吧。

          1,wait_en

          如果R0 = 0x0, R1=0x0。緊接著會(huì)執(zhí)行下面兩條指令:1, MOV R0,#0xFFFF; 2, ADD R1,R1,[R0,LSL #4]。執(zhí)行完后,正確的結(jié)果應(yīng)該是:R1=0xFFFF0。

          rom_encode_flagcmd_flag

          -----------------

          |胎兒|嬰兒小孩-->幽靈

          -----------------

          XADD R1,R1,[R0,LSL #4]MOV R0,#0xFFFF

          如上圖在“小孩”階段:正在執(zhí)行MOV R0,#0xFFFF,但是R0這個(gè)寄存器里面存放的是0x0,而不是0xFFFF。因?yàn)樵谛『㈦A段,只是要寫R1,但是并沒有寫入,在下一個(gè)時(shí)鐘生效。但是“嬰兒”階段,要執(zhí)行ADD R1,R1,[R0, LSL #4],必須先對R0移位。那么它取得R0的來源是從case語句,是從R0這個(gè)寄存器里得來的,而不是“小孩”階段執(zhí)行的結(jié)果得來的。

          所以如果出項(xiàng)這樣的情況:上一條指令的輸出,正好是下一條指令的輸入。那么下一條指令是不能執(zhí)行,必須要緩一個(gè)周期執(zhí)行。也就是說在兩條指令之間插入一個(gè)空指令,讓R0得到新的值,再執(zhí)行下一條語句,就不會(huì)出錯(cuò)。wait_en就表示這種情況。

          如果wait_en == 1b1,那么rom_en==1b0,表示ADD R1,R1,[R0,LSL #4]還沒執(zhí)行呢,先不用取下一條指令。code_flag不受wait_en影響;cmd_flag<=1b0;下一個(gè)時(shí)鐘,表示這是一條空指令,并不執(zhí)行。

          2,hold_en

          簡而言之,就是在cmd_flag這一階段的指令一個(gè)時(shí)鐘執(zhí)行不下去,需要多個(gè)時(shí)鐘。比如說:LDMIA R13! {R0-R3},需要從RAM里面讀四個(gè)數(shù),送入相應(yīng)的寄存器。我們只有一個(gè)RAM的讀寫端口,執(zhí)行這條命令需要啟動(dòng)這個(gè)讀寫端口四次。那么就要告訴rom_en,你不能取新數(shù)吶。所以我們在LDMIA R13! {R0-R3}占用的4個(gè)周期里,前三個(gè)時(shí),讓hold_en==1b1。那么在這段時(shí)間內(nèi),rom_en==1b0, cmd_flag不受影響。因?yàn)檫@時(shí)執(zhí)行有效,cmd_flag必須保持開始的1b1不變。

          好了,這一節(jié),先寫到這,希望大家也發(fā)揮divide & conquer的精神,一點(diǎn)點(diǎn)的解決問題,走向最后的成功,歡迎提出有疑問的地方。



          關(guān)鍵詞: ARM處理器主體結(jié)

          評論


          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();