admin 管理员组

文章数量: 887016

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

文章目录

  • 1. MY_SOC Memory Map
  • 2. MY_SOC 源码
  • 3. 测试代码
  • 4 创建自己的IP
    • 4.1 memory map
    • 4.2 my_test_ip源码
    • 4.3 测试代码

源码文件

随着国内芯片公司越来越多,越来越多的底层程序员需要在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) //< 4M15 16 #define MY_SOC_SRAM_START    (0x20000000)17 #define MY_SOC_SRAM_SIZE     (16 * 1024 * 1024) //<16M18 19 #define PL011_UART0_START   (0x40000000)20 #define PL011_UART0_IRQn    (0)21 22 #define NUM_IRQ_LINES 6423 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 = 4MSRAM (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 RestetISRmain,                               // The reset handlerNmiSR,                                  // 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 6428 29 typedef struct {30     SysBusDevice parent_obj;31 32     qemu_irq irq;33     MemoryRegion iomem;34     uint32_t id0;       //T35     uint32_t id1;       //E36     uint32_t id2;       //S37     uint32_t id3;       //T38     uint32_t id4;       //39     uint32_t id5;       //I40     uint32_t id6;       //P41     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。

本文标签: 如何利用qemu搭建SOC protoype80行代码实现一个Cortex M4 模拟器