|
马上注册,结交更多好友,享用更多功能,让你轻松玩转社区。
您需要 登录 才可以下载或查看,没有账号?注册
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核作为一块数字电路,则可以正常运作了。
但是光有这些,还是不够的。因为如果只有这些,那它属于一个“闷骚”的系统。里面运作的轰轰烈烈,外界却一概不知。所以我们有时候需要干预它的运作。比如为了监察它内部的工作情况,可以使用串口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。
下面是截图:
大概跑到5 s多以后,给出结果。
可以看出,最终的结果是:2109.7,那么很自然的得出本ARM软核的Dhrystone等于:
2109.7/1757 = 1.2 DMIPS/MHz。
sim.rar
(20.94 KB, 下载次数: 145 )
|
|