如何利用qemu搭建SOC protoype:80行代码实现一个Cortex M4 模拟器

10 篇文章 0 订阅
5 篇文章 9 订阅

源码文件

随着国内芯片公司越来越多,越来越多的底层程序员需要在pre silicon阶段就要开发代码。而在pre silicon阶段测试方式有多种:

方式优点缺点
RTL simulation可以验证最准确的硬件行为,可以测试SOC相关代码仿真速度非常慢,且rtl freeze之前硬件有bug
FPGA/ZEBU emulation可以验证部分硬件行为,速度相对RTL simulation快价格昂贵,难以布署大量测试,且有些硬件没法仿真
软件模拟器,如QEMU速度最快,可以布署大量测试没有现成的模拟器对应正在开发的SOC

很明显软件模拟器的优点缺点显而易见,如果开发人员可以在芯片开发前期定制出一个软件模拟器对应SOC 原型,那么可以大大提高pre silicon的效率。本文以一个小demo来演示如何在qemu源码基础上搭建一个最简单的Cortex M4的SOC。 这里就不讲如何编译qemu-system-arm了,网上有很多教程教学如何编译qemu-system-arm。后文简称该SOC为MY_SOC

1. MY_SOC Memory Map

Cortex M自带的system peripheral的地址这里就不列出来了,比如systick,nvic这些都是arm规定的,没法改,也没必要改。这里定义了最简单三个外设。一段FLASH用来跑代码,一段SRAM用来存数据,一个UART来打印。

起始地址结束地址
FLASH(定义了一段4M 的FLASH)0x000000000x003FFFFF
SRAM(定义了一段16M的SRAM)0x200000000x20FFFFFF
UART0(使用ARM PL011 IP)0x400000000x40000FFF

2. MY_SOC 源码

将my_soc.c 放在qemu/hw/arm目录下并且加入arm的makefile编译即可。先贴出全部代码再逐行解释。加上头文件include和宏定义一共77行代码,可见利用qemu能够很方便地搭出一个SOC模拟器原型。

#include "qemu/osdep.h"
  2 #include "qapi/error.h"
  3 #include "hw/arm/boot.h"
  4 #include "hw/boards.h"
  5 #include "qemu/log.h"
  6 #include "exec/address-spaces.h"
  7 #include "sysemu/sysemu.h"
  8 #include "hw/arm/armv7m.h"
  9 #include "hw/char/pl011.h"
 10 #include "hw/irq.h"
 11 #include "cpu.h"
 12 
 13 #define MY_SOC_FLASH_START  (0x0)
 14 #define MY_SOC_FLASH_SIZE   (4 * 1024 * 1024) //< 4M
 15 
 16 #define MY_SOC_SRAM_START    (0x20000000)
 17 #define MY_SOC_SRAM_SIZE     (16 * 1024 * 1024) //<16M
 18 
 19 #define PL011_UART0_START   (0x40000000)
 20 #define PL011_UART0_IRQn    (0)
 21 
 22 #define NUM_IRQ_LINES 64
 23 
 24 static void mysoc_init(MachineState *ms)
 25 {
 26     DeviceState *nvic;
 27 
 28     MemoryRegion *sram = g_new(MemoryRegion, 1);
 29     MemoryRegion *flash = g_new(MemoryRegion, 1);
 30     MemoryRegion *system_memory = get_system_memory();
 31     
 32     /* Flash programming is done via the SCU, so pretend it is ROM.  */
 33     memory_region_init_ram(flash, NULL, "mysoc.flash", MY_SOC_FLASH_SIZE, &error_fatal);
 34     memory_region_set_readonly(flash, true);
 35     memory_region_add_subregion(system_memory, MY_SOC_FLASH_START, flash);
 36     
 37     memory_region_init_ram(sram, NULL, "mysoc.sram", MY_SOC_SRAM_SIZE, &error_fatal);
 38     memory_region_add_subregion(system_memory, MY_SOC_SRAM_START, sram);
 39     
 40     nvic = qdev_create(NULL, TYPE_ARMV7M);
 41     qdev_prop_set_uint32(nvic, "num-irq", NUM_IRQ_LINES);
 42     qdev_prop_set_string(nvic, "cpu-type", ms->cpu_type);
 43     qdev_prop_set_bit(nvic, "enable-bitband", true);
 44     object_property_set_link(OBJECT(nvic), OBJECT(get_system_memory()), "memory", &error_abort);
 45     
 46     /* This will exit with an error if the user passed us a bad cpu_type */
 47     qdev_init_nofail(nvic);
 48     
 49     pl011_luminary_create(PL011_UART0_START , qdev_get_gpio_in(nvic, PL011_UART0_IRQn), serial_hd(0));
 50     armv7m_load_kernel(ARM_CPU(first_cpu), ms->kernel_filename, MY_SOC_FLASH_SIZE);
 51 }
 52 
 53 
 54 static void mysoc_class_init(ObjectClass *oc, void *data)
 55 {
 56     MachineClass *mc = MACHINE_CLASS(oc);
 57     printf("%s entry\n", __func__);
 58 
 59     mc->desc = "My SOC Cortex M4";
 60     mc->init = mysoc_init;
 61     mc->ignore_memory_transaction_failures = true;
 62     mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m4");
 63 }
 64 
 65 static const TypeInfo mysoc_type = {
 66     .name = MACHINE_TYPE_NAME("mysoc_evb"),
 67     .parent = TYPE_MACHINE,
 68     .class_init = mysoc_class_init,
 69 };
 70 
 71 static void mysoc_evb_init(void)
 72 {
 73     type_register_static(&mysoc_type);
 74 }
 75 
 76 type_init(mysoc_evb_init)
 77 

代码很简单,从下往上看。这里定义了一块板子叫mysoc_evb,通过type_init宏上报给qemu,之后qemu在启动地时候就能自动地调用mysoc_init初始化soc外设。
这里定义了描述字符串为My SOC Cortex M4,cpu类型是cortex-m4,板子名字是mysoc_evb。当这些结构体初始化完后,运行qemu-system-arm -machine help 就会出现我们自己地设备。
在这里插入图片描述

 54 static void mysoc_class_init(ObjectClass *oc, void *data)
 55 {
 56     MachineClass *mc = MACHINE_CLASS(oc);
 57     printf("%s entry\n", __func__);
 58 
 59     mc->desc = "My SOC Cortex M4";
 60     mc->init = mysoc_init;
 61     mc->ignore_memory_transaction_failures = true;
 62     mc->default_cpu_type = ARM_CPU_TYPE_NAME("cortex-m4");
 63 }
 64 
 65 static const TypeInfo mysoc_type = {
 66     .name = MACHINE_TYPE_NAME("mysoc_evb"),
 67     .parent = TYPE_MACHINE,
 68     .class_init = mysoc_class_init,
 69 };
 70 
 71 static void mysoc_evb_init(void)
 72 {
 73     type_register_static(&mysoc_type);
 74 }
 75 
 76 type_init(mysoc_evb_init)

所以最关键地函数就是mysoc_init这个函数,这个函数里做的事情非常简单,就是调用qemu的API创建相应的memory map以及设备就可以了。

  • 创建4M flash并设置为只读,定义了一个默认的加载文件mysoc.flash。如果mysoc.flash存在。那么qemu就会把该文件的数据读到这段flash中
 33     memory_region_init_ram(flash, NULL, "mysoc.flash", MY_SOC_FLASH_SIZE, &error_fatal);
 34     memory_region_set_readonly(flash, true);                                       
 35     memory_region_add_subregion(system_memory, MY_SOC_FLASH_START, flash);  
  • 创建一段16M的SRAM, 并定义默认文件mysoc.sram
 37     memory_region_init_ram(sram, NULL, "mysoc.sram", MY_SOC_SRAM_SIZE, &error_fatal);
 38     memory_region_add_subregion(system_memory, MY_SOC_SRAM_START, sram);
  • 配置NVIC, 设置64个外部中断槽,使能bitband。
 40     nvic = qdev_create(NULL, TYPE_ARMV7M);
 41     qdev_prop_set_uint32(nvic, "num-irq", NUM_IRQ_LINES);                          
 42     qdev_prop_set_string(nvic, "cpu-type", ms->cpu_type);                          
 43     qdev_prop_set_bit(nvic, "enable-bitband", true);
 44     object_property_set_link(OBJECT(nvic), OBJECT(get_system_memory()), "memory", &error_abort);
  • 添加一个PL011 UART,地址为0x40000000, 中断号为0。
 49     pl011_luminary_create(PL011_UART0_START , qdev_get_gpio_in(nvic, PL011_UART0_IRQn), serial_hd(0));
  • 设置kernel加载到flash中
 50     armv7m_load_kernel(ARM_CPU(first_cpu), ms->kernel_filename, MY_SOC_FLASH_SIZE);

至此MY_SOC就配置完了。我们写一段测试代码来测试这个模拟器能不能运行。

3. 测试代码

链接文件,把代码段放在FLASH中,data,bss段放在SRAM中

MEMORY
{
    FLASH (rx) : ORIGIN = 0x00000000, LENGTH = 4M
    SRAM (rwx) : ORIGIN = 0x20000000, LENGTH = 16M
}

SECTIONS
{
    .text :
    {
        _text = .;
        KEEP(*(.isr_vector))
        *(.text*)
        *(.rodata*)
        _etext = .;
    } > FLASH

    /DISCARD/ :
    {
        *(.ARM.exidx*)
        *(.gnu.linkonce.armexidx.*)
    }

    .data : AT(ADDR(.text) + SIZEOF(.text))
    {
        _data = .;
        *(vtable)
        *(.data*)
        _edata = .;
    } > SRAM

    .bss :
    {
        _bss = .;
        *(.bss*)
        *(COMMON)
        _ebss = .;
    } > SRAM

    . = ALIGN(32);           /*Not sure if this needs to be done, but why not.*/
    _stack_bottom = .;       /*Address of the bottom of the stack.*/
    . = . + 0x4000;          /*Allocate 4K for the Stack.*/
    _stack_top = 0x20008000;  /*Address of the top of the heap, also end of RAM.*/
}

startup.c 定义了栈指针和函数入口main

__attribute__ ((section(".isr_vector")))void (*g_pfnVectors[])(void) =
{
    0x20008000,                                      // StackPtr, set in RestetISR
    main,                               // The reset handler
    NmiSR,                                  // The NMI handl

main函数里就是往UART0里输出了Hello My SOC

#include <stdint.h>

static volatile uint32_t * const UART0_DR = (uint32_t *)0x40000000;

void puts(char *str)
{
    while(*str != 0) {
        *UART0_DR = *str;
        str++;
    }
}

int main()
{

    puts("Hello My SOC\n");
    while(1);
    return 0;
}

编译运行看结果
在这里插入图片描述

4 创建自己的IP

qemu/hw下面已经内置了许多可用的外设IP,如果有就可以简单地直接在像搭积木一样地配置一下就行。但如果在SOC开发中加入了一些自研的IP, 而此时在hw下面并没有,这个时候就需要自己加入IP的模拟器代码。本小节以一个最简单的读写寄存器来示范如何在qemu模拟器中加入自己的IP。

4.1 memory map

registeroffsetreset_valueR/W
ID00x00x54 (T)RO
ID10x40045 (E)RO
ID20x80x53 (S)RO
ID30xc0x54 (T)RO
ID40x100x20 (Space)RO
ID50x140x40 (I)RO
ID60x180x50 §RO
Test Reg0x1c0RO

寄存器功能很简单,前面7个是只读ID寄存器,最后一个是可读写的寄存器,没什么实际作用,就是demo用。没有中断产生。

4.2 my_test_ip源码

 25 #define MY_TEST_IP_START    (0x40001000)
 26 
 27 #define NUM_IRQ_LINES 64
 28 
 29 typedef struct {
 30     SysBusDevice parent_obj;
 31 
 32     qemu_irq irq;
 33     MemoryRegion iomem;
 34     uint32_t id0;       //T
 35     uint32_t id1;       //E
 36     uint32_t id2;       //S
 37     uint32_t id3;       //T
 38     uint32_t id4;       //
 39     uint32_t id5;       //I
 40     uint32_t id6;       //P
 41     uint32_t test_reg;
 42 } my_test_ip_state;
106 #define TEST_IP(obj) \
107     OBJECT_CHECK(my_test_ip_state, (obj), TYPE_TEST_IP)
108     
109 
110 static const VMStateDescription my_test_ip_vm = {
111     .name = "my_test_ip",
112     .version_id = 1,
113     .minimum_version_id = 1,
114     .fields = (VMStateField[]) {
115         VMSTATE_UINT32(id0, my_test_ip_state),
116         VMSTATE_UINT32(id1, my_test_ip_state),
117         VMSTATE_UINT32(id2, my_test_ip_state),
118         VMSTATE_UINT32(id3, my_test_ip_state),
119         VMSTATE_UINT32(id4, my_test_ip_state),
120         VMSTATE_UINT32(id5, my_test_ip_state),
121         VMSTATE_UINT32(id6, my_test_ip_state),
122         VMSTATE_UINT32(test_reg, my_test_ip_state),
123         VMSTATE_END_OF_LIST()
124     }
125 };
126 
127 static uint64_t my_test_ip_read(void *opaque, hwaddr offset,
128                                    unsigned size)
129 {   
130     uint64_t ret = 0;
131     my_test_ip_state *s = (my_test_ip_state *)opaque;
132     printf("%s hwaddr:%lx, size:%x\n", __func__, offset, size);
133     switch (offset) {
134     case 0x0:
135         ret = s->id0;
136         break;
137     case 0x4:
138         ret = s->id1; 
139         break;
140     case 0x8: 
141         ret = s->id2;
142         break;
143     case 0xc:
144         ret = s->id3;
145         break;
146     case 0x10:
147         ret = s->id4;
148         break;
149     case 0x14:
150         ret = s->id5;
151         break;
152     case 0x18:
153         ret = s->id6;
154         break;
155     case 0x1c:
156         ret = s->test_reg;
157         break;
158     }
159     return ret;
160 }
161 
162 static void my_test_ip_write(void *opaque, hwaddr offset,
163                                 uint64_t value, unsigned size)
164 {
165 
166     my_test_ip_state *s = (my_test_ip_state *)opaque;
167     printf("%s hwaddr:%lx, size:%x, value:%lx\n", __func__, offset, size, value);
168     switch(offset){
169     case 0x0:
170     case 0x4:
171     case 0x8:
172     case 0xc:
173     case 0x10:
174     case 0x14:
175     case 0x18:
176         printf("%s: cannot write the read only register\n", __func__);
177         break;
178     case 0x1c:
179         s->test_reg = value;
180         break;
181     }
182 }
183 
184 static const MemoryRegionOps my_test_ip_ops = {
185     .read = my_test_ip_read,
186     .write = my_test_ip_write,
187     .endianness = DEVICE_NATIVE_ENDIAN,
188 };
189 
190 static void my_test_ip_init(Object *obj)
191 {
192     printf("%s \n", __func__);
193 
194     my_test_ip_state *s = TEST_IP(obj);
195 
196     memory_region_init_io(&s->iomem, obj, &my_test_ip_ops, s, TYPE_TEST_IP, 0x1000);
197     sysbus_init_mmio(SYS_BUS_DEVICE(obj), &s->iomem);
198 
199     s->id0 = 0x54;
200     s->id1 = 0x45;
201     s->id2 = 0x53;
202     s->id3 = 0x54;
203     s->id4 = 0x20;
204     s->id5 = 0x49;
205     s->id6 = 0x50;
206     s->test_reg = 0;
207 }
208 
209 static void my_test_ip_class_init(ObjectClass *klass, void *data)
210 {
211     printf("%s \n", __func__);
212     DeviceClass *dc = DEVICE_CLASS(klass);
213     dc->vmsd = &my_test_ip_vm;
214 }
215 
216 static const TypeInfo my_test_ip = {
217     .name          = TYPE_TEST_IP,
218     .parent        = TYPE_SYS_BUS_DEVICE,
219     .instance_size = sizeof(my_test_ip_state),
220     .instance_init = my_test_ip_init,
221     .class_init    = my_test_ip_class_init,
222 };
223 
224 static void my_test_ip_types(void)
225 {
226     printf("%s \n", __func__);
227     type_register_static(&my_test_ip);
228 }
229 
230 type_init(my_test_ip_types)
                               

4.3 测试代码

#define MY_TEST_IP_START 0x40001000
typedef struct my_ip_tag {
    volatile uint32_t id0;
    volatile uint32_t id1;
    volatile uint32_t id2;
    volatile uint32_t id3;
    volatile uint32_t id4;
    volatile uint32_t id5;
    volatile uint32_t id6;
    volatile uint32_t test_reg;
} my_test_ip_t;

void my_test_ip_sample()
{
    my_test_ip_t *ip = (my_test_ip_t *)MY_TEST_IP_START;
    char id[8];
    id[0] = (char) ip->id0;
    id[1] = (char) ip->id1;
    id[2] = (char) ip->id2;
    id[3] = (char) ip->id3;
    id[4] = (char) ip->id4;
    id[5] = (char) ip->id5;
    id[6] = (char) ip->id6;
    id[7] = 0;

    puts(id);
    puts("\n");

    ip->test_reg = 0x00414141;
    id [0] = ip->test_reg & 0xff;
    id [1] = (ip->test_reg & 0xff00) >> 8;
    id [2] = (ip->test_reg & 0xff0000) >> 16;
    id [3] = (ip->test_reg & 0xff000000) >> 24;
    puts(id);
    puts("\n");
}

运行结果: 红框内是qemu打印的,蓝框是测试程序打印出来的,可以看到能顺利读出ID,同时能读写test reg。
在这里插入图片描述

  • 8
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 8
    评论
利用QEMU实现一个鸿蒙系统模拟器,可以按照以下步骤进操作: 1. 下载鸿蒙系统镜像文件 在华为官网或者其他可信的下载站点下载鸿蒙系统镜像文件。 2. 安装QEMU 在Linux系统中可以使用以下命令安装QEMU: ``` sudo apt-get install qemu ``` 3. 创建虚拟机 使用以下命令创建一个虚拟机(以鸿蒙系统版本为1.1.0为例): ``` qemu-system-arm -kernel helloworld.bin -M virt -cpu cortex-a15 -m 2048 -nographic ``` 其中,helloworld.bin是鸿蒙系统的镜像文件,-M virt指定了虚拟机使用的是virt机型,-cpu cortex-a15指定了CPU类型,-m 2048指定了虚拟机内存大小,-nographic指定了不使用图形界面。 4. 配置网络 使用以下命令为虚拟机配置网络: ``` qemu-system-arm -kernel helloworld.bin -M virt -cpu cortex-a15 -m 2048 -nographic -netdev user,id=mynet0,hostfwd=tcp::5555-:22 -device virtio-net-device,netdev=mynet0 ``` 其中,-netdev user指定了使用用户模式网络,-id mynet0指定了网络设备的名称,-hostfwd tcp::5555-:22指定了将本地主机的5555端口映射到虚拟机的22端口,-device virtio-net-device,netdev=mynet0指定了虚拟机使用virtio网络设备,并连接到mynet0网络设备。 5. 启动虚拟机 使用以下命令启动虚拟机: ``` qemu-system-arm -kernel helloworld.bin -M virt -cpu cortex-a15 -m 2048 -nographic -netdev user,id=mynet0,hostfwd=tcp::5555-:22 -device virtio-net-device,netdev=mynet0 ``` 启动后,可以使用ssh客户端连接到虚拟机的22端口,即本地主机的5555端口: ``` ssh root@localhost -p 5555 ``` 以上就是利用QEMU实现鸿蒙系统模拟器的基本步骤。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 8
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值