Skip to content

Commit 7bbed51

Browse files
committed
[femu] add fucntional emulator bridge spike and t1devices
1 parent 575fd77 commit 7bbed51

File tree

10 files changed

+458
-3
lines changed

10 files changed

+458
-3
lines changed

difftest/Cargo.lock

+10
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

difftest/Cargo.toml

+1
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ members = [
99
"dpi_t1rocketemu",
1010
"dpi_common",
1111
"t1devices",
12+
"t1emu",
1213
]
1314
exclude = [
1415
"spike_interfaces"

difftest/default.nix

+20-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@
1111
}:
1212

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

@@ -27,6 +27,7 @@ let
2727
./offline_t1emu
2828
./offline_t1rocketemu
2929
./t1devices
30+
./t1emu
3031
./Cargo.lock
3132
./Cargo.toml
3233
./.rustfmt.toml
@@ -58,7 +59,7 @@ if (lib.hasPrefix "dpi" moduleType) then
5859
dpiLibPath = "/lib/libdpi_${moduleType}.a";
5960
};
6061
}
61-
else
62+
else if (lib.hasPrefix "offline" moduleType) then
6263
assert lib.assertMsg (emuType == "") "emuType shall not be set for offline";
6364
rustPlatform.buildRustPackage {
6465
name = outputName;
@@ -86,3 +87,20 @@ else
8687

8788
meta.mainProgram = "offline";
8889
}
90+
else
91+
rustPlatform.buildRustPackage {
92+
name = outputName;
93+
src = rustSrc;
94+
95+
buildFeatures = [ ];
96+
buildAndTestSubdir = "./${moduleType}";
97+
98+
env = {
99+
SPIKE_LIB_DIR = "${libspike}/lib";
100+
SPIKE_INTERFACES_LIB_DIR = "${libspike_interfaces}/lib";
101+
};
102+
103+
cargoLock = {
104+
lockFile = ./Cargo.lock;
105+
};
106+
}

difftest/spike_interfaces/CMakeLists.txt

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ set(CMAKE_CXX_STANDARD 17)
44

55
find_package(libspike REQUIRED)
66

7-
add_library(${CMAKE_PROJECT_NAME} STATIC spike_interfaces.cc)
7+
add_library(${CMAKE_PROJECT_NAME} STATIC spike_interfaces.cc t1emu.cc)
88

99
target_link_libraries(${CMAKE_PROJECT_NAME} PUBLIC libspike)
1010

difftest/spike_interfaces/t1emu.cc

+148
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
#include <type_traits>
2+
#include <iostream>
3+
#include <iomanip>
4+
5+
#include "cfg.h"
6+
#include "decode_macros.h"
7+
#include "disasm.h"
8+
#include "mmu.h"
9+
#include "processor.h"
10+
#include "simif.h"
11+
12+
static_assert(std::is_same_v<reg_t, uint64_t>);
13+
14+
struct t1emu_memory_vtable_t {
15+
uint8_t* (*addr_to_mem)(void* memory, reg_t addr);
16+
int (*mmio_load)(void* memory, reg_t addr, size_t len, uint8_t* bytes);
17+
int (*mmio_store)(void* memory, reg_t addr, size_t len, const uint8_t* bytes);
18+
};
19+
20+
class t1emu_sim_t: public simif_t {
21+
void* m_memory;
22+
t1emu_memory_vtable_t m_vtable;
23+
24+
cfg_t m_cfg;
25+
isa_parser_t m_isa_parser;
26+
processor_t m_proc;
27+
28+
public:
29+
t1emu_sim_t(
30+
void* memory,
31+
t1emu_memory_vtable_t const* vtable,
32+
cfg_t cfg,
33+
size_t vlen
34+
):
35+
m_memory(memory),
36+
m_vtable(*vtable),
37+
m_cfg(std::move(cfg)),
38+
m_isa_parser(m_cfg.isa, m_cfg.priv),
39+
m_proc(
40+
&m_isa_parser,
41+
&m_cfg,
42+
this,
43+
0,
44+
true,
45+
nullptr,
46+
std::cerr
47+
)
48+
{
49+
m_proc.VU.lane_num = vlen / 32;
50+
m_proc.VU.lane_granularity = 32;
51+
}
52+
53+
char* addr_to_mem(reg_t addr) override {
54+
return (char*)m_vtable.addr_to_mem(m_memory, addr);
55+
}
56+
57+
bool mmio_fetch(reg_t addr, size_t len, uint8_t *bytes) override {
58+
// TODO: currently inst fetch is disallowed on mmio
59+
return false;
60+
}
61+
62+
bool mmio_load(reg_t addr, size_t len, uint8_t *bytes) override {
63+
return (bool)m_vtable.mmio_load(m_memory, addr, len, bytes);
64+
}
65+
66+
bool mmio_store(reg_t addr, size_t len, const uint8_t *bytes) override {
67+
return (bool)m_vtable.mmio_store(m_memory, addr, len, bytes);
68+
}
69+
70+
virtual void proc_reset(unsigned id) override {
71+
// do nothing
72+
}
73+
74+
virtual const char* get_symbol(uint64_t addr) override {
75+
throw std::logic_error("t1emu_sim_t::get_symbol not implemented");
76+
}
77+
78+
const cfg_t& get_cfg() const override {
79+
return m_cfg;
80+
}
81+
82+
const std::map<size_t, processor_t *> &
83+
get_harts() const override {
84+
throw std::logic_error("t1emu_sim_t::get_harts not implemented");
85+
}
86+
87+
void reset_with_pc(reg_t new_pc) {
88+
m_proc.reset();
89+
m_proc.check_pc_alignment(new_pc);
90+
m_proc.get_state()->pc = new_pc;
91+
}
92+
93+
void step_one() {
94+
reg_t pc = m_proc.get_state()->pc;
95+
mmu_t* mmu = m_proc.get_mmu();
96+
state_t* state = m_proc.get_state();
97+
98+
try {
99+
insn_fetch_t fetch = mmu->load_insn(pc);
100+
reg_t new_pc = fetch.func(&m_proc, fetch.insn, pc);
101+
printf("pc=%08lx, new_pc=%08lx\n", pc, new_pc);
102+
if ((new_pc & 1) == 0) {
103+
state->pc = new_pc;
104+
} else {
105+
switch (new_pc) {
106+
case PC_SERIALIZE_BEFORE: state->serialized = true; break;
107+
case PC_SERIALIZE_AFTER: break;
108+
default: throw std::logic_error("invalid PC after fetch.func");
109+
}
110+
}
111+
} catch (trap_t &trap) {
112+
std::cerr << "Error: spike trapped with " << trap.name()
113+
<< " (tval=" << std::uppercase << std::setfill('0')
114+
<< std::setw(8) << std::hex << trap.get_tval()
115+
<< ", tval2=" << std::setw(8) << std::hex << trap.get_tval2()
116+
<< ", tinst=" << std::setw(8) << std::hex << trap.get_tinst()
117+
<< ")" << std::endl;
118+
throw;
119+
} catch (std::exception& e) {
120+
std::cerr << e.what() << std::endl;
121+
throw;
122+
}
123+
}
124+
};
125+
126+
extern "C" {
127+
t1emu_sim_t* t1emu_create(
128+
void* memory,
129+
t1emu_memory_vtable_t const* vtable,
130+
const char* isa_set,
131+
size_t vlen
132+
) {
133+
cfg_t cfg;
134+
cfg.isa = strdup(isa_set);
135+
cfg.priv = "M";
136+
137+
return new t1emu_sim_t(memory, vtable, cfg, vlen);
138+
}
139+
void t1emu_destroy(t1emu_sim_t* emu) {
140+
delete emu;
141+
}
142+
void t1emu_reset_with_pc(t1emu_sim_t* emu, reg_t new_pc) {
143+
emu->reset_with_pc(new_pc);
144+
}
145+
void t1emu_step_one(t1emu_sim_t* emu) {
146+
emu->step_one();
147+
}
148+
}

difftest/t1emu/Cargo.toml

+10
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
[package]
2+
name = "t1emu"
3+
edition = "2021"
4+
version.workspace = true
5+
6+
[dependencies]
7+
t1devices = { path = "../t1devices" }
8+
anyhow.workspace = true
9+
clap.workspace = true
10+
elf = "0.7.4"

difftest/t1emu/build.rs

+25
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
use std::env;
2+
3+
fn main() {
4+
println!(
5+
"cargo::rustc-link-search=native={}",
6+
env::var("SPIKE_LIB_DIR").expect("SPIKE_LIB_DIR should be set")
7+
);
8+
println!(
9+
"cargo::rustc-link-search=native={}",
10+
env::var("SPIKE_INTERFACES_LIB_DIR").expect("SPIKE_INTERFACES_LIB_DIR should be set")
11+
);
12+
13+
println!("cargo::rerun-if-env-changed=SPIKE_LIB_DIR");
14+
println!("cargo::rerun-if-env-changed=SPIKE_INTERFACES_LIB_DIR");
15+
16+
println!("cargo::rustc-link-lib=static=spike_interfaces");
17+
18+
println!("cargo::rustc-link-lib=static=riscv");
19+
println!("cargo::rustc-link-lib=static=softfloat");
20+
println!("cargo::rustc-link-lib=static=disasm");
21+
println!("cargo::rustc-link-lib=static=fesvr");
22+
println!("cargo::rustc-link-lib=static=fdt");
23+
24+
println!("cargo::rustc-link-lib=stdc++");
25+
}

difftest/t1emu/src/main.rs

+114
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
use std::{
2+
fs::File,
3+
os::unix::fs::FileExt as _,
4+
path::{Path, PathBuf},
5+
};
6+
7+
use anyhow::Context as _;
8+
use clap::Parser;
9+
use elf::{
10+
abi::{EM_RISCV, ET_EXEC, PT_LOAD},
11+
endian::LittleEndian,
12+
ElfStream,
13+
};
14+
use spike::{Spike, SpikeMemory};
15+
use t1devices::AddressSpace;
16+
17+
mod spike;
18+
19+
#[derive(Parser)]
20+
pub struct CliArgs {
21+
/// Path to the ELF file
22+
#[arg(long)]
23+
pub elf_file: PathBuf,
24+
25+
/// ISA config
26+
#[arg(long)]
27+
pub isa: String,
28+
29+
/// vlen config
30+
#[arg(long)]
31+
pub vlen: u32,
32+
}
33+
34+
fn main() -> anyhow::Result<()> {
35+
let args = CliArgs::parse();
36+
let (memory, exit_flag) = t1devices::create_emu_addrspace(fake_get_cycle);
37+
let mut memory = Memory::new(memory);
38+
let elf_entry = memory.load_elf(&args.elf_file)?;
39+
let mut emu = Spike::new(&args.isa, args.vlen as usize, memory);
40+
dbg!(elf_entry);
41+
emu.reset_with_pc(elf_entry);
42+
while !exit_flag.is_finish() {
43+
emu.step_one();
44+
}
45+
Ok(())
46+
}
47+
48+
fn fake_get_cycle() -> u64 {
49+
0
50+
}
51+
52+
struct Memory {
53+
mem: t1devices::AddressSpace,
54+
}
55+
56+
impl Memory {
57+
pub fn new(mem: AddressSpace) -> Self {
58+
Memory { mem }
59+
}
60+
61+
pub fn load_elf(&mut self, path: &Path) -> anyhow::Result<u64> {
62+
let mem = &mut self.mem;
63+
let file = File::open(path).with_context(|| "reading ELF file")?;
64+
let mut elf: ElfStream<LittleEndian, _> =
65+
ElfStream::open_stream(&file).with_context(|| "parsing ELF file")?;
66+
67+
if elf.ehdr.e_machine != EM_RISCV {
68+
anyhow::bail!("ELF is not in RISC-V");
69+
}
70+
71+
if elf.ehdr.e_type != ET_EXEC {
72+
anyhow::bail!("ELF is not an executable");
73+
}
74+
75+
if elf.ehdr.e_phnum == 0 {
76+
anyhow::bail!("ELF has zero size program header");
77+
}
78+
79+
let mut load_buffer = Vec::new();
80+
elf.segments().iter().filter(|phdr| phdr.p_type == PT_LOAD).for_each(|phdr| {
81+
let vaddr: usize = phdr.p_vaddr.try_into().expect("fail converting vaddr(u64) to usize");
82+
let filesz: usize = phdr.p_filesz.try_into().expect("fail converting p_filesz(u64) to usize");
83+
84+
// Load file start from offset into given mem slice
85+
// The `offset` of the read_at method is relative to the start of the file and thus independent from the current cursor.
86+
load_buffer.resize(filesz, 0u8);
87+
file.read_at(load_buffer.as_mut_slice(), phdr.p_offset).unwrap_or_else(|err| {
88+
panic!(
89+
"fail reading ELF into mem with vaddr={}, filesz={}, offset={}. Error detail: {}",
90+
vaddr, filesz, phdr.p_offset, err
91+
)
92+
});
93+
mem.write_mem(vaddr as u32, load_buffer.len() as u32, &load_buffer);
94+
});
95+
96+
Ok(elf.ehdr.e_entry)
97+
}
98+
}
99+
100+
impl SpikeMemory for Memory {
101+
fn addr_to_mem(&mut self, addr: u64) -> Option<&mut u8> {
102+
self.mem.addr_to_mem(addr.try_into().unwrap())
103+
}
104+
105+
fn mmio_load(&mut self, addr: u64, data: &mut [u8]) -> bool {
106+
self.mem.read_mem(addr.try_into().unwrap(), data.len() as u32, data);
107+
true
108+
}
109+
110+
fn mmio_store(&mut self, addr: u64, data: &[u8]) -> bool {
111+
self.mem.write_mem(addr.try_into().unwrap(), data.len() as u32, data);
112+
true
113+
}
114+
}

0 commit comments

Comments
 (0)