-
Notifications
You must be signed in to change notification settings - Fork 24
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[femu] add fucntional emulator bridge spike and t1devices
- Loading branch information
Showing
10 changed files
with
458 additions
and
3 deletions.
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,6 +9,7 @@ members = [ | |
"dpi_t1rocketemu", | ||
"dpi_common", | ||
"t1devices", | ||
"t1emu", | ||
] | ||
exclude = [ | ||
"spike_interfaces" | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
#include <type_traits> | ||
#include <iostream> | ||
#include <iomanip> | ||
|
||
#include "cfg.h" | ||
#include "decode_macros.h" | ||
#include "disasm.h" | ||
#include "mmu.h" | ||
#include "processor.h" | ||
#include "simif.h" | ||
|
||
static_assert(std::is_same_v<reg_t, uint64_t>); | ||
|
||
struct t1emu_memory_vtable_t { | ||
uint8_t* (*addr_to_mem)(void* memory, reg_t addr); | ||
int (*mmio_load)(void* memory, reg_t addr, size_t len, uint8_t* bytes); | ||
int (*mmio_store)(void* memory, reg_t addr, size_t len, const uint8_t* bytes); | ||
}; | ||
|
||
class t1emu_sim_t: public simif_t { | ||
void* m_memory; | ||
t1emu_memory_vtable_t m_vtable; | ||
|
||
cfg_t m_cfg; | ||
isa_parser_t m_isa_parser; | ||
processor_t m_proc; | ||
|
||
public: | ||
t1emu_sim_t( | ||
void* memory, | ||
t1emu_memory_vtable_t const* vtable, | ||
cfg_t cfg, | ||
size_t vlen | ||
): | ||
m_memory(memory), | ||
m_vtable(*vtable), | ||
m_cfg(std::move(cfg)), | ||
m_isa_parser(m_cfg.isa, m_cfg.priv), | ||
m_proc( | ||
&m_isa_parser, | ||
&m_cfg, | ||
this, | ||
0, | ||
true, | ||
nullptr, | ||
std::cerr | ||
) | ||
{ | ||
m_proc.VU.lane_num = vlen / 32; | ||
m_proc.VU.lane_granularity = 32; | ||
} | ||
|
||
char* addr_to_mem(reg_t addr) override { | ||
return (char*)m_vtable.addr_to_mem(m_memory, addr); | ||
} | ||
|
||
bool mmio_fetch(reg_t addr, size_t len, uint8_t *bytes) override { | ||
// TODO: currently inst fetch is disallowed on mmio | ||
return false; | ||
} | ||
|
||
bool mmio_load(reg_t addr, size_t len, uint8_t *bytes) override { | ||
return (bool)m_vtable.mmio_load(m_memory, addr, len, bytes); | ||
} | ||
|
||
bool mmio_store(reg_t addr, size_t len, const uint8_t *bytes) override { | ||
return (bool)m_vtable.mmio_store(m_memory, addr, len, bytes); | ||
} | ||
|
||
virtual void proc_reset(unsigned id) override { | ||
// do nothing | ||
} | ||
|
||
virtual const char* get_symbol(uint64_t addr) override { | ||
throw std::logic_error("t1emu_sim_t::get_symbol not implemented"); | ||
} | ||
|
||
const cfg_t& get_cfg() const override { | ||
return m_cfg; | ||
} | ||
|
||
const std::map<size_t, processor_t *> & | ||
get_harts() const override { | ||
throw std::logic_error("t1emu_sim_t::get_harts not implemented"); | ||
} | ||
|
||
void reset_with_pc(reg_t new_pc) { | ||
m_proc.reset(); | ||
m_proc.check_pc_alignment(new_pc); | ||
m_proc.get_state()->pc = new_pc; | ||
} | ||
|
||
void step_one() { | ||
reg_t pc = m_proc.get_state()->pc; | ||
mmu_t* mmu = m_proc.get_mmu(); | ||
state_t* state = m_proc.get_state(); | ||
|
||
try { | ||
insn_fetch_t fetch = mmu->load_insn(pc); | ||
reg_t new_pc = fetch.func(&m_proc, fetch.insn, pc); | ||
printf("pc=%08lx, new_pc=%08lx\n", pc, new_pc); | ||
if ((new_pc & 1) == 0) { | ||
state->pc = new_pc; | ||
} else { | ||
switch (new_pc) { | ||
case PC_SERIALIZE_BEFORE: state->serialized = true; break; | ||
case PC_SERIALIZE_AFTER: break; | ||
default: throw std::logic_error("invalid PC after fetch.func"); | ||
} | ||
} | ||
} catch (trap_t &trap) { | ||
std::cerr << "Error: spike trapped with " << trap.name() | ||
<< " (tval=" << std::uppercase << std::setfill('0') | ||
<< std::setw(8) << std::hex << trap.get_tval() | ||
<< ", tval2=" << std::setw(8) << std::hex << trap.get_tval2() | ||
<< ", tinst=" << std::setw(8) << std::hex << trap.get_tinst() | ||
<< ")" << std::endl; | ||
throw; | ||
} catch (std::exception& e) { | ||
std::cerr << e.what() << std::endl; | ||
throw; | ||
} | ||
} | ||
}; | ||
|
||
extern "C" { | ||
t1emu_sim_t* t1emu_create( | ||
void* memory, | ||
t1emu_memory_vtable_t const* vtable, | ||
const char* isa_set, | ||
size_t vlen | ||
) { | ||
cfg_t cfg; | ||
cfg.isa = strdup(isa_set); | ||
cfg.priv = "M"; | ||
|
||
return new t1emu_sim_t(memory, vtable, cfg, vlen); | ||
} | ||
void t1emu_destroy(t1emu_sim_t* emu) { | ||
delete emu; | ||
} | ||
void t1emu_reset_with_pc(t1emu_sim_t* emu, reg_t new_pc) { | ||
emu->reset_with_pc(new_pc); | ||
} | ||
void t1emu_step_one(t1emu_sim_t* emu) { | ||
emu->step_one(); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
[package] | ||
name = "t1emu" | ||
edition = "2021" | ||
version.workspace = true | ||
|
||
[dependencies] | ||
t1devices = { path = "../t1devices" } | ||
anyhow.workspace = true | ||
clap.workspace = true | ||
elf = "0.7.4" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
use std::env; | ||
|
||
fn main() { | ||
println!( | ||
"cargo::rustc-link-search=native={}", | ||
env::var("SPIKE_LIB_DIR").expect("SPIKE_LIB_DIR should be set") | ||
); | ||
println!( | ||
"cargo::rustc-link-search=native={}", | ||
env::var("SPIKE_INTERFACES_LIB_DIR").expect("SPIKE_INTERFACES_LIB_DIR should be set") | ||
); | ||
|
||
println!("cargo::rerun-if-env-changed=SPIKE_LIB_DIR"); | ||
println!("cargo::rerun-if-env-changed=SPIKE_INTERFACES_LIB_DIR"); | ||
|
||
println!("cargo::rustc-link-lib=static=spike_interfaces"); | ||
|
||
println!("cargo::rustc-link-lib=static=riscv"); | ||
println!("cargo::rustc-link-lib=static=softfloat"); | ||
println!("cargo::rustc-link-lib=static=disasm"); | ||
println!("cargo::rustc-link-lib=static=fesvr"); | ||
println!("cargo::rustc-link-lib=static=fdt"); | ||
|
||
println!("cargo::rustc-link-lib=stdc++"); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,114 @@ | ||
use std::{ | ||
fs::File, | ||
os::unix::fs::FileExt as _, | ||
path::{Path, PathBuf}, | ||
}; | ||
|
||
use anyhow::Context as _; | ||
use clap::Parser; | ||
use elf::{ | ||
abi::{EM_RISCV, ET_EXEC, PT_LOAD}, | ||
endian::LittleEndian, | ||
ElfStream, | ||
}; | ||
use spike::{Spike, SpikeMemory}; | ||
use t1devices::AddressSpace; | ||
|
||
mod spike; | ||
|
||
#[derive(Parser)] | ||
pub struct CliArgs { | ||
/// Path to the ELF file | ||
#[arg(long)] | ||
pub elf_file: PathBuf, | ||
|
||
/// ISA config | ||
#[arg(long)] | ||
pub isa: String, | ||
|
||
/// vlen config | ||
#[arg(long)] | ||
pub vlen: u32, | ||
} | ||
|
||
fn main() -> anyhow::Result<()> { | ||
let args = CliArgs::parse(); | ||
let (memory, exit_flag) = t1devices::create_emu_addrspace(fake_get_cycle); | ||
let mut memory = Memory::new(memory); | ||
let elf_entry = memory.load_elf(&args.elf_file)?; | ||
let mut emu = Spike::new(&args.isa, args.vlen as usize, memory); | ||
dbg!(elf_entry); | ||
emu.reset_with_pc(elf_entry); | ||
while !exit_flag.is_finish() { | ||
emu.step_one(); | ||
} | ||
Ok(()) | ||
} | ||
|
||
fn fake_get_cycle() -> u64 { | ||
0 | ||
} | ||
|
||
struct Memory { | ||
mem: t1devices::AddressSpace, | ||
} | ||
|
||
impl Memory { | ||
pub fn new(mem: AddressSpace) -> Self { | ||
Memory { mem } | ||
} | ||
|
||
pub fn load_elf(&mut self, path: &Path) -> anyhow::Result<u64> { | ||
let mem = &mut self.mem; | ||
let file = File::open(path).with_context(|| "reading ELF file")?; | ||
let mut elf: ElfStream<LittleEndian, _> = | ||
ElfStream::open_stream(&file).with_context(|| "parsing ELF file")?; | ||
|
||
if elf.ehdr.e_machine != EM_RISCV { | ||
anyhow::bail!("ELF is not in RISC-V"); | ||
} | ||
|
||
if elf.ehdr.e_type != ET_EXEC { | ||
anyhow::bail!("ELF is not an executable"); | ||
} | ||
|
||
if elf.ehdr.e_phnum == 0 { | ||
anyhow::bail!("ELF has zero size program header"); | ||
} | ||
|
||
let mut load_buffer = Vec::new(); | ||
elf.segments().iter().filter(|phdr| phdr.p_type == PT_LOAD).for_each(|phdr| { | ||
let vaddr: usize = phdr.p_vaddr.try_into().expect("fail converting vaddr(u64) to usize"); | ||
let filesz: usize = phdr.p_filesz.try_into().expect("fail converting p_filesz(u64) to usize"); | ||
|
||
// Load file start from offset into given mem slice | ||
// The `offset` of the read_at method is relative to the start of the file and thus independent from the current cursor. | ||
load_buffer.resize(filesz, 0u8); | ||
file.read_at(load_buffer.as_mut_slice(), phdr.p_offset).unwrap_or_else(|err| { | ||
panic!( | ||
"fail reading ELF into mem with vaddr={}, filesz={}, offset={}. Error detail: {}", | ||
vaddr, filesz, phdr.p_offset, err | ||
) | ||
}); | ||
mem.write_mem(vaddr as u32, load_buffer.len() as u32, &load_buffer); | ||
}); | ||
|
||
Ok(elf.ehdr.e_entry) | ||
} | ||
} | ||
|
||
impl SpikeMemory for Memory { | ||
fn addr_to_mem(&mut self, addr: u64) -> Option<&mut u8> { | ||
self.mem.addr_to_mem(addr.try_into().unwrap()) | ||
} | ||
|
||
fn mmio_load(&mut self, addr: u64, data: &mut [u8]) -> bool { | ||
self.mem.read_mem(addr.try_into().unwrap(), data.len() as u32, data); | ||
true | ||
} | ||
|
||
fn mmio_store(&mut self, addr: u64, data: &[u8]) -> bool { | ||
self.mem.write_mem(addr.try_into().unwrap(), data.len() as u32, data); | ||
true | ||
} | ||
} |
Oops, something went wrong.