搜索
bottom↓
回复: 0

如何使用兼容ARMv4架构的软核测量dhrystone MIPS

[复制链接]

出0入0汤圆

发表于 2010-2-2 14:28:03 | 显示全部楼层 |阅读模式
这款ARM兼容CPU核是支持最通用的ARMv4指令集架构。相对于常用的ARM9系列MCU,区别是不支持thumb指令集和协处理器指令集。它的结构非常简单,全部代码只有1800行左右,但是功能却一点也不精简:除了完美支持ARMv4的指令集外,还具有面积小,速度快的优点。它的面积小和速度快的优点,都是相对于同类型的ARM9系列MCU而言,是非常有说服力的。希望它能够为SoC设计提供坚强有力的核心,使得在ARM高效的嵌入式软件的支持下,SoC设计变得更加轻松容易。
ARM兼容CPU核的设计,其实是非常复杂的。我一时不知从何处开始,但忽然想到我最近做的dhrystone MIPS仿真。这个仿真案例是非常有说服力的。那么我就从这个仿真案例开始,让大家对CPU核的设计有一个初步的认识。
一般来说,ARM核正常工作,则需要一块ROM,当然现在用的更多的是FLASH,它可以不断擦写。这块ROM里面存放的是machine code。ARM核根据machine code的指示,运行不同的动作。它还需要一块RAM,这块RAM可大可小,丰俭由人,它由machine code预先设定的大小而定。有了ROM/RAM,再加上时钟,ARM核作为一块数字电路,则可以正常运作了。


(原文件名:arm.jpg)

但是光有这些,还是不够的。因为如果只有这些,那它属于一个“闷骚”的系统。里面运作的轰轰烈烈,外界却一概不知。所以我们有时候需要干预它的运作。比如为了监察它内部的工作情况,可以使用串口UART,让ARM核不时得把工作情况显示出来。有时候,为了干预它的内部工作,也可以通过中断来改变它的进程。
相对于dhrystone测试,我们需要的中断是一个定时器。它不断告诉ARM核现在的real time。那么它就可以根据这个节拍,计算一定的时间内,进行了多少次dhrystone运算,然后,把结果从UART里面显示出来。
所以我们现在做的工作有两个,一是搭建一个testbench,把上述的最简单的一个mcu在modelsim里面运行起来,这个工作非常简单,简单的就是很短的一个tb.v,就像所有数字设计作业一样,它需要的工具是modelsim;二是修改dhrystone的C程序,使得它能够在这个最简单的ARM mcu里面运行起来,它需要的工具是KEIL或ADS。下面,我们先完成第一项工作。
阅读者可以打开附件的tb_dhrystone.v。第一行是:module tb_dhrystone; 这是所有testbench文件的开头。
然后是:
reg         clk;

initial clk = 1'b0;

always clk = #500 ~clk;

它的作用是生成一个1M的时钟。为什么是1M的时钟?既然是仿真,时钟的大小是可以任意设定的。之所以设定为1MHz,是因为dhrystone测试结果的单位是: Dhrystone MIPS/MHz。那么如果让时钟设定为1MHz,最终的结果就不用除上时钟频率了。
接下来生成一个高电平有效的rst信号。异步reset复位信号作为数字电路设计的基本要件,和时钟信号clk基本上是如影随形,焦孟不离。因此,ARM核也需要一个这样的信号,它在仿真之前,复位里面的所有寄存器。
reg         rst;
initial begin
rst = 1'b1;
#1000 rst = 1'b0;
end

再下来生成一个timer,它用来生成一个real time时钟,它按照固定的周期发送一个中断。由于dhrystone的C程序采用的是KEIL自带的example。它一般位于:C:\Keil\ARM\Examples\DHRY的目录下面。在这个目录下面,有一个timer.c文件,定义了如何生成这个irq中断。
long timeval = 0;

/* Timer Counter 0 Interrupt executes each 10ms @ 40 MHz Crystal Clock        */
__irq void IRQ_Handler (void) {
  timeval++;
//  AIC_EOICR = TC0_SR;                          /* end interrupt               */
}
从这段描述里面可以看出,它需要一个IRQ中断,并且每10 ms发出一个中断。因此,在testbench里面,我们生成一个计数器,让它周期性的发送一个中断。这个周期等于多少呢?它应该等于:10ms/1us(1MHz)=10000。

integer irq_cnt = 0;

always @ ( posedge clk )
if ( irq_cnt==9999 )
    irq_cnt <= 0;
else
    irq_cnt <= irq_cnt + 1'b1;
       
wire irq;
assign irq = (irq_cnt==9999);       

从上面可以看出,ARM核里面的IRQ中断是高电平有效的,所以在计数到9999时,把这个高电平送入到irq信号内。那么它就会触发一个IRQ中断。运用IRQ中断,其实就是这么简单。

下面就涉及到ROM的生成。ROM不同于RAM,它不能是空白,里面必须包含你已经生成的machine code。所以如果我们把dhrystone C程序编译的BIN code生成后,则需要加载到testbench里面。在这里,我使用的是: DHRY.bin。这个bin文件不是原始的ascii形式,而是转换为文本文件格式。之所以这样转换是因为方便使用verilog函数$readmemh来加载。
reg [7:0] rom_contain [32767:0];

initial begin
   $readmemh("DHRY.bin", rom_contain);
end
在上面的verilog testbench句子里,我生成了一个32K byte的ROM,把保存在DHRY.bin的文本格式,十六进制保存的machine code按顺序加载到rom_contain里面了。
生成ROM后,那么接下来的问题是如何让ARM核取用machine code。ARM核不可能在所有的时钟周期内都取机器码的,比如有些指令需要多个周期执行,紧邻的两个周期出现了数据互锁,那么这时候,指令是不用取出的。所以我们用rom_en来表示取指令的情况。如果rom_en为高电平,表示它想取指令,否则表示它无意于取指令。由于不执行thumb指令,所有的指令长度等于32位,地址长度也是32位的,所以下面对于取指令的描述就显而易见了。
wire            rom_en;
wire [31:0]     rom_addr;
reg  [31:0]     rom_data;
always @ ( posedge clk )
if ( rom_en )
    rom_data <= {rom_contain[rom_addr+3],rom_contain[rom_addr+2],rom_contain[rom_addr+1],rom_contain[rom_addr]};
else;
我们知道rom_addr作为指令地址,它是对齐模式的,也即rom_addr的最后2 bit总是等于0的,原因是rom_data——指令本身是32 bit的。所以作为byte为基准的地址描述rom_addr的最低2 bit等于0。在rom_en为高电平的情况下,rom_addr对应的byte和它紧邻的其他三个byte,同时作为一条指令送出。
对于RAM,我们采用的是单口RAM模型。单口RAM是指,同一时钟周期内,要么读操作,要么写操作,不能两者并行操作。由于在ARM指令集内,设计到对word, half-word, byte不同宽度的数据操作。为了统一接口,加入了byte enable信号:ram_flag[3:0]。如果是字操作,则ram_flag[3:0]=4’b1111;如果是半字操作,ram_flag[3:0]要么是2’b11,要么是2’b1100,那么字节操作,就有四种不同的形式。我们看看RAM操作的相关信号:
wire          ram_cen;
wire [3:0]    ram_flag;
wire          ram_wen;
wire [31:0]   ram_addr;
wire [31:0]   ram_wdata;

reg [31:0] ram_contain [4095:0];
ram_cen等于一般的CEN信号,它为高电平,对RAM的读写操作才有效。ram_wen等同于一般的WEN信号,它为高电平,表示此次对RAM的操作是写操作,否则是读操作。ram_flag[3:0]是byte enable信号,前面已经讲述。ram_addr[31:0]提供读写操作的地址。ram_wdata[31:0]只有在写操作时有效,它提供写数据。
在这里,我们例化了4K word,也就是16K byte的RAM空间供ARM核使用。
always @ ( posedge clk )
if ( ram_cen & ram_wen & (ram_addr[31:28]==4'h4 ) )
    ram_contain[ram_addr[13:2]] <= {(ram_flag[3]?ram_wdata[31:24]:ram_contain[ram_addr[13:2]][31:24]),(ram_flag[2]?ram_wdata[23:16]:ram_contain[ram_addr[13:2]][23:16]),(ram_flag[1]?ram_wdata[15:8]:ram_contain[ram_addr[13:2]][15:8]),(ram_flag[0]?ram_wdata[7:0]:ram_contain[ram_addr[13:2]][7:0])};
else;
上面对ram_contain的操作正是写操作的行为。写操作的条件是:ram_cen = 1’b1, ram_wen = 1’b1,并且地址的高bit指明是RAM地址。由于数据的读写操作不仅针对RAM,而且针对寄存器和其他外设的读写。一般为了区分,对它们的地址位的高位分别赋予不同的值。在这里,ram_addr[31:28]==4’h4,表示的选中RAM进行写操作。那么,在写入时,根据byte enable信号,改写对应的byte。

reg [31:0] ram_rdata;

always @ (posedge clk )
if ( ram_cen & ~ram_wen )
    if ( ram_addr == 32'he0000000 )
            ram_rdata <= 32'h0;
        else if ( ram_addr[31:28]==4'h0 )
            ram_rdata <= {rom_contain[ram_addr+3],rom_contain[ram_addr+2],rom_contain[ram_addr+1],rom_contain[ram_addr]};
        else
            ram_rdata <= ram_contain[ram_addr[13:2]];
else;

读操作,则根据C代码规定的需要,可能读寄存器,也可能读ROM的内容,也有可能读RAM的数据。在这里,寄存器主要是指UART的状态寄存器,C代码通过查询UART的状态,如果UART处于IDLE状态,则可以把信息输出,如果处于忙态,则需要等待。它读取ROM的内容是通过地址的高字节来区分的。在这里,ROM的地址高字节等于0,RAM的地址高字节等于8’h40。

对照上图,可以看到,只有UART的输出没有模拟了。那么我们运用下面的语句来模拟UART串口的输出。
always @ ( posedge clk )
if ( ram_cen & ram_wen & ( ram_addr==32'he0000004 ) )
    $write("%s",ram_wdata[7:0]);
else;

我们为串口的输出分配为地址:32’he0000004。则只要对该地址写入数据,则该字节使用$write指令输出到transcript的输出窗口里。

通过上述描述,我们已经设计了一个简单的MCU了。当然,这一切都基于兼容ARM的CPU软核。那么最后一步,则是把我们的ARM软核请出,例化在下面:

arm u_arm(
          .clk         (             clk          ),
          .cpu_en      (             1'b1         ),
          .cpu_restart (             1'b0         ),
          .fiq         (             1'b0         ),
          .irq         (             irq          ),
          .ram_abort   (             1'b0         ),
          .ram_rdata   (             ram_rdata    ),
          .rom_abort   (             1'b0         ),
          .rom_data    (             rom_data     ),
          .rst         (             rst          ),

          .ram_addr    (             ram_addr     ),
          .ram_cen     (             ram_cen      ),
          .ram_flag    (             ram_flag     ),
          .ram_wdata   (             ram_wdata    ),
          .ram_wen     (             ram_wen      ),
          .rom_addr    (             rom_addr     ),
          .rom_en      (             rom_en       )
        );
可以看出,里面有几个信号,我们根本没有描述,它们是做什么用的呢?下面做一个简单描述。cpu_en是同步使能信号:简而言之,只有它等于1’b1,ARM软核工作,它等于1’b0,则ARM软核内部所有的寄存器保持不变。该信号如同一个魔杖,可以自由控制ARM软核这座城堡的运行节奏。当然,如果你不需要它继续工作了,则可以一直赋值为0,那么可以达到省电的目的。
cpu_restart是reset中断信号。在ARM手册中,有一个reset exception,那么软核中就根据这个输入信号进行触发。很显然fiq对应FIQ中断;rom_abort对应prefetch abort;ram_abort对应data abort。上述中断触发都是高电平有效的。
好了,这个简单的testbench只差一个endmodule来结束了。

以上是testbench的搭建。下面是仿真过程。

当我们生成dhrystone的程序代码后,保存为DHRY.bin,然后进行仿真,仿真的时间大概是6s。
下面是截图:


(原文件名:before.JPG)

大概跑到5 s多以后,给出结果。


(原文件名:after.JPG)

可以看出,最终的结果是:2109.7,那么很自然的得出本ARM软核的Dhrystone等于:

2109.7/1757 = 1.2 DMIPS/MHz。



点击此处下载 ourdev_531787.rar(文件大小:21K) (原文件名:dhrystone_modelsim.rar)


点击此处下载 ourdev_531797.rar(文件大小:138K) (原文件名:dhrystone_keil.rar)

阿莫论坛20周年了!感谢大家的支持与爱护!!

知道什么是神吗?其实神本来也是人,只不过神做了人做不到的事情 所以才成了神。 (头文字D, 杜汶泽)
回帖提示: 反政府言论将被立即封锁ID 在按“提交”前,请自问一下:我这样表达会给举报吗,会给自己惹麻烦吗? 另外:尽量不要使用Mark、顶等没有意义的回复。不得大量使用大字体和彩色字。【本论坛不允许直接上传手机拍摄图片,浪费大家下载带宽和论坛服务器空间,请压缩后(图片小于1兆)才上传。压缩方法可以在微信里面发给自己(不要勾选“原图),然后下载,就能得到压缩后的图片。注意:要连续压缩2次才能满足要求!!】。另外,手机版只能上传图片,要上传附件需要切换到电脑版(不需要使用电脑,手机上切换到电脑版就行,页面底部)。
您需要登录后才可以回帖 登录 | 注册

本版积分规则

手机版|Archiver|amobbs.com 阿莫电子技术论坛 ( 粤ICP备2022115958号, 版权所有:东莞阿莫电子贸易商行 创办于2004年 (公安交互式论坛备案:44190002001997 ) )

GMT+8, 2024-7-24 19:23

© Since 2004 www.amobbs.com, 原www.ourdev.cn, 原www.ouravr.com

快速回复 返回顶部 返回列表