Skip to content

Commit

Permalink
[femu] add fucntional emulator bridge spike and t1devices
Browse files Browse the repository at this point in the history
  • Loading branch information
FanShupei committed Jan 25, 2025
1 parent 575fd77 commit 7bbed51
Show file tree
Hide file tree
Showing 10 changed files with 458 additions and 3 deletions.
10 changes: 10 additions & 0 deletions difftest/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions difftest/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ members = [
"dpi_t1rocketemu",
"dpi_common",
"t1devices",
"t1emu",
]
exclude = [
"spike_interfaces"
Expand Down
22 changes: 20 additions & 2 deletions difftest/default.nix
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
}:

assert let
available = [ "dpi_t1emu" "dpi_t1rocketemu" "offline_t1emu" "offline_t1rocketemu" ];
available = [ "dpi_t1emu" "dpi_t1rocketemu" "offline_t1emu" "offline_t1rocketemu" "t1emu" ];
in
lib.assertMsg (lib.elem moduleType available) "moduleType is not in ${lib.concatStringsSep ", " available}";

Expand All @@ -27,6 +27,7 @@ let
./offline_t1emu
./offline_t1rocketemu
./t1devices
./t1emu
./Cargo.lock
./Cargo.toml
./.rustfmt.toml
Expand Down Expand Up @@ -58,7 +59,7 @@ if (lib.hasPrefix "dpi" moduleType) then
dpiLibPath = "/lib/libdpi_${moduleType}.a";
};
}
else
else if (lib.hasPrefix "offline" moduleType) then
assert lib.assertMsg (emuType == "") "emuType shall not be set for offline";
rustPlatform.buildRustPackage {
name = outputName;
Expand Down Expand Up @@ -86,3 +87,20 @@ else

meta.mainProgram = "offline";
}
else
rustPlatform.buildRustPackage {
name = outputName;
src = rustSrc;

buildFeatures = [ ];
buildAndTestSubdir = "./${moduleType}";

env = {
SPIKE_LIB_DIR = "${libspike}/lib";
SPIKE_INTERFACES_LIB_DIR = "${libspike_interfaces}/lib";
};

cargoLock = {
lockFile = ./Cargo.lock;
};
}
2 changes: 1 addition & 1 deletion difftest/spike_interfaces/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)

find_package(libspike REQUIRED)

add_library(${CMAKE_PROJECT_NAME} STATIC spike_interfaces.cc)
add_library(${CMAKE_PROJECT_NAME} STATIC spike_interfaces.cc t1emu.cc)

target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC libspike)

Expand Down
148 changes: 148 additions & 0 deletions difftest/spike_interfaces/t1emu.cc
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();
}
}
10 changes: 10 additions & 0 deletions difftest/t1emu/Cargo.toml
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"
25 changes: 25 additions & 0 deletions difftest/t1emu/build.rs
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++");
}
114 changes: 114 additions & 0 deletions difftest/t1emu/src/main.rs
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
}
}
Loading

0 comments on commit 7bbed51

Please sign in to comment.