SkyEye硬件模拟平台,第三部分: 硬件仿真实现之三

来源:互联网 发布:高达模型淘宝 编辑:程序博客网 时间:2024/05/16 14:35

MMU/CACHE仿真

    

 


级别: 初级

陈渝 (yuchen@tsinghua.edu.cn)清华大学

2004 年 10 月 01 日

本系列文章的第三部分主要介绍了SkyEye硬件模拟平台的实现细节。主要内容包括SkyEye的总体设计、SkyEye的可扩展框架、SkyEye的关键数据结构、SkyEye对各种CPU的模拟实现、SkyEye对各种外设的模拟实现、如何安装使用SkyEye以及如何扩展SkyEye的仿真模块等。对 SkyEye的深入了解,有助于对嵌入式硬件系统有更深入的认识,特别是对操作系统、驱动程序如何与嵌入式硬件系统进行交互有更深刻的了解。

SkyEye的MMU/CACHE和Memory模拟实现

1. MMU和Memory系统结构


图 0-1 ARM系统中MMU和Memory的系统结构
图 0-1 ARM系统中MMU和Memory的系统结构

ARM 系统中MMU和Memory的系统结构如图 0 1所示。不过具体的CPU在实现MMU时差别较大,可能对其做简化和扩展, SkyEye的MMU模拟实现基于此,在提供一个标准的接口基础上,分成与具体CPU类型无关的MMU模拟子模块和与具体CPU类型相关的MMU模拟子模块两个主要部分。

2. ARM 数据访问的基本流程图

ARM CPU进行数据访问的基本流程如图 0 2所示。


图 0-2 ARM CPU进行数据访问的基本流程
图 0-2 ARM CPU进行数据访问的基本流程

3. MMU的统一接口

数据结构

typedef struct mmu_state_t {ARMwordcontrol;//CP15 control registerARMwordtranslation_table_base;//CP15 translation table base registerARMworddomain_access_control;//CP15 domain access control  registerARMwordfault_status;//CP15 fault status registerARMwordfault_address;//CP15 fault address registerARMwordlast_domain;  //last access domainARMwordprocess_id;  //CP15 process id registermmu_ops_tops;union{sa_mmu_tsa_mmu;arm7100_mmu_t  arm7100_mmu;}u;} mmu_state_t;typedef struct mmu_ops_s{int (*init)(ARMul_State *state);/*initilization*/void (*exit)(ARMul_State *state);/*free on exit*/ fault_t (*read_byte)(ARMul_State *state, ARMword va, ARMword *data);fault_t (*write_byte)(ARMul_State *state, ARMword va, ARMword data);fault_t (*read_halfword)(ARMul_State *state, ARMword va, ARMword *data);fault_t (*write_halfword)(ARMul_State *state, ARMword va,ARMword data);fault_t (*read_word)(ARMul_State *state, ARMword va,ARMword *data);fault_t (*write_word)(ARMul_State *state, ARMword va,ARMword data);fault_t (*load_instr)(ARMul_State *state, ARMword va, ARMword *instr);void (*mcr)(ARMul_State *state, ARMword instr, ARMword val);ARMword (*mrc)(ARMul_State *state, ARMword instr);}mmu_ops_t;

数据结构ARMul_State中类型为mmu_state_t的mmu代表模拟中MMU的状态。mmu_state_t中的ops提供具体mmu的接口函数,同时包括一个联合结构u用于具体mmu实现的数据结构。

4. 与具体CPU类型无关的MMU模拟子模块

与具体CPU类型无关的MMU模拟子模块是实现MMU模拟的基础,它实现了TLB,TTW, 访问控制,CACHE, Write Buffer,Read Buffer等MMU要素的模拟,并且提供了较统一的接口,可以较灵活地组合实现具体MMU模拟。

TLB的实现(mmu/tlb.[ch])
TLB是一个表,它的表项包含地址映射信息和访问控制信息,地址转换信息只有在该表中查询不到时,才通过TTW在内存中查找。

数据结构

TLB 映射类型

typedef enum tlb_mapping_t {TLB_INVALID = 0,TLB_SMALLPAGE = 1,TLB_LARGEPAGE = 2,TLB_SECTION = 3} tlb_mapping_t;

TLB 表项

typedef struct tlb_entry_t {ARMwordvirt_addr;  //virtual addressARMwordphys_addr; //physical addressARMwordperms;     //access permissionARMworddomain;   //access domaintlb_mapping_tmapping;//tlb mapping type} tlb_entry_t;

TLB 表

typedef struct tlb_s{int num;/*num of tlb entry*/int cycle;/*current tlb cycle,used for allocate tlb_entry*/tlb_entry_t*entrys;}tlb_t; 

接口函数

   int mmu_tlb_init(tlb_t *tlb_t, int num);   初始化TLBvoid mmu_tlb_exit(tlb_t *tlb_t);   释放TLBvoid mmu_tlb_invalidate_all(ARMul_State *state, tlb_t *tlb_t);  无效所有的tlb_entryvoid mmu_tlb_invalidate_entry(ARMul_State *state, tlb_t *tlb_t, ARMword virt_addr);  无效包含指定virt_addr的tlb_entry.tlb_entry_t * mmu_tlb_search(ARMul_State *state, tlb_t *tlb_t, ARMword virt_addr);   根据virt_addr查找tlb_entry   

TTW的实现(mmu/tlb.[ch])
ARM系统中通过2级映射,实现了页式地址转换。

接口函数

fault_t translate(ARMul_State *state, ARMword virt_addr, tlb_t *tlb_t, tlb_entry_t **tlb){/*首先查找TLB表,如果没有找到,执行TTW并更新TLB表*/*tlb = mmu_tlb_search(state, tlb_t, virt_addr);/*查找tlb*/if (!*tlb) {/* 没有找到,进行TTW*/ARMword l1addr, l1desc; /*一级描述符地址、一级描述符*/tlb_entry_t entry;l1addr = state->mmu.translation_table_base & 0xFFFFC000;l1addr = (l1addr | (virt_addr >> 18)) & ~3;/*计算一级描述符地址*/l1desc = mem_read_word(state, l1addr);/*读取一级描述符*/switch (l1desc & 3) {case 0:case 3:return SECTION_TRANSLATION_FAULT;case 1:/*页式变换*/{ARMword l2addr, l2desc; /*二级描述符的地址,二级描述符*/l2addr = l1desc & 0xFFFFFC00;/*计算二级描述符的地址*/l2addr = (l2addr | ((virt_addr & 0x000FF000) >> 10)) & ~3;/*读取二级描述符*/l2desc = mem_read_word(state, l2addr);entry.virt_addr = virt_addr;entry.phys_addr = l2desc;entry.perms = l2desc & 0x00000FFC;entry.domain = (l1desc >> 5) & 0x0000000F;switch (l2desc & 3) {case 0:case 3:state->mmu.last_domain = entry.domain;return PAGE_TRANSLATION_FAULT;case 1:entry.mapping = TLB_LARGEPAGE; /*大页*/break;case 2:entry.mapping = TLB_SMALLPAGE; /*小页*/break;}}break;case 2:/*段变换*/entry.virt_addr = virt_addr;entry.phys_addr = l1desc;entry.perms = l1desc & 0x00000C0C;entry.domain = (l1desc >> 5) & 0x0000000F;entry.mapping = TLB_SECTION;break;}entry.virt_addr &= tlb_masks[entry.mapping];entry.phys_addr &= tlb_masks[entry.mapping];/* 更新tlb*/*tlb = &tlb_t->entrys[tlb_t->cycle];tlb_t->cycle = (tlb_t->cycle + 1) % tlb_t->num;**tlb = entry;}state->mmu.last_domain = (*tlb)->domain;return NO_FAULT;}

访问控制的实现(mmu/tlb.[ch])

接口函数

fault_t check_access(ARMul_State *state, ARMword virt_addr, tlb_entry_t *tlb, int read);

CACHE的实现(mmu/cache.[ch])

ARM系统中一般只实现组相联CACHE。组相联CACHE分成多组,一个组有包括多个CACHE line,一个32位地址可以由下图表示。

TagSetWord10

数据结构

Cache行

typedef struct cache_line_t{ARMword tag;/* cache line align address |bit2: last half dirtybit1: first half dirtybit0: cache valid flag*/ARMword pa;/*physical address*/ARMword *data; /*array of cached data*/}cache_line_t;#define TAG_VALID_FLAG 0x00000001#define TAG_FIRST_HALF_DIRTY 0x00000002#define TAG_LAST_HALF_DIRTY0x00000004

Cache 组

typedef struct cache_set_s{cache_line_t*lines;int cycle;/*used for cache line allocation*/}cache_set_t;

Cache 结构

typedef struct cache_s{intwidth;/*bytes in a line*/int way;/*way of set asscociate*/intset;/*num of set*/int w_mode;/*write back or write through*///int a_mode;/*alloc mode: random or round-bin*/cache_set_t *sets;}cache_t;

接口函数

     /*Cache的初始化和释放*/int mmu_cache_init(cache_t *cache_t, int width, int way, int set, int w_mode);void mmu_cache_exit(cache_t *cache_t);/*Cache 查找和分配*/cache_line_t * mmu_cache_search(ARMul_State *state, cache_t *cache_t, ARMword va);cache_line_t * mmu_cache_alloc(ARMul_State *state, cache_t *cache_t, ARMword va, ARMword pa);/*Cache 操作*/void mmu_cache_write_back(ARMul_State *state, cache_t *cache_t, cache_line_t *cache);void mmu_cache_clean(ARMul_State *state, cache_t *cache_t, ARMword va);void mmu_cache_invalidate(ARMul_State *state, cache_t *cache_t, ARMword va);void mmu_cache_invalidate_all(ARMul_State *state, cache_t *cache_t);void mmu_cache_soft_flush(ARMul_State *state, cache_t *cache_t, ARMword pa);

Write Buffer的实现(mmu/wb.[hc])

用一个循环队列模拟Write Buffer。

数据结构

typedef struct wb_entry_s{ARMword pa;//phy_addrARMbyte *data;//dataint nb;//number byte to write}wb_entry_t;typedef struct wb_s{int num; //wb_entry_t的总数int nb; //wb_entry_t中最大字节数int first;//循环队列头int last;//循环队列尾int used;//循环队列中已用wb_entry_t的个数wb_entry_t *entrys;}wb_t;

接口函数

    /*初始化和释放*/intmmu_wb_init(wb_t *wb_t, int num, int nb);voidmmu_wb_exit(wb_t *wb);/*数据入Write Buffer*/voidmmu_wb_write_bytess(ARMul_State *state, wb_t *wb_t, ARMword pa,ARMbyte *data, int n);/*将Write Buffer中数据全部写入内存*/voidmmu_wb_drain_all(ARMul_State *state, wb_t *wb_t);

Read Buffer的实现(mmu/rb.[hc])

数据结构

typedef struct rb_entry_s{ARMworddata[RB_WORD_NUM];//array to store dataARMwordva;//first word vainttype;//rb typefault_tfault;//fault set by rb alloc}rb_entry_t;typedef struct rb_s{int num;rb_entry_t *entrys;}rb_t;

接口函数

    /*初始化和释放*/int mmu_rb_init(rb_t *rb_t, int num);void mmu_rb_exit(rb_t *rb_t);rb_entry_t *mmu_rb_search(rb_t *rb_t, ARMword va);/*查找*/void mmu_rb_invalidate_entry(rb_t *rb_t, int i);/*无效*/void mmu_rb_invalidate_all(rb_t *rb_t);void mmu_rb_load(ARMul_State *state, rb_t *rb_t, int i_rb, int type, ARMword va);/*装入*/

5. 与具体CPU类型相关的MMU模拟子模块

与具体CPU类型相关的MMU模拟子模块建立在与具体CPU无关的MMU模拟实现子模块的基础上,通过实现具体CPU的mmu_ops_t,完成具体CPU的MMU模拟。以下详细介绍StrongARM的MMU实现。

6. StrongARM MMU的组成结构

StrongARM 的MMU分成指令和数据两部分。指令部分包括一个指令TLB和一个指令CACHE,数据部分包括一个数据TLB,两个数据CACHE(main CACHE和 mini CACHE),一个Write Buffer和一个Read Buffer。如图 0 3所示:


图 0-3 StrongARM数据访问功能部件图
图 0-3 StrongARM数据访问功能部件图

7. StrongARM mmu_ops_t的实现

mmu_ops_t sa_mmu_ops = {sa_mmu_init,sa_mmu_exit,sa_mmu_read_byte,sa_mmu_write_byte,sa_mmu_read_halfword,sa_mmu_write_halfword,sa_mmu_read_word,sa_mmu_write_word,sa_mmu_load_instr,sa_mmu_mcr,sa_mmu_mrc,};

SA MMU的初始化

typedef struct sa_mmu_desc_s{inti_tlb;cache_desc_t i_cache;int d_tlb;cache_desc_t main_d_cache;cache_desc_t mini_d_cache;intrb;wb_desc_twb;}sa_mmu_desc_t;static sa_mmu_desc_t sa11xx_mmu_desc ={32,{32, 32, 16, CACHE_WRITE_BACK},32,{32, 32, 8, CACHE_WRITE_BACK},{32, 2, 8, CACHE_WRITE_BACK},4,{8, 16}};typedefstruct sa_mmu_s{/*指令部分*/tlb_ti_tlb;/*指令tlb*/cache_ti_cache;/*指令cache*//*数据部分*/tlb_td_tlb;/*数据tlb*/cache_tmain_d_cache;/*main cache*/cache_tmini_d_cache;/*mini cache*/rb_trb_t;/*Read Buffer*/wb_twb_t;/*Write Buffer*/}sa_mmu_t;

int sa_mmu_init(ARMul_State *state);
sa_mmu_desc_t 是SA MMU参数描述的数据结构,sa_mmu_t实现SA MMU 的数据表示。sa_mmu_init根据sa11xx_mmu_desc,完成对state->u.sa_mmu的初始化。

SA MMU的数据读操作

static fault_tsa_mmu_read(ARMul_State *state, ARMword va, ARMword *data, ARMword datatype){fault_t fault;rb_entry_t *rb;tlb_entry_t *tlb;cache_line_t *cache;ARMword pa, real_va,temp,offset;/*根据CP15 process id register转换地址*/va = mmu_pid_va_map(va);real_va=va;if (MMU_Disabled){/*如果MMU关闭,直接从内存读取*/        if (datatype==ARM_BYTE_TYPE)                        *data = mem_read_byte(state, va);                else if (datatype==ARM_HALFWORD_TYPE)                        *data = mem_read_halfword(state, va);                else if (datatype==ARM_WORD_TYPE)                        *data = mem_read_word(state, va);                else {                        printf("SKYEYE:1 sa_mmu_read error: unknown data type %d/n",datatype);                        exit(-1);                }return 0;}/*align 异常检查*/if (((va & 3 ) && (datatype==ARM_WORD_TYPE) && MMU_Aligned) || /    ((va & 1 ) && (datatype==ARM_HALFWORD_TYPE) && MMU_Aligned) ){d_msg("align/n");return ALIGNMENT_FAULT;} // else        va &= ~(WORD_SIZE - 1);/*TTW得到tlb*/fault = translate(state, va, D_TLB(), &tlb);if (fault){d_msg("translate/n");return fault;}/*访问控制检查*/fault = check_access(state, va, tlb, 1);if (fault)return fault;/*首先在Read Buffer中查找数据*/rb = mmu_rb_search(RB(), va);if (rb){if (rb->fault)return rb->fault;*data = rb->data[(va & (rb_masks[rb->type]-1))>>WORD_SHT];goto datatrans; //return 0;};/*其次查找main cache*/cache = mmu_cache_search(state, MAIN_D_CACHE(), va);if (cache){*data = cache->data[va_cache_index(va, MAIN_D_CACHE())];goto datatrans; //return 0;}/*最后查找mini cache*/cache = mmu_cache_search(state, MINI_D_CACHE(), va);if (cache){*data = cache->data[va_cache_index(va, MINI_D_CACHE())];goto datatrans; //return 0;}/*计算物理地址*/pa = tlb_va_to_pa(tlb, va);   /*如果在[0xe0000000, 0xe8000000]区间,执行mmu_cache_soft_flush*/if ((pa >= 0xe0000000) && (pa < 0xe8000000)){if (tlb_c_flag(tlb)){if (tlb_b_flag(tlb)){mmu_cache_soft_flush(state, MAIN_D_CACHE(), pa);}else{mmu_cache_soft_flush(state, MINI_D_CACHE(), pa);}}return 0;}/*如果B=1,必须先清空Write Buffer*/if (tlb_b_flag(tlb))mmu_wb_drain_all(state, WB());if (tlb_c_flag(tlb) && MMU_DCacheEnabled){/*如果C=1,并且Data Cache开启,将数据读入Cache*/cache_t *cache_t;if (tlb_b_flag(tlb))cache_t = MAIN_D_CACHE();elsecache_t = MINI_D_CACHE();cache = mmu_cache_alloc(state, cache_t, va, pa);*data = cache->data[va_cache_index(va, cache_t)];}else{ /*直接从内存读取*/        if (datatype==ARM_BYTE_TYPE)                        *data = mem_read_byte(state, pa|(real_va&3));                else if (datatype==ARM_HALFWORD_TYPE)                        *data = mem_read_halfword(state, pa|(real_va&2));                else if (datatype==ARM_WORD_TYPE)                        *data = mem_read_word(state, pa);                else {                        printf("SKYEYE:2 sa_mmu_read error: unknown data type %d/n", datatype);                        exit(-1);                }return 0;}    /*根据读类型和Endian模式整理结果*/datatrans:        if (datatype==ARM_HALFWORD_TYPE){             temp = *data;offset = (((ARMword) state->bigendSig * 2) ^ (real_va & 2)) << 3; /* bit offset into the word */             *data= (temp>> offset) & 0xffff;        } else if (datatype==ARM_BYTE_TYPE) {             temp = *data;offset = (((ARMword) state->bigendSig * 3) ^ (real_va & 3)) << 3; /* bit offset into the word */             *data=(temp >> offset & 0xffL);        }end:return 0;}

SA MMU的mcr操作

static voidsa_mmu_mcr(ARMul_State *state, ARMword instr, ARMword value){mmu_regnum_t creg = BITS(16, 19) & 15;if (!strncmp(skyeye_config.cpu->cpu_arch_name, "armv4", 5) ){ switch (creg) {………………….case MMU_CACHE_OPS:sa_mmu_cache_ops(state, instr, value);break;case MMU_TLB_OPS:sa_mmu_tlb_ops(state, instr, value);break;case MMU_SA_RB_OPS:sa_mmu_rb_ops(state, instr, value);break;case MMU_SA_DEBUG:break;case MMU_SA_CP15_R15:break;case MMU_PID:state->mmu.process_id = value & 0x8e000000;break; default:printf("mmu_mcr wrote UNKNOWN - reg %d/n", creg);        break;}}}

sa_mmu_mcr实现SA MMU特定的cache, tlb, read buffer, write buffer等操作。

8. Memory系统的实现

SkyEye memory系统的模拟实现的基本思想是:

1. 地址划分,每个划分区域都提供对应的读写函数

2. 通过skyeye.conf配置文件配置memory地址区域

数据结构

typedef struct mem_bank_t {ARMword(*read_byte)(ARMul_State *state, ARMword addr);void(*write_byte)(ARMul_State *state, ARMword addr, ARMword data);ARMword(*read_halfword)(ARMul_State *state, ARMword addr);void(*write_halfword)(ARMul_State *state, ARMword addr, ARMword data);ARMword(*read_word)(ARMul_State *state, ARMword addr);void(*write_word)(ARMul_State *state, ARMword addr, ARMword data);unsigned longaddr, len;charfilename[MAX_STR];} mem_bank_t;typedef struct {int bank_num;int current_num;/*current num of bank*/mem_bank_t mem_banks[MAX_BANK];} mem_config_t;

配置函数

int do_mem_bank_option(skyeye_option_t *this_option, int num_params, const char *params[]); /*完成memory bank的配置*/

读写接口函数

ARMwordmem_read_byte(ARMul_State *state, ARMword addr);ARMwordmem_read_halfword(ARMul_State *state, ARMword addr);ARMwordmem_read_word(ARMul_State *state, ARMword addr);voidmem_write_byte(ARMul_State *state, ARMword addr, ARMword data);voidmem_write_halfword(ARMul_State *state, ARMword addr, ARMword data);voidmem_write_word(ARMul_State *state, ARMword addr, ARMword data);

实现方式都是先根据地址在mem_config_t中找到对应的mem_bank,再调用mem_bank对应的读写函数。

内存区域的读写函数

ARMwordreal_read_byte(ARMul_State *state, ARMword addr);voidreal_write_byte(ARMul_State *state, ARMword addr, ARMword data);ARMwordreal_read_halfword(ARMul_State *state, ARMword addr);voidreal_write_halfword(ARMul_State *state, ARMword addr, ARMword data);ARMwordreal_read_word(ARMul_State *state, ARMword addr);voidreal_write_word(ARMul_State *state, ARMword addr, ARMword data);

IO区域的读写函数

ARMwordio_read_byte(ARMul_State *state, ARMword addr);voidio_write_byte(ARMul_State *state, ARMword addr, ARMword data);ARMwordio_read_halfword(ARMul_State *state, ARMword addr);voidio_write_halfword(ARMul_State *state, ARMword addr, ARMword data);ARMwordio_read_word(ARMul_State *state, ARMword addr);voidio_write_word(ARMul_State *state, ARMword addr, ARMword data);

这些函数将IO的读写转换成模拟的CPU的IO读写



参考资料

  • 本文节自 《源码开放的嵌入式系统软件分析与实践--基于SkyEye和ARM开发平台》一书的第三章,对 SkyEye 开源项目感兴趣的可以阅读本书。
  • SkyEye硬件模拟平台, 第一部分: SkyEye 介绍
  • SkyEye硬件模拟平台,第二部分: 安装与使用
  • SkyEye硬件模拟平台,第三部分: 硬件仿真实现之一
  • SkyEye硬件模拟平台,第三部分: 硬件仿真实现之二
  • 在 developerWorks Linux 专区 可以找到更多为 Linux 开发者准备的参考资料。


关于作者

 

 

陈渝, 清华大学,通过 yuchen@tsinghua.edu.cn 可以和他联系。

原创粉丝点击