跟我写ARM处理器之二:主体结构的确定

发布者:平和宁静最新更新时间:2016-07-19 来源: eefocus关键字:ARM处理器  主体结构 手机看文章 扫描二维码
随时随地手机看文章
好了,上一节定义了端口,基本功能大慨大家已经了然于胸了,现在来确定一下主体结构。我举几个指令执行的例子吧。

第一个是MLA R1,R2,R3,R0。它的意思是:R1=R2*R3 + R0。如果我们要实现这一条指令的话,一个32×32的乘法器需要,一个32+32的加法器是跑不了的。现在定义几个节点:Rm = R2; Rs=R3; sec_operand(第二操作数的意思)=mult_rm_rs[31:0](mult_rm_rs的低32位);Rn=R0;则结果等于:Rn + sec_operand。

第二个是:SUB R1,R0, R2, LSL #2。它的意思是:R1=R0 - R2<<2。看了我前面文章的知道,这个指令同样可以像前面一样套入:Rm=R2; Rs=32'b100; sec_operand=mult_rm_rs[31:0];Rn=R0;结果等于:Rn - sec_operand。

第三个是:LDR R1,[R0,R2,LSR #2]!。这是一条取RAM的数据进入寄存器的指令,取地址是:R0+R2>>2。并把取地址保存回R0。现在比较难计算的是: R0+R2>>2。但是这个同样也可以往前两个模式一样靠:Rm=R2; Rs=32'b0100_0000_0000_0000_0000_0000_0000_0000,那么sec_operand = mult_rm_rs[63:32]正好等于:R2>>2。如果Rn=R0,取地址就等于:Rn+sec_operand。这个地址还要送入R0中。

看到这,大家明白了本核的核心结构了吧。网友先别赞我眼光如炬,目光如神,一眼看出核心所在。实际上我在写第一版的时候,绝没想到把移位交给乘法器来完成,也是傻傻地参考别人文档写了一个桶形移位器。但后来灵光一现,觉得既然乘法器避免不了,如果只让他在MUL指令的时候使用,其他指令的时候闲着,那多么没意思呀。这样乘法器复用起来,让它参与了大部分指令运算。

好了,我们要做的事是这样的。指令到来,准备Rm, Rs, Rn, 为生成sec_operand产生控制信号,决定Rn和sec_operand之间是加还是减,那么最后生成的结果要么送入寄存器组,要么作为地址参与读写操作。就这么简单!

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

上面解决了做什么的问题,随之而来的是怎么做的问题。可能大家首先想到的是三级流水线。为什么是三级呢?为什么不是两级呢?两级有什么不好?我告诉你们,两级同样可以,无非是关键路径长一点。我接下来,就要做两级,没有什么能束缚我们!实际上,很多项目用不到30、40MHz的速度,10M,20M也是可以接受,100ns,50ns内,我那一套乘加结构同样能满足。口说无凭,看看我代码中是如何生成: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],2'b0};

else if ( code_is_ldm )

    case( code[24:23] )

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

    2'd1 : code_rm =  0;

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

         2'd3 : code_rm =  3'b100;

         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 + 1'b1;

else if ( ( (code[6:5]==2'b10) & 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] )

4'h0 : code_rma =  r0;

4'h1 : code_rma =  r1;      

4'h2 : code_rma =  r2;

4'h3 : code_rma =  r3;

4'h4 : code_rma =  r4;

4'h5 : code_rma =  r5;      

4'h6 : code_rma =  r6;

4'h7 : code_rma =  r7;      

4'h8 : code_rma =  r8;

4'h9 : code_rma =  r9;      

4'ha : code_rma =  ra;

4'hb : code_rma =  rb;

4'hc : code_rma =  rc;

4'hd : code_rma =  rd;      

4'he : code_rma =  re;

4'hf : code_rma =  rf;

 endcase     

*/

我有if else这个法宝,你不管来什么指令,我都给你准备好Rm。这就像一台脱粒机,你只要在送货口送东西即可。你送麦子脱麦子,你送玉米脱玉米。你的Rm来自于寄存器组,那好我用code_rma来给你选中,送入Rm这个送货口。你的Rm来自代码,就是一套立即数,那我就把code[11:0]送入Rm,下面的程式有了正确的输入,你只要把最后的正确结果,送给寄存器组即可。

再看看Rs的生成:

注:以下非正式代码,讲解举例所用

/*

always @ ( * )

if ( code_is_dp0|code_is_ldr1 )

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

else if ( code_is_dp1 )

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

else if ( code_is_msr1|code_is_dp2 )

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

else

    code_rot_num =  5'b0;

 

always @ ( * )

if ( code_is_multl )

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

             code_rs =  ~code_rsa + 1'b1;

         else

             code_rs =  code_rsa;

else if ( code_is_mult )

    code_rs =  code_rsa;

else begin

    code_rs =  32'b0;

         code_rs[code_rot_num] = 1'b1;

    end

 

 

always @ ( * )

case ( code[11:8] )

4'h0 : code_rsa =  r0;

4'h1 : code_rsa =  r1;       

4'h2 : code_rsa =  r2;

4'h3 : code_rsa =  r3;

4'h4 : code_rsa =  r4;

4'h5 : code_rsa =  r5;       

4'h6 : code_rsa =  r6;

4'h7 : code_rsa =  r7;       

4'h8 : code_rsa =  r8;

4'h9 : code_rsa =  r9;       

4'ha : code_rsa =  ra;

4'hb : code_rsa =  rb;

4'hc : code_rsa =  rc;

4'hd : code_rsa =  rd;       

4'he : code_rsa =  re;

4'hf : code_rsa =  rf;

endcase      

*/

Sec_operand的例子就不用举了吧,无非是根据指令选择符合该指令的要求,来送给下一级的加/减法器。

所以说,这样的两级流水线我们同样可以完成。现在使用三级流水线,关键路径是26ns。如果使用两级流水线,绝对在50 ns以内。工作在20MHz的ARM,同样也是受低功耗用户们欢迎的。有兴趣的,在看完我的文章后,把ARM核改造成两级流水线。

 

现在要转换一个观念。以前的说法:第一级取代码;第二级解释代码,第三级执行代码。现在要转换过来,只有两级,第一级:取代码;第二级执行代码。而现在我做成第三级,是因为一级执行不完,所以要分两级执行。所以是:第一级取代码;第二级执行代码阶段一(主要是乘法);第三级执行代码阶段二(主要是加/减法)。

 

也许有人要问,那解释代码为什么不安排一级?是因为我觉得解释代码太简单,根本不需要安排一级,这一点,我在下一节会讲到。

 

既然这个核是三级流水线,还是从三级流水线讲起。我把三级流水线的每一级给了一个标志信号,分别是:rom_en, code_flag, cmd_flag。rom_en对应第一级取代码,如果rom_en==1'b1表示需要取代码,那这个代码其实还处在ROM内,我们命名为“胎儿”;如果code_flag==1'b1表示对应的code处于执行阶段一,可以命名为“婴儿”;如果cmd_flag==1'b1,表示对应的code处于执行阶段二,命名为“小孩”。当这个指令最终执行结束,可以认为它死去了,命名为“幽灵”。

 

 

rom_en               code_flag        cmd_flag

 

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

|  胎儿     |           婴儿           小孩        -->   幽灵

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

 

现在,我们模拟一下这个执行过程吧。一般ROM里面从0开始的前几条指令都是跳转指令,以hello这个例程为例,存放的是:LDR PC,[PC,#0x0018];连续五条都是这样的。

刚上电时,rom_en==1'b1,表示要取number 0 号指令:

 

 rom_en==1'b1          code_flag        cmd_flag

(addr=0)

 

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

|  胎儿     |           婴儿           小孩        -->   幽灵

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

 

LDR PC,[PC,#0x0018]

 

第一个clock后;第一条指令LDR PC,[PC,#0x0018]到了婴儿阶段。

 

 rom_en==1'b1           code_flag        cmd_flag

(addr=4)

 

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

|  胎儿     |             婴儿            小孩        -->   幽灵

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

 

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

 

第二个clock后,第一条指令LDR PC,[PC,#0x0018]到了小孩阶段。

 

 rom_en==1'b1           code_flag        cmd_flag

(addr=8)

 

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

|  胎儿     |             婴儿            小孩             -->   幽灵

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

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

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

 

 

当“小孩”== LDR PC,[PC,#0x0018]时,不能再取addr==8的指令了。因为addr=0时的LDR PC,[PC,#0x0018]更改了PC的值,不仅不能取新的code,连处于婴儿阶段的code也不能执行了。如果执行的话,那就是错误执行。为了避免addr=4的LDR PC,[PC,#0x0018]执行,我们可以给每一个阶段打一个标签tag,比如code_flag对应婴儿,cmd_flag对应小孩。只有在cmd_flag==1'b1时,指令才执行。如下图所示。

 

 rom_en==1'b0           code_flag        cmd_flag

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

 

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

|  胎儿     |             婴儿            小孩             -->   幽灵

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

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

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

                                          (修改PC)

                                          发出读指令

 

一旦有修改PC,那么rom_en立即赋值为1'b0。code_flag, cmd_flag在下一个时钟赋给1'b0。表示在下一个时钟“婴儿”和“小孩”都是非法的,不能执行。但是新的PC值不是立即得到的,因为LDR指令是要从RAM取数据,在小孩阶段只能发出读指令,在一个时钟,新的PC值才出现在ram_rdata,但还没有出现在R15里面,所以要等一个时钟。

 

 rom_en==1'b0           code_flag==1'b0   cmd_flag==1'b0

(addr=8)               

 

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

|  胎儿     |             婴儿            小孩             -->   幽灵

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

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

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

                                                             ram_rdata=NEW PC

 

在空闲的这个周期内,为了让指令不执行,只要赋值:rom_en, code_flag, cmd_flag为1'b0就达到目的了。

 

 rom_en, code_flag, cmd_flag在一般情况下都是1'b1,但是如果PC值一改变,那么就需要同时被赋值给1'b0。不过rom_en和code_flag,cmd_flag有区别: rom_en是立即生效,code_flag/cmd_flag要在下一个时钟生效。rom_en下一个时钟是要有效的,因为要读新的PC值。

 

改变PC有三种情况:

1,中断发生:我们命名为:int_all。只要中断发生,PC要么等于0,4,8,10,1C等等。

2,从寄存器里给PC赋值:一般情况是:MOV PC,R0。在小孩阶段,已经可以给出新的PC值了,这个和中断类似。我们命名为:to_rf_vld。

3,从RAM里面取值给PC赋值:一般是LDR PC [PC,#0x0018],那么在小孩阶段,发出读指令,我们命名为:cha_rf_vld;在幽灵阶段,新的PC出现,但还没写入PC(R15),这时,也是不能执行任何指令的,我们命名为:go_rf_vld。

 

下面是我写的rom_en, code_flag, cmd_flag赋值语句,可以对照体会一下。发扬古人“格”物“格”竹子的精神,设想一下,是不是那么回事!

 

 

wire rom_en;

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

 

 

reg  code_flag;

always @ ( posedge clk or posedge rst )

if ( rst )

    code_flag <= #`DEL 1'd0;

else if ( cpu_en )

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

             code_flag <= #`DEL  0;

         else

             code_flag <= #`DEL  1;

else;

 

reg cmd_flag;

always @ ( posedge clk or posedge rst )

if ( rst )

    cmd_flag <= #`DEL 1'd0;

else if ( cpu_en )

    if ( int_all )

             cmd_flag <= #`DEL  0;

         else if ( ~hold_en )

             if ( wait_en | to_rf_vld | cha_rf_vld | go_rf_vld )

                   cmd_flag <= #`DEL  0;

             else

                   cmd_flag <= #`DEL  code_flag;

         else;

else;

 

ldm_rf_vld是在执行LDM指令时,改变R15的情况,这个情况比较特殊,以后再讲。

 

除了这个,还有wait_en和hold_en。我还是举例子说明吧。

 

1,wait_en

如果R0 = 0x0, R1=0x0。紧接着会执行下面两条指令:1, MOV R0,#0xFFFF; 2, ADD R1,R1,[R0,LSL #4]。执行完后,正确的结果应该是:R1=0xFFFF0。

 

 rom_en               code_flag        cmd_flag

 

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

|  胎儿     |             婴儿            小孩        -->   幽灵

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

 

     X      ADD R1,R1,[R0,LSL #4]  MOV R0,#0xFFFF

 

如上图在“小孩”阶段:正在执行MOV R0,#0xFFFF,但是R0这个寄存器里面存放的是0x0,而不是0xFFFF。因为在小孩阶段,只是要写R1,但是并没有写入,在下一个时钟生效。但是“婴儿”阶段,要执行ADD R1,R1,[R0, LSL #4],必须先对R0移位。那么它取得R0的来源是从case语句,是从R0这个寄存器里得来的,而不是“小孩”阶段执行的结果得来的。

 

所以如果出项这样的情况:上一条指令的输出,正好是下一条指令的输入。那么下一条指令是不能执行,必须要缓一个周期执行。也就是说在两条指令之间插入一个空指令,让R0得到新的值,再执行下一条语句,就不会出错。wait_en就表示这种情况。

 

如果wait_en == 1'b1,那么rom_en==1'b0, 表示ADD R1,R1,[R0,LSL #4]还没执行呢,先不用取下一条指令。code_flag不受wait_en影响;cmd_flag<=1'b0;下一个时钟,表示这是一条空指令,并不执行。

 

2,hold_en

简而言之,就是在cmd_flag这一阶段的指令一个时钟执行不下去,需要多个时钟。比如说:LDMIA R13! {R0-R3},需要从RAM里面读四个数,送入相应的寄存器。我们只有一个RAM的读写端口,执行这条命令需要启动这个读写端口四次。那么就要告诉rom_en,你不能取新数呐。所以我们在LDMIA R13! {R0-R3}占用的4个周期里,前三个时,让hold_en==1'b1。那么在这段时间内,rom_en==1'b0, cmd_flag不受影响。因为这时执行有效,cmd_flag必须保持开始的1'b1不变。

 

好了,这一节,先写到这,希望大家也发挥divide & conquer的精神,一点点的解决问题,走向最后的成功,欢迎提出有疑问的地方。

关键字:ARM处理器  主体结构 引用地址:跟我写ARM处理器之二:主体结构的确定

上一篇:stm32的 开漏电路 与 推挽输出
下一篇:跟我写ARM处理器之一:从写module arm开始

推荐阅读最新更新时间:2024-03-16 15:00

ARM处理器S3C2440A为核心的嵌入式无线实时图像传输系统设计
1 引言 随着信息化,智能化,网络化的发展,嵌入式系统技术也将获得广阔的发展空间。进入20 世纪90 年代,嵌入式技术全面展开,目前已成为通信和消费类产品的共同发展方向。在通信领域,数字技术正在全面取代模拟技术。毫无疑问,模拟图像采集系统必将被数字图像采集系统所代替,其中的嵌入式图像采集系统由于其优越的性能越来越受到人们的关注。同时,在技术进步推动信息传递日趋无线化的背景下,无线图像传输也就成为了图像传输的前沿领域。对于边远的和可移动的系统,无线网络接入传输数据方式显得十分重要。本文介绍了采用nRF2401 作为传输手段的无线图像传输系统。该系统由无线照相机和图片接收器两部分组成,具有视频图像采集、压缩、传输和存储等功能。 2
[单片机]
以<font color='red'>ARM处理器</font>S3C2440A为核心的嵌入式无线实时图像传输系统设计
基于ARM处理器的GPS移动设备设计
引言   GPS即全球定位系统,由美国从上世纪70年代开始研制,历时20年,耗资200亿美元,于1994年全面建成,具有在海、陆、空进行全方位实时三维导航与定位的能力。近年来随着GPS的不断改进,硬、软件的不断完善,应用领域正在不断展开,目前已遍及国民经济各种部门,并开始逐步深入人们的日常生活。如何设计一个带有GPS功能的移动设备,实现对GPS卫星数据的接收和解码,已经是现在CPS应用的热点。   1 开发平台   1.1 软件平台   为适应大多数Windows用户的使用习惯,我们设定移动设备运行环境为嵌入式操作系统Windows CE 5.0(简称WinCE 5.0),开发过程在Windows XP操作系统下进行。
[单片机]
基于<font color='red'>ARM处理器</font>的GPS移动设备设计
日本芯片制造商 Socionext 宣布联合台积电,开发 2nm ARM处理器
10 月 27 日消息,Socionext 是日本唯一一家负责定制 Soc 芯片的上市公司,这家公司号称“只有在接到订单后才会研发与生产芯片”,运营模式与英伟达和 AMD 有一定本质区别。 不过目前该公司宣布正联合台积电,开发一款 32 核 ARM 处理器,该 CPU 采用了 Arm 的 Neoverse 计算子系统技术,号称“能够在超大规模数据中心和下一代移动基础设施(包括 5G 和 6G)中提供‘可扩展的性能’”。 ▲ 图源 Socionext 官方新闻稿 经过查询得知,Neoverse 计算子系统技术是一种预集成、预验证的计算平台,旨在简化芯片的定制流程。芯片组可在单个封装内提供单个或多个 实例 ,以及用于满足 IO 和
[半导体设计/制造]
日本芯片制造商 Socionext 宣布联合台积电,开发 2nm <font color='red'>ARM处理器</font>
嵌入式系统架构之ARM处理器 
ARM公司于1991年成立于英国剑桥,主要出售芯片设计技术的授权。目前,采用ARM技术智能财产(IP)核心的处理器,即我们通常所说的ARM 处理器,已遍及工业控制、消费类电子产品、通信系统、网络系统、无线系统等各类产品市场,基于ARM技术的处理器应用约占据了32位RISC微处理器 75%以上的市场,ARM技术不止逐步渗入到我们生活的各个方面,我们甚至可以说,ARM于人类的生活环境中,已经是不可或缺的一环。 目前市面上常见的ARM处理器架构,可分为ARM7、ARM9以及ARM11,新推出的Cortex系列尚在进行开发验证,市面上还未有相关产品推出。ARM也是嵌入式处理器中首先推出多核心架构的厂商。 ARM首个多核心架构为AR
[单片机]
嵌入式系统架构之<font color='red'>ARM处理器</font> 
微软Windows 8将支持ARM处理器
微软 (Microsoft)在美国时间1月5日终于正式宣布,该公司下一版 Windows 操作系统 ──应该会是叫做 Windows 8 ──将支持 ARM 核心处理器,也证实了数月以来业界猜测该公司将扩大支持 x86 以外的处理器平台之传言。 在 2011年国际消费性电子展( CES )开展前夕的一场记者会上,微软表示,新版本的Windows操作系统,将可执行在各种ARM核心SoC上,包括来自Nvidia、高通(Qualcomm)、德州仪器(TI)等微软伙伴厂商的芯片,也能执行在英特尔(Intel)、AMD等厂商的x86架构处理器上。 “Windows对ARM核心SoC的支持,对微软与对产业界来说都是重要的
[工业控制]
采用嵌入式ARM处理器设计便携式脑卒中康复仪
脑卒中(Stroke)是脑中风的学名,是一种突然起病的脑血液循环障碍性疾病。又叫脑血管意外。是指在脑血管疾病的病人,因各种诱发因素引起脑内动脉狭窄,闭塞或破裂,而造成急性脑血液循环障碍,临床上表现为一过性或永久性脑功能障碍的症状和体征。脑卒中分为缺血性脑卒中和出血性脑卒中。临床表现以猝然昏扑、不省人事或突然发生口眼歪斜、半身不遂、舌强言蹇、智力障碍为主要特征。脑中风包括缺血性中风(短暂性脑缺血发作、动脉粥样硬化性血栓性脑梗塞、腔隙性脑梗塞、脑栓塞)、出血性中风(脑出血、蛛网膜下腔出血)、高血压脑病和血管性痴呆四大类。如何运用新技术开发更有效、更适用的治疗仪器来改善患者的生理功能,使患者能在最短时间内达到最满意的治疗效果并最终摆脱病
[单片机]
采用嵌入式<font color='red'>ARM处理器</font>设计便携式脑卒中康复仪
ARM处理器的节能优势
许多嵌入式ARM处理器的系统都是基于电池供电的能量供应方式,而处理器的功耗对于整个SoC芯片至关重要,因此ARM处理器的低功耗优势可以充分节省能量消耗。总之,当前的典型功耗的电流图并不依赖于标准过程、标准集或工作负载。 EnergyBench提供若干工具,这些工具可容易低与经济实用的硬件结合使用,以便使用E EM B C开发的标准方法测量典型功耗。不过,除了处理器之外,具体芯片设计和集成到芯片内部的外围模块也是影响芯片功耗的重要因素。虽然许多芯片供应商都会在产品的datasheet中提供功耗参数,但是这些参数往往是不具可比性的。当设计者试图对集成到SoC中的不同处理器进行对比时,如果想要弄清楚处理器的真实功耗是怎样的,将会变得非
[单片机]
<font color='red'>ARM处理器</font>的节能优势
基于ARM的车载GPS定位终端的设计
1 引言 车载GPS定位终端在过去十年内已经成为汽车工业发展的焦点。在欧美国家和日本,车载GPS定位终端在最近几年内得以广泛的应用。车载GPS定位终端是融全球卫星定位技术(GPS)和现代无线通信技术于一体的高科技系统。该终端的主要功能是通过GPS模块从卫星获取GPS数据,将移动车辆的动态位置(经度、纬度、时间、速度)等信息实时地通过无线通信链路上传至监控中心,同时接收监控中心发送的控制命令。目前的车辆监控系统中大多采用GSM通信网以短信息的方式进行通信,不能充分满足实际应用的需要。而GPRS(General Packet Radio Service)通用分组无线业务是一种以分组交换技术为基础,采用IP数据网络协议的高效数据传输网
[单片机]
基于ARM的车载GPS定位终端的设计
小广播
添点儿料...
无论热点新闻、行业分析、技术干货……
设计资源 培训 开发板 精华推荐

最新单片机文章
何立民专栏 单片机及嵌入式宝典

北京航空航天大学教授,20余年来致力于单片机与嵌入式系统推广工作。

换一换 更多 相关热搜器件
电子工程世界版权所有 京B2-20211791 京ICP备10001474号-1 电信业务审批[2006]字第258号函 京公网安备 11010802033920号 Copyright © 2005-2024 EEWORLD.com.cn, Inc. All rights reserved