在线咨询
eetop公众号 创芯大讲堂 创芯人才网
切换到宽版

EETOP 创芯网论坛 (原名:电子顶级开发网)

手机号码,快捷登录

手机号码,快捷登录

找回密码

  登录   注册  

快捷导航
搜帖子
查看: 20638|回复: 47

[原创] 再发一篇博文:仿真测测看,兼容ARM9软核处理器的Dhrystone性能

[复制链接]
发表于 2012-3-31 11:17:56 | 显示全部楼层 |阅读模式

马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。

您需要 登录 才可以下载或查看,没有账号?注册

x
本帖最后由 free-arm 于 2012-4-1 10:20 编辑

这款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核作为一块数字电路,则可以正常运作了。

1.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文件的开头。

然后是:



  1. reg         clk;

  2. initial clk = 1'b0;

  3. always clk = #500 ~clk;


复制代码
它的作用是生成一个1M的时钟。为什么是1M的时钟?既然是仿真,时钟的大小是可以任意设定的。之所以设定为1MHz,是因为dhrystone测试结果的单位是: Dhrystone MIPS/MHz。那么如果让时钟设定为1MHz,最终的结果就不用除上时钟频率了。

接下来生成一个高电平有效的rst信号。异步reset复位信号作为数字电路设计的基本要件,和时钟信号clk基本上是如影随形,焦孟不离。因此,ARM核也需要一个这样的信号,它在仿真之前,复位里面的所有寄存器。



  1. reg         rst;
  2. initial begin
  3. rst = 1'b1;
  4. #1000 rst = 1'b0;
  5. end


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



  1. long timeval = 0;

  2. /* Timer Counter 0 Interrupt executes each 10ms @ 40 MHz Crystal Clock        */
  3. __irq void IRQ_Handler (void) {
  4.   timeval++;
  5. //  AIC_EOICR = TC0_SR;                          /* end interrupt               */
  6. }


复制代码
从这段描述里面可以看出,它需要一个IRQ中断,并且每10 ms发出一个中断。因此,在testbench里面,我们生成一个计数器,让它周期性的发送一个中断。这个周期等于多少呢?它应该等于:10ms/1us(1MHz)=10000。



  1. integer irq_cnt = 0;

  2. always @ ( posedge clk )
  3. if ( irq_cnt==9999 )
  4.     irq_cnt <= 0;
  5. else
  6.     irq_cnt <= irq_cnt + 1'b1;
  7.         
  8. wire irq;
  9. 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来加载。



  1. reg [7:0] rom_contain [32767:0];

  2. initial begin
  3.    $readmemh("DHRY.bin", rom_contain);
  4. end


复制代码
在上面的verilog testbench句子里,我生成了一个32K byte的ROM,把保存在DHRY.bin的文本格式,十六进制保存的machine code按顺序加载到rom_contain里面了。

生成ROM后,那么接下来的问题是如何让ARM核取用machine code。ARM核不可能在所有的时钟周期内都取机器码的,比如有些指令需要多个周期执行,紧邻的两个周期出现了数据互锁,那么这时候,指令是不用取出的。所以我们用rom_en来表示取指令的情况。如果rom_en为高电平,表示它想取指令,否则表示它无意于取指令。由于不执行thumb指令,所有的指令长度等于32位,地址长度也是32位的,所以下面对于取指令的描述就显而易见了。



  1. wire            rom_en;
  2. wire [31:0]     rom_addr;
  3. reg  [31:0]     rom_data;
  4. always @ ( posedge clk )
  5. if ( rom_en )
  6.     rom_data <= {rom_contain[rom_addr+3],rom_contain[rom_addr+2],rom_contain[rom_addr+1],rom_contain[rom_addr]};
  7. 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操作的相关信号:



  1. wire          ram_cen;
  2. wire [3:0]    ram_flag;
  3. wire          ram_wen;
  4. wire [31:0]   ram_addr;
  5. wire [31:0]   ram_wdata;

  6. 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核使用。



  1. always @ ( posedge clk )
  2. if ( ram_cen & ram_wen & (ram_addr[31:28]==4'h4 ) )
  3.     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])};
  4. else;


复制代码
上面对ram_contain的操作正是写操作的行为。写操作的条件是:ram_cen = 1’b1, ram_wen = 1’b1,并且地址的高bit指明是RAM地址。由于数据的读写操作不仅针对RAM,而且针对寄存器和其他外设的读写。一般为了区分,对它们的地址位的高位分别赋予不同的值。在这里,ram_addr[31:28]==4’h4,表示的选中RAM进行写操作。那么,在写入时,根据byte enable信号,改写对应的byte。



  1. reg [31:0] ram_rdata;

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


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

对照上图,可以看到,只有UART的输出没有模拟了。那么我们运用下面的语句来模拟UART串口的输出。



  1. always @ ( posedge clk )
  2. if ( ram_cen & ram_wen & ( ram_addr==32'he0000004 ) )
  3.     $write("%s",ram_wdata[7:0]);
  4. else;


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

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



  1. arm u_arm(
  2.           .clk         (             clk          ),
  3.           .cpu_en      (             1'b1         ),
  4.           .cpu_restart (             1'b0         ),
  5.           .fiq         (             1'b0         ),
  6.           .irq         (             irq          ),
  7.           .ram_abort   (             1'b0         ),
  8.           .ram_rdata   (             ram_rdata    ),
  9.           .rom_abort   (             1'b0         ),
  10.           .rom_data    (             rom_data     ),
  11.           .rst         (             rst          ),

  12.           .ram_addr    (             ram_addr     ),
  13.           .ram_cen     (             ram_cen      ),
  14.           .ram_flag    (             ram_flag     ),
  15.           .ram_wdata   (             ram_wdata    ),
  16.           .ram_wen     (             ram_wen      ),
  17.           .rom_addr    (             rom_addr     ),
  18.           .rom_en      (             rom_en       )
  19.         );


复制代码
可以看出,里面有几个信号,我们根本没有描述,它们是做什么用的呢?下面做一个简单描述。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。

sim.rar (20.94 KB, 下载次数: 145 )
发表于 2012-3-31 22:37:02 | 显示全部楼层
期待你的书
发表于 2012-4-8 22:49:36 | 显示全部楼层
怎么评价楼主呢,几年下来搞了点指令集的学习,而且体系结构也是半吊子。有这个精力不如搞个实用些的单片机核。资料做学习还不够好,容易误人子弟。
 楼主| 发表于 2012-4-9 10:07:30 | 显示全部楼层
感谢关注。我稍有心得的是使用Verilog的能力,以及因此掌控FPGA的经验体会,希望能够帮到其他人。如果不能帮到你,非常遗憾,希望忘掉我,不用费心评价我。

之所以几年才出一本书,是因为在中国做一件事情太难了,如果不多用些业余时间做好,只会贻笑大方。但我希望第二个“我”,也就是希望在FPGA/ASIC设计上有建树的爱好者,不能那么难,那么辛苦了,你们可以在论坛上,在我的基础上自由发挥你的创造力,做一些连想都不敢想的创新。

谢谢!
发表于 2012-4-9 10:20:14 | 显示全部楼层
不错 支持啦!!
发表于 2012-4-13 11:02:19 | 显示全部楼层
支持一下!
发表于 2012-4-13 11:25:10 | 显示全部楼层


动嘴永远比动手容易。
发表于 2012-4-15 12:17:59 | 显示全部楼层


怎么评价楼主呢,几年下来搞了点指令集的学习,而且体系结构也是半吊子。有这个精力不如搞个实用些的单片机 ...
fuchouzhe 发表于 2012-4-8 22:49



自以为是。。自命不凡不如多做点事。。而不是动动嘴皮子评论别人做的事情。。
发表于 2012-4-15 22:48:06 | 显示全部楼层
看起来很NB
发表于 2012-4-16 09:27:58 | 显示全部楼层
学习了啊,谢谢楼主了啊
您需要登录后才可以回帖 登录 | 注册

本版积分规则

关闭

站长推荐 上一条 /2 下一条

小黑屋| 关于我们| 联系我们| 在线咨询| 隐私声明| EETOP 创芯网
( 京ICP备:10050787号 京公网安备:11010502037710 )

GMT+8, 2024-4-27 23:51 , Processed in 0.043520 second(s), 12 queries , Gzip On, Redis On.

eetop公众号 创芯大讲堂 创芯人才网
快速回复 返回顶部 返回列表