weiqi7777

spike中增加uart外设

0
阅读(95) 评论(0)

    spike是官方发布的riscv的指令模拟器。但是在spike中,支持的外设比较少,有如下一些:

  • irom:存放复位之后的boot代码
  • clinc:实现中断
  • ram:模拟DRAM

如果我们想要做一个soc级的riscv指令模拟器,那么我们就需要往spike中,增加我们自己的外设。

下面介绍下,如何往spike中,增加uart外设。如果能成功增加uart外设,那么增加其他外设,也同样是可以的。

一、实现uart外设

    在spike中外设,均要从类 abstract_device_t 中集成。该类,定义在 riscv/devices.h 中。

class abstract_device_t {

public:

virtual bool load(reg_t addr, size_t len, uint8_t* bytes) = 0;

virtual bool store(reg_t addr, size_t len, const uint8_t* bytes) = 0;

virtual ~abstract_device_t() {}

}

    这个类中,主要实现了2个函数:

  • load函数,负责实现core读取该外设时的行为
  • store函数,负责实现core写入该外设时的行为

    两个函数,如果返回1,就表示访问成功。如果返回0,就表示访问不成功。

    实现uart_t类,代码如下:

#define UART_SIZE 0x1000

class uart_t : public abstract_device_t {

public:

uart_t() {

std::cout << "create uart component" << std::endl;

}

~uart_t() {}

bool load(reg_t addr, size_t len, uint8_t* bytes);

bool store(reg_t addr, size_t len, const uint8_t* bytes);

size_t size() {return UART_SIZE;}

private:

uint32_t control_register=8;

uint32_t reserve_register=7;

uint8_t data_register=5;

std::string s_data;

}

    uart外设的大小是 0x1000。有如下寄存器:

  • control_register: 控制寄存器
  • data_register: 数据寄存器
  • reserve_register:除了 控制/数据 寄存器之外的所有寄存器

s_data,用于保存写入到uart数据寄存器的信息。因为实现了行缓冲,当往数据寄存器写入回车时,uart外设就会将s_data中的数据全部打印出来。

load,store的函数,实现如下:

#define DATA_REG_OFFSET 0x00

#define CON_REG_OFFSET 0x04

bool uart_t::load(reg_t addr, size_t len, uint8_t *bytes)

{

if(addr == CON_REG_OFFSET)

memcpy(bytes, (uint8_t*)&control_register, len);

else

memcpy(bytes, (uint8_t*)&reserve_register, len);

return true;

}

bool uart_t::store(reg_t addr, size_t len, const uint8_t *bytes)

{

if(addr == DATA_REG_OFFSET) {

memcpy((uint8_t*)&data_register, bytes, 1);

if( data_register == '\n') {

std::cout << s_data << std::endl;

s_data.clear();

}

else {

s_data += data_register;

}

}

else if(addr == CON_REG_OFFSET) {

memcpy((uint8_t*)&control_register, bytes, 4);

}

return true;

}

    在riscv-isa-sim跟目录,创建device目录,将实现的uart.h和uart.c 文件,放到这个目录下。

    另外实现soc_components.h文件,用来include 我们自己实现的外设。

#ifndef __SOC_COMPONENTS__

#define __SOC_COMPONENTS__

#include "uart.h"

#endif

    在实现 soc_devices.h 文件,内容如下,用来往spike中,加入自己的外设。

#define UART_BASE 0x40000000

uart_t *uart;

uart = new uart_t();

bus.add_device(reg_t(UART_BASE), uart);

二、修改configure流程

    因为有自己增加目录device,并且放置了我们自己实现的文件。需要修改configure流程,实现能够将增加的文件,编译到spike中。

    首先修改根目录下的 configure.ac文件,在 MCPPBS_SUBPOJECTS选项中,增加device,表示编译的时候,要编译这个目录下的文件。

    将riscv目录下的riscv.ac文件,拷贝到device目录下,并且重命名为device.ac。

    创建 device.mk.in 文件,填入如下内容:

device_subproject_deps = \

    riscv

 

device_install_prog_srcs =

 

device_hdrs = \

    uart.h \

    soc_devices.h \

    soc_components.h

 

device_srcs = \

    uart.cc

    修改riscv目录下的riscv.mk.in文件,在riscv_subproject_deps中,加入device,表示riscv这个目录编译时候,会依赖device这个目录下的源文件。

    在加入如下内容,将uart.cc加入到 riscv_srcs 变量中。我这里,定义了 soc_devices_srcs 中间变量,方便将来增加其他外设。

    上述修改之后,使用 autoconf configure.ac > configure, 更新configure文件。这样,之后编译的时候,才会将我们增加的代码,给编译进去。

三、修改sim_t

    spike的仿真控制,是由sim_t这个类控制的。这个类,实现在 riscv/sim.cc中。

    在sim_t的构造函数的最后,增加一行代码:

sim_t::sim_t(const char* isa, size_t nprocs, bool halted, reg_t start_pc,

std::vector<std::pair<reg_t, mem_t*>> mems,

const std::vector<std::string>& args,

std::vector<int> const hartids,

const debug_module_config_t &dm_config)

: htif_t(args), mems(mems), procs(std::max(nprocs, size_t(1))),

start_pc(start_pc), current_step(0), current_proc(0), debug(false),

histogram_enabled(false), dtb_enabled(true), remote_bitbang(NULL),

debug_module(this, dm_config)

{

signal(SIGINT, &handle_signal);

 

for (auto& x : mems)

bus.add_device(x.first, x.second);

 

debug_module.add_device(&bus);

 

debug_mmu = new mmu_t(this, NULL);

 

if (hartids.size() == 0) {

for (size_t i = 0; i < procs.size(); i++) {

procs[i] = new processor_t(isa, this, i, halted);

}

}

else {

if (hartids.size() != procs.size()) {

std::cerr << "Number of specified hartids doesn't match number of processors" << strerror(errno) << std::endl;

exit(1);

}

for (size_t i = 0; i < procs.size(); i++) {

procs[i] = new processor_t(isa, this, hartids[i], halted);

}

}

 

clint.reset(new clint_t(procs));

bus.add_device(CLINT_BASE, clint.get());

 

// add our soc device

#include "soc_devices.h"

}

    增加的 #include "soc_devices.h" 这行代码,就将我们之前实现的,往spike中增加外设的代码,给包含在这,从而实现增加外设。

四、spike访问外设原理

    sim_t类中,有bus_t bus成员变量。

class bus_t : public abstract_device_t {

public:

bool load(reg_t addr, size_t len, uint8_t* bytes);

bool store(reg_t addr, size_t len, const uint8_t* bytes);

void add_device(reg_t addr, abstract_device_t* dev);

std::pair<reg_t, abstract_device_t*> find_device(reg_t addr);

private:

std::map<reg_t, abstract_device_t*> devices;

}

    实现的外设,均需要挂载到这个bus上面,这样sim_t,最终,可以通过访问该bus的load或者store函数,来实现core的load和store。

    bus_t中,有devices这个关联数组,key是外设的首地址,键值是 abstract_device_t类指令。所以之前说,外设,需要从abstract_device_t中继承。

    bus_t中的add_device,就往bus上,增加一个外设。所以之前soc_devices.h程序中,就通过调用bus.add_device函数,将我们实现的uart外设,挂载到bus上。从而让之后,core可以访问。

    在sim_t中,实现了mmio_load和mmio_store函数。

bool sim_t::mmio_load(reg_t addr, size_t len, uint8_t* bytes)

{

if (addr + len < addr)

return false;

return bus.load(addr, len, bytes);

}

bool sim_t::mmio_store(reg_t addr, size_t len, const uint8_t* bytes)

{

if (addr + len < addr)

return false;

return bus.store(addr, len, bytes);

    当core执行一条load指令时,最终,会调用sim_t的mmio_load函数,而mmio_load函数,调用bus_t的load函数,bus_t根据传入的地址,确定是哪一个外设访问,在调用该外设的load函数,从而实现外设的load访问。同理,可以推出core执行一条store指令,是如何工作的。

五、总结

    通过以上的流程,就往spike中增加了uart外设。当我们能够成功增加uart外设,那么理论上,增加其他的外设,也都不是难事了。

更多内容,访问我的个人网站:www.lujun.org.cn