Compare commits

...

31 Commits
main ... main

Author SHA1 Message Date
刘子宸 c29f51b6e2 Added gdb server. 2026-05-20 15:57:53 +08:00
刘子宸 342549bd5c Fixed CLINT address map. 2026-05-19 22:15:30 +08:00
刘子宸 dcca687395 Added log interface. 2026-05-19 18:17:56 +08:00
刘子宸 b851d99598 Added PTY ioflow backend. 2026-05-14 14:02:42 +08:00
刘子宸 9d4bc1e79f Added RISC-V A extension. 2026-05-13 17:22:38 +08:00
刘子宸 2b1b429bd6 Added virtual peripheral UART Lite and CLINT. 2026-05-11 19:40:58 +08:00
刘子宸 8549c5b373 New IO backend interface. 2026-05-09 20:06:16 +08:00
刘子宸 6b423fab0c Changed `ringbuffer` so its capacity can only be powers of 2. 2026-04-11 17:23:06 +08:00
刘子宸 bd51e32487 Moved `ringbuffer` and `width_t` to namespace `libanemo`. 2026-04-11 15:22:15 +08:00
刘子宸 0ff4753ab0 Added address translation. 2026-04-09 12:00:09 +08:00
刘子宸 a336b2d975 Optimized performance of virtual memory. 2026-04-04 22:39:22 +08:00
刘子宸 734f3f16e9 Changed the memory interface of `abstract_cpu` from `memory` to `memory_view`. 2025-09-12 13:29:36 +08:00
刘子宸 25280346db `memory_veiw` can have different position mapped to base address with source. 2025-08-09 00:58:41 +08:00
刘子宸 04ef9a2276 Added `memory_view`. 2025-08-06 01:48:02 +08:00
刘子宸 1894331c5b Addded example for a minimal RISC-V simulator. 2025-08-04 14:37:08 +08:00
刘子宸 594b4cb101 Moved emulator backend to namespace `riscv`. 2025-08-04 14:22:04 +08:00
刘子宸 4771a17e11 Refactored project layout. 2025-08-03 21:45:14 +08:00
刘子宸 4878dba4d5 Staged to new emulator. 2025-08-03 14:42:25 +08:00
刘子宸 521794ce51 RV64IM support. 2025-08-03 03:10:30 +08:00
刘子宸 723232645c Improved difftest logic. 2025-08-03 00:41:03 +08:00
刘子宸 4921bef5dc New RISC-V emulator with privileged operation support. 2025-08-03 00:38:42 +08:00
刘子宸 ba585249ab Specialize SDB for difftest. 2025-08-02 17:14:37 +08:00
刘子宸 69edff820a New RISC-V simulator backends. 2025-08-02 17:14:06 +08:00
刘子宸 69ae644920 An unprivileged only RISC-V CPU fast emulator. 2025-07-30 22:04:14 +08:00
刘子宸 4a742b5420 Renamed source file. 2025-07-24 13:57:14 +08:00
刘子宸 daf4a0588e `simple_difftest` now compares trap events. 2025-07-24 01:34:51 +08:00
刘子宸 22805f671d Migrate `rv32i_cpu_system` to a subclass of `riscv_cpu<uint32_t>`. 2025-07-24 01:33:50 +08:00
刘子宸 55ae688a79 Fixed display format of `event_t`. 2025-07-23 22:27:17 +08:00
刘子宸 30ac32dd8c An unprivileged only RISC-V CPU base class. 2025-07-23 22:26:34 +08:00
刘子宸 36f73c4cbe Fixed mistakes in difftest and the `trace` SDB command. 2025-07-23 18:44:49 +08:00
刘子宸 b596e5df5c Migrate RISC-V constant definitions to static constexpr members of template structs. 2025-07-23 13:36:23 +08:00
64 changed files with 5354 additions and 2871 deletions

View File

@ -8,11 +8,13 @@ set(CMAKE_CXX_STANDARD_REQUIRED ON)
include_directories(SYSTEM "include")
file(GLOB_RECURSE "LIBVIO_SRCS" "src/libvio/*.cc")
file(GLOB_RECURSE "LIBCPU_SRCS" "src/libcpu/rv32i_cpu_system/*.cc")
file(GLOB_RECURSE "LIBCPU_SRCS" "src/libcpu/*.cc")
file(GLOB_RECURSE "LIBSDB_SRCS" "src/libsdb/*.cc")
add_library(anemo STATIC "${LIBCPU_SRCS}" "${LIBVIO_SRCS}" "${LIBSDB_SRCS}")
set_target_properties(anemo PROPERTIES POSITION_INDEPENDENT_CODE ON)
add_executable(example src/main.cc)
target_link_libraries(example PRIVATE anemo)
add_executable(quick_start src/examples/quick_start.cc)
target_link_libraries(quick_start PRIVATE anemo)
add_executable(riscv_minimal src/examples/riscv_minimal.cc)
target_link_libraries(riscv_minimal PRIVATE anemo)

View File

@ -54,14 +54,14 @@ Then you can use `agent.read()` and `agent.write()` to simulate MMIO operations.
### Simulating a CPU
`libcpu` provides the `abstract_cpu` base class for a simulated processor core, and the `abstract_memory` base class for simulated memory. To simulate a processor, instantiate a processor core, a memory, initialize the memory with proper content, and connect the memory to the memory ports of the CPU core.
`libcpu` provides the `abstract_cpu` base class for a simulated processor core, and the `memory` class for simulated memory. To simulate a processor, instantiate a processor core, a memory, initialize the memory with proper content, and connect the memory to the memory ports of the CPU core.
```c++
#include <libcpu/rv32i_cpu_system.hh>
#include <libcpu/riscv_cpu_system.hh>
libcpu::rv32i_cpu_system cpu;
libcpu::riscv_cpu_system<uint32_t> cpu;
libcpu::contiguous_memory<uint32_t> memory{0x80000000, 128*1024*1024};
libcpu::memory memory{0x80000000, 128*1024*1024};
memory.load_elf_from_file(argv[1]);
cpu.instr_bus = &memory;
@ -74,7 +74,7 @@ Optionally connect an MMIO agent to the processor. MMIO requests are ignored if
cpu.mmio_bus = dispatcher.new_agent();
```
You can connect the `instr_bus` and `data_bus` to the same memory, or different caches with the same underlaying memory, or even different memories if the processor uses different address space to access instruction and data.
In most cases, you should connect the `instr_bus` and `data_bus` to the same memory, unless the processor uses different address spaces to access instruction and data.
Then reset the CPU with specified initial program counter with `reset()`. Then you can step the CPU forward with `next_instruction()` or `next_cycle()`, and check whether it has stopped with `stopped()`.
@ -85,10 +85,10 @@ while (!cpu.stopped()) {
}
```
`libcpu` provides `event_t<WORD_T>` in `libcpu/event.hh` describing an architectural event, for example, writing to a register and a memory operation. To enable event tracing, attach a `libvio::ringbuffer<event_t<WORT_T>>` to the CPU core. The events will be automatically put into the ring-buffer if supported.
`libcpu` provides `event_t<WORD_T>` in `libcpu/event.hh` describing an architectural event, for example, writing to a register and a memory operation. To enable event tracing, attach a `libanemo::ringbuffer<event_t<WORT_T>>` to the CPU core. The events will be automatically put into the ring-buffer if supported.
```c++
libvio::ringbuffer<libcpu::event_t<uint32_t>> events{4096};
libanemo::ringbuffer<libcpu::event_t<uint32_t>> events{4096};
cpu.event_buffer = &events;
```

View File

@ -83,10 +83,10 @@ while (!cpu.stopped()) {
}
```
`libcpu`在`libcpu/event.hh`中提供了`event_t<WORD_T>`,用于描述架构事件,例如写入寄存器或内存操作。要启用事件追踪,需将一个`libvio::ringbuffer<event_t<WORT_T>>`附加到CPU核心。如果支持事件将自动放入环形缓冲区。
`libcpu`在`libcpu/event.hh`中提供了`event_t<WORD_T>`,用于描述架构事件,例如写入寄存器或内存操作。要启用事件追踪,需将一个`libanemo::ringbuffer<event_t<WORT_T>>`附加到CPU核心。如果支持事件将自动放入环形缓冲区。
```c++
libvio::ringbuffer<libcpu::event_t<uint32_t>> events{4096};
libanemo::ringbuffer<libcpu::event_t<uint32_t>> events{4096};
cpu.event_buffer = &events;
```

81
include/libanemo/log.hh Normal file
View File

@ -0,0 +1,81 @@
#ifndef LIBANEMO_LOG_HH
#define LIBANEMO_LOG_HH
#include <cstdint>
#include <cstdio>
#include <cstdarg>
#ifndef LIBANEMO_LOG_LEVEL
#define LIBANEMO_LOG_LEVEL info
#endif
namespace libanemo {
enum class log_level_t: uint8_t {
none, critical, error, warning, info, debug, trace, all
};
static inline void log_critical(const char* source, const char* fmt, ...) {
if constexpr (static_cast<uint8_t>(log_level_t::LIBANEMO_LOG_LEVEL) >= static_cast<uint8_t>(log_level_t::critical)) {
va_list args;
va_start(args, fmt);
printf("%s: critical: ", source);
vprintf(fmt, args);
va_end(args);
}
}
static inline void log_error(const char* source, const char* fmt, ...) {
if constexpr (static_cast<uint8_t>(log_level_t::LIBANEMO_LOG_LEVEL) >= static_cast<uint8_t>(log_level_t::error)) {
va_list args;
va_start(args, fmt);
printf("%s: error: ", source);
vprintf(fmt, args);
va_end(args);
}
}
static inline void log_warning(const char* source, const char* fmt, ...) {
if constexpr (static_cast<uint8_t>(log_level_t::LIBANEMO_LOG_LEVEL) >= static_cast<uint8_t>(log_level_t::warning)) {
va_list args;
va_start(args, fmt);
printf("%s: warning: ", source);
vprintf(fmt, args);
va_end(args);
}
}
static inline void log_info(const char* source, const char* fmt, ...) {
if constexpr (static_cast<uint8_t>(log_level_t::LIBANEMO_LOG_LEVEL) >= static_cast<uint8_t>(log_level_t::info)) {
va_list args;
va_start(args, fmt);
printf("%s: info: ", source);
vprintf(fmt, args);
va_end(args);
}
}
static inline void log_debug(const char* source, const char* fmt, ...) {
if constexpr (static_cast<uint8_t>(log_level_t::LIBANEMO_LOG_LEVEL) >= static_cast<uint8_t>(log_level_t::debug)) {
va_list args;
va_start(args, fmt);
printf("%s: debug: ", source);
vprintf(fmt, args);
va_end(args);
}
}
static inline void log_trace(const char* source, const char* fmt, ...) {
if constexpr (static_cast<uint8_t>(log_level_t::LIBANEMO_LOG_LEVEL) >= static_cast<uint8_t>(log_level_t::trace)) {
va_list args;
va_start(args, fmt);
printf("%s: trace: ", source);
vprintf(fmt, args);
va_end(args);
}
}
}
#endif

View File

@ -1,11 +1,12 @@
#ifndef LIBVIO_RINGBUFFER_HH
#define LIBVIO_RINGBUFFER_HH
#ifndef LIBANEMO_RINGBUFFER_HH
#define LIBANEMO_RINGBUFFER_HH
#include <algorithm>
#include <cstddef>
#include <iterator>
#include <limits>
namespace libvio {
namespace libanemo {
template <typename T>
class ringbuffer;
@ -22,6 +23,13 @@ class ringbuffer_const_iterator;
* This class implements a fixed-size circular buffer that allows efficient addition and removal
* of elements at both ends. The buffer maintains its elements in a contiguous storage, with
* indices wrapping around at the boundaries.
*
* @note `index_mask==0 && buffer==nullptr` is reversed for an invalid buffer. Any read or write on
* an invalid buffer is undefined behavior. Whether the buffer is valid can be checked with `capacity()`.
* An invalid buffer has the capacity of 0;
*
* @note This implementation is designed for trivially copiable types. Complicated objects and move
* only objects are not supported. It usually makes no sense to put such objects into a ringbuffer.
*
* @note This implementation intentionally omits push_front/pop_front operations. When multiple
* consumers access the buffer concurrently, each consumer should maintain its own front index.
@ -34,7 +42,7 @@ class ringbuffer {
private:
T* buffer; ///< Pointer to the underlying storage array
size_t max_size; ///< Maximum capacity of the buffer (never changes after construction)
size_t index_mask; ///< Mask of the index (never changes after construction)
size_t first_index; ///< Index of the first valid element in the buffer
size_t last_index; ///< Index of the next available position in the buffer
@ -46,46 +54,86 @@ class ringbuffer {
/**
* @brief Constructs a ringbuffer with the specified capacity.
*
* @param n The number of elements that can be stored in the buffer.
* @param capacity_log_2 The base-2 logarithm of the buffer capacity
*
* @note `capacity_log_2` must be greater than zero
*/
ringbuffer(size_t n) {
buffer = new T[n];
max_size = n;
ringbuffer(unsigned int capacity_log_2) {
size_t buffer_size = size_t(1) << capacity_log_2;
if (capacity_log_2 > 0) {
buffer = new T[buffer_size];
} else {
buffer = nullptr;
}
index_mask = buffer_size - 1;
first_index = 0;
last_index = 0;
}
~ringbuffer() {
delete[] buffer;
if (buffer != nullptr) {
delete[] buffer;
}
}
ringbuffer<T>(const ringbuffer<T>& other) {
buffer = new T[other.max_size];
max_size = other.max_size;
index_mask = other.index_mask;
first_index = other.first_index;
last_index = other.last_index;
std::copy(other.buffer, other.buffer+max_size, buffer);
if (other.index_mask != 0) {
size_t buffer_size = other.index_mask + 1;
buffer = new T[buffer_size];
if (other.buffer != nullptr) {
std::copy(other.buffer, other.buffer+buffer_size, buffer);
}
} else {
buffer = nullptr;
}
}
ringbuffer<T>(ringbuffer<T>&& other) {
ringbuffer<T>(ringbuffer<T>&& other) noexcept {
buffer = other.buffer;
max_size = other.max_size;
index_mask = other.index_mask;
first_index = other.first_index;
last_index = other.last_index;
other.buffer = nullptr;
other.max_size = 0;
other.index_mask = 0;
other.first_index = 0;
other.last_index = 0;
}
ringbuffer<T>& operator=(const ringbuffer<T>& other) {
ringbuffer<T>& operator=(ringbuffer<T>&& other) noexcept {
if (this != &other) {
delete[] buffer;
buffer = new T[other.max_size];
max_size = other.max_size;
if (buffer != nullptr) {
delete [] buffer;
}
buffer = other.buffer;
index_mask = other.index_mask;
first_index = other.first_index;
last_index = other.last_index;
std::copy(other.buffer, other.buffer+max_size, buffer);
other.buffer = nullptr;
other.index_mask = 0;
other.first_index = 0;
other.last_index = 0;
}
}
ringbuffer<T>& operator=(const ringbuffer<T>& other) {
if (this != &other) {
if (buffer != nullptr) {
delete[] buffer;
}
index_mask = other.index_mask;
first_index = other.first_index;
last_index = other.last_index;
if (other.index_mask != 0) {
size_t buffer_size = other.index_mask + 1;
buffer = new T[buffer_size];
// `other.buffer` must be not null here
std::copy(other.buffer, other.buffer+buffer_size, buffer);
} else {
buffer = nullptr;
}
}
return *this;
}
@ -113,12 +161,34 @@ class ringbuffer {
}
T& operator[] (size_t index) {
return buffer[index%max_size];
return buffer[index&index_mask];
}
const T& operator[](size_t index) const {
return buffer[index%max_size];
}
return buffer[index&index_mask];
}
/**
* @brief Gets the maximum number of elements the buffer can hold.
*
* @return The buffer capacity
*/
constexpr size_t capacity() const {
if (index_mask == 0) {
return 0;
} else {
return index_mask + 1;
}
}
/**
* @brief Gets the current number of elements in the buffer.
*
* @return The number of elements
*/
size_t size() const {
return last_index - first_index;
}
/**
* @brief Adds an element to the end of the buffer.
@ -128,9 +198,15 @@ class ringbuffer {
* @param value The element to add
*/
void push_back(const T& value) {
buffer[last_index%max_size] = value;
size_t buffer_size = index_mask + 1;
buffer[last_index&index_mask] = value;
// this ensures that the first and last index never overflow
if (last_index == std::numeric_limits<size_t>::max()) {
first_index &= std::numeric_limits<size_t>::max() >> 1;
last_index &= std::numeric_limits<size_t>::max() >> 1;
}
++last_index;
first_index = (last_index-first_index) > max_size ? last_index-max_size : first_index;
first_index = (last_index-first_index) > buffer_size ? last_index-buffer_size : first_index;
}
/**
@ -148,24 +224,6 @@ class ringbuffer {
Each consumer should maintain its own front index rather than rely on the state of the ringbuffer.
*/
/**
* @brief Gets the maximum number of elements the buffer can hold.
*
* @return The buffer capacity
*/
constexpr size_t capacity() const {
return max_size;
}
/**
* @brief Gets the current number of elements in the buffer.
*
* @return The number of elements (last_index - first_index)
*/
size_t size() const {
return last_index - first_index;
}
/**
* @brief Checks if the buffer contains no elements.
*
@ -181,7 +239,7 @@ class ringbuffer {
* @return An iterator pointing to the first element
*/
iterator begin() {
return iterator(buffer, max_size, first_index);
return iterator(buffer, index_mask, first_index);
}
/**
@ -190,7 +248,7 @@ class ringbuffer {
* @return An iterator pointing to the past-the-end element
*/
iterator end() {
return iterator(buffer, max_size, last_index);
return iterator(buffer, index_mask, last_index);
}
/**
@ -199,7 +257,7 @@ class ringbuffer {
* @return A const iterator pointing to the first element
*/
const_iterator begin() const {
return const_iterator(buffer, max_size, first_index);
return const_iterator(buffer, index_mask, first_index);
}
/**
@ -208,7 +266,7 @@ class ringbuffer {
* @return A const iterator pointing to the past-the-end element
*/
const_iterator end() const {
return const_iterator(buffer, max_size, last_index);
return const_iterator(buffer, index_mask, last_index);
}
/**
@ -229,6 +287,41 @@ class ringbuffer {
return end();
}
/**
* @brief Gets the first element in the buffer.
*
* @return The first element
*/
T& front() {
return buffer[first_index&index_mask];
}
/**
* @brief Gets the last element in the buffer.
*
* @return The last element
*/
T& back() {
return buffer[(last_index-1)&index_mask];
}
/**
* @brief Gets the first element in the buffer.
*
* @return The first element
*/
const T& front() const {
return buffer[first_index&index_mask];
}
/**
* @brief Gets the last element in the buffer.
*
* @return The last element
*/
const T& back() const {
return buffer[(last_index-1)&index_mask];
}
};
template <typename T>
@ -236,7 +329,7 @@ class ringbuffer_iterator {
friend class ringbuffer_const_iterator<T>;
private:
T *buffer;
size_t max_size;
size_t index_mask;
size_t index;
public:
using iterator_category = std::random_access_iterator_tag;
@ -245,7 +338,7 @@ class ringbuffer_iterator {
using pointer = T*;
using reference = T&;
ringbuffer_iterator(T* buffer, size_t max_size, size_t index): buffer(buffer), max_size(max_size), index(index) {}
ringbuffer_iterator(T* buffer, size_t index_mask, size_t index): buffer(buffer), index_mask(index_mask), index(index) {}
// Increment/decrement
ringbuffer_iterator& operator++() { ++index; return *this; }
@ -255,10 +348,10 @@ class ringbuffer_iterator {
// Arithmetic
ringbuffer_iterator operator+(difference_type n) const {
return ringbuffer_iterator(buffer, max_size, index + n);
return ringbuffer_iterator(buffer, index_mask, index+n);
}
ringbuffer_iterator operator-(difference_type n) const {
return ringbuffer_iterator(buffer, max_size, index - n);
return ringbuffer_iterator(buffer, index_mask, index-n);
}
ringbuffer_iterator& operator+=(difference_type n) {
index += n;
@ -278,27 +371,33 @@ class ringbuffer_iterator {
}
// Access
T& operator*() const { return buffer[index % max_size]; }
T& operator*() const { return buffer[index&index_mask]; }
T& operator[](difference_type n) const { return *(*this + n); }
// Comparison
bool operator==(const ringbuffer_iterator& other) const {
return index == other.index && buffer == other.buffer;
return index==other.index && buffer==other.buffer;
}
bool operator==(const ringbuffer_const_iterator<T>& other) const {
return index==other.index && buffer==other.buffer;
}
bool operator!=(const ringbuffer_iterator& other) const {
return !(*this == other);
return index!=other.index || buffer!=other.buffer;
}
bool operator!=(const ringbuffer_const_iterator<T>& other) const {
return index!=other.index || buffer!=other.buffer;
}
bool operator<(const ringbuffer_iterator& other) const {
return index < other.index;
return index<other.index && buffer==other.buffer;
}
bool operator<=(const ringbuffer_iterator& other) const {
return index <= other.index;
return index<=other.index && buffer==other.buffer;
}
bool operator>(const ringbuffer_iterator& other) const {
return index > other.index;
return index>other.index && buffer==other.buffer;
}
bool operator>=(const ringbuffer_iterator& other) const {
return index >= other.index;
return index>=other.index && buffer==other.buffer;
}
};
@ -307,17 +406,17 @@ class ringbuffer_const_iterator {
friend class ringbuffer_iterator<T>;
private:
const T* buffer;
size_t max_size;
size_t index_mask;
size_t index;
public:
using iterator_category = std::random_access_iterator_tag;
using value_type = const T;
using value_type = T;
using difference_type = std::ptrdiff_t;
using pointer = const T*;
using reference = const T&;
ringbuffer_const_iterator(const ringbuffer_iterator<T>& other): buffer(other.buffer), max_size(other.max_size), index(other.index) {}
ringbuffer_const_iterator(const T* buffer, size_t max_size, size_t index): buffer(buffer), max_size(max_size), index(index) {}
ringbuffer_const_iterator(const ringbuffer_iterator<T>& other): buffer(other.buffer), index_mask(other.index_mask), index(other.index) {}
ringbuffer_const_iterator(const T* buffer, size_t index_mask, size_t index): buffer(buffer), index_mask(index_mask), index(index) {}
// Increment/decrement
ringbuffer_const_iterator& operator++() { ++index; return *this; }
@ -327,10 +426,10 @@ class ringbuffer_const_iterator {
// Arithmetic
ringbuffer_const_iterator operator+(difference_type n) const {
return ringbuffer_const_iterator(buffer, max_size, index + n);
return ringbuffer_const_iterator(buffer, index_mask, index+n);
}
ringbuffer_const_iterator operator-(difference_type n) const {
return ringbuffer_const_iterator(buffer, max_size, index - n);
return ringbuffer_const_iterator(buffer, index_mask, index-n);
}
ringbuffer_const_iterator& operator+=(difference_type n) {
index += n;
@ -350,27 +449,33 @@ class ringbuffer_const_iterator {
}
// Access
const T& operator*() const { return buffer[index % max_size]; }
const T& operator*() const { return buffer[index&index_mask]; }
const T& operator[](difference_type n) const { return *(*this + n); }
// Comparison
bool operator==(const ringbuffer_const_iterator& other) const {
return index == other.index && buffer == other.buffer;
return index==other.index && buffer==other.buffer;
}
bool operator==(const ringbuffer_iterator<T>& other) const {
return index==other.index && buffer==other.buffer;
}
bool operator!=(const ringbuffer_const_iterator& other) const {
return !(*this == other);
return index!=other.index || buffer!=other.buffer;
}
bool operator!=(const ringbuffer_iterator<T>& other) const {
return index!=other.index || buffer!=other.buffer;
}
bool operator<(const ringbuffer_const_iterator& other) const {
return index < other.index;
return index<other.index && buffer==other.buffer;
}
bool operator<=(const ringbuffer_const_iterator& other) const {
return index <= other.index;
return index<=other.index && buffer==other.buffer;
}
bool operator>(const ringbuffer_const_iterator& other) const {
return index > other.index;
return index>other.index && buffer==other.buffer;
}
bool operator>=(const ringbuffer_const_iterator& other) const {
return index >= other.index;
return index>=other.index && buffer==other.buffer;
}
};

186
include/libanemo/width.hh Normal file
View File

@ -0,0 +1,186 @@
/**
* @file width.hh
* @brief Provides width-related operations for integer types
*/
#ifndef LIBANEMO_WIDTH_HH
#define LIBANEMO_WIDTH_HH
#include <climits>
#include <cstdint>
#include <string>
namespace libanemo {
/**
* @enum width_t
* @brief Enumeration representing different data widths
*/
enum class width_t {
byte = 1, ///< 1-byte width (8 bits)
half = 2, ///< 2-byte width (16 bits)
word = 4, ///< 4-byte width (32 bits)
dword = 8 ///< 8-byte width (64 bits)
};
/**
* @brief Creates a bit mask with specified range.
*
* Generates a bit mask where bits from `low` (inclusive) to `high` (exclusive)
* are set to 1, and all other bits are set to 0. Bits are numbered from 0
* (least significant bit).
*
* @tparam WORD_T Unsigned integer type for the mask.
* @param high Exclusive upper bound of the bit range.
* @param low Inclusive lower bound of the bit range.
* @return WORD_T Bit mask with specified bits set.
*/
template <typename WORD_T>
constexpr WORD_T bit_mask(unsigned int high, unsigned low) {
WORD_T low_mask = ~WORD_T(0) << low;
WORD_T high_mask = high>=(sizeof(WORD_T)*CHAR_BIT) ? 0 : ~WORD_T(0)<<high;
return high_mask ^ low_mask;
}
/**
* @brief Truncates a value to the specified width by zeroing upper bits
* @tparam WORD_T The word type (uint32_t or uint64_t)
* @param value The input value to truncate
* @param width The target width to truncate to
* @return The truncated value with upper bits zeroed
*/
template <typename WORD_T>
constexpr WORD_T zero_truncate(WORD_T value, width_t width) {
return value & bit_mask<WORD_T>(static_cast<unsigned int>(width)*8, 0);
}
/**
* @brief Sign-extends a value to the full width of the type
* @tparam WORD_T The word type (uint32_t or uint64_t)
* @param value The input value to sign-extend
* @param width The original width of the input value
* @return The sign-extended value
*/
template <typename WORD_T>
constexpr WORD_T sign_extend(WORD_T value, width_t width);
/**
* @brief Specialization of sign_extend for uint32_t
* @param value The 32-bit input value to sign-extend
* @param width The original width of the input value
* @return The sign-extended 32-bit value
*/
template <>
constexpr uint32_t sign_extend<uint32_t>(uint32_t value, width_t width) {
switch (width) {
case width_t::byte:
return uint32_t(int32_t(int8_t(value)));
case width_t::half:
return uint32_t(int32_t(int16_t(value)));
default:
return value;
}
}
/**
* @brief Specialization of sign_extend for uint64_t
* @param value The 64-bit input value to sign-extend
* @param width The original width of the input value
* @return The sign-extended 64-bit value
*/
template <>
constexpr uint64_t sign_extend<uint64_t>(uint64_t value, width_t width) {
switch (width) {
case width_t::byte:
return uint64_t(int64_t(int8_t(value)));
case width_t::half:
return uint64_t(int64_t(int16_t(value)));
case width_t::word:
return uint64_t(int64_t(int32_t(value)));
default:
return value;
}
}
/**
* @brief Converts an integer type to its corresponding width enumeration
*
* @tparam T The integer type to convert to width_t
* @return The corresponding width enumeration value
*/
template <typename T>
constexpr width_t int_type_to_width_t(void);
template <>
constexpr width_t int_type_to_width_t<uint8_t>(void) {
return width_t::byte;
}
template <>
constexpr width_t int_type_to_width_t<uint16_t>(void) {
return width_t::half;
}
template <>
constexpr width_t int_type_to_width_t<uint32_t>(void) {
return width_t::word;
}
template <>
constexpr width_t int_type_to_width_t<uint64_t>(void) {
return width_t::dword;
}
/**
* @brief Checks if an address is aligned to a given width.
*
* @tparam WORD_T Unsigned integer type for the address (e.g., uint32_t, uint64_t).
* @param addr The address to test for alignment.
* @param width The alignment boundary as a width_t value (byte, half, word, dword).
* @return true if `addr` is aligned to `width`, false otherwise.
*/
template <typename WORD_T>
constexpr bool aligned(WORD_T addr, width_t width) {
return (addr & (static_cast<WORD_T>(width) - 1)) == 0;
}
template <typename WORD_T>
constexpr WORD_T partial_read(uint64_t offset, width_t width, WORD_T data_read) {
return zero_truncate(data_read>>(offset*8), width);
}
template <typename WORD_T>
constexpr WORD_T partial_write(uint64_t offset, width_t width, WORD_T data_read, WORD_T data_write) {
data_write = zero_truncate(data_write, width) << (offset*8);
data_read &= ~bit_mask<WORD_T>((offset+static_cast<uint64_t>(width))*8, offset*8);
return data_read | data_write;
}
}
namespace std {
/**
* @brief Converts a width_t enum value to its string representation
* @param width The width enum value to convert
* @return String representation of the width
*/
inline string to_string(libanemo::width_t width) noexcept {
switch (width) {
case libanemo::width_t::byte:
return "byte";
case libanemo::width_t::half:
return "half";
case libanemo::width_t::word:
return "word";
case libanemo::width_t::dword:
return "dword";
default:
return "unknown";
}
}
}
#endif

View File

@ -8,7 +8,7 @@
#include <libvio/agent.hh>
#include <libvio/bus.hh>
#include <libvio/frontend.hh>
#include <libvio/ringbuffer.hh>
#include <libanemo/ringbuffer.hh>
namespace libcpu {
@ -26,11 +26,11 @@ class abstract_cpu {
public:
using word_t = WORD_T; ///< Type alias for the CPU word type
abstract_memory<WORD_T> *instr_bus = nullptr; // Pointer to the simulated instruction bus. Ignored if the subclass do not use a simulated memory.
abstract_memory<WORD_T> *data_bus = nullptr; // Pointer to the simulated data bus. Ignored if the subclass do not use a simulated memory.
memory_view *instr_bus = nullptr; // Pointer to the simulated instruction bus. Ignored if the subclass do not use a simulated memory.
memory_view *data_bus = nullptr; // Pointer to the simulated data bus. Ignored if the subclass do not use a simulated memory.
libvio::io_agent *mmio_bus = nullptr; ///< The virtual MMIO bus. If nullptr, MMIO is disabled. Ignored on user-space emulators.
libvio::ringbuffer<event_t<WORD_T>> *event_buffer = nullptr; ///< Buffer for storing CPU events. If nullptr, event tracing is off.
libanemo::ringbuffer<event_t<WORD_T>> *event_buffer = nullptr; ///< Buffer for storing CPU events. If nullptr, event tracing is off.
/**
@ -60,7 +60,7 @@ class abstract_cpu {
virtual void reset(WORD_T init_pc) = 0;
/**
* @brief Get the program counter value of the the next instruction to be comitted.
* @brief Get the program counter value of the the next instruction to be executed.
* @return The next program counter value
*/
virtual WORD_T get_pc(void) const = 0;
@ -94,24 +94,21 @@ class abstract_cpu {
}
/**
* @brief Advance the CPU until at least one more instruction is committed.
* @brief Advance the CPU until at least one more instruction is has been executed.
* @note On superscalar CPUs, this function may execute more than one instruction.
* Self-traps are counted as commited.
*/
virtual void next_instruction(void) = 0;
/**
* @brief Advance the CPU until at least n more instructions are committed.
* @brief Advance the CPU until at least n more instructions has been executed.
* @param n The number of instructions to execute
* @note On superscalar CPUs, this function may execute more than n instructions.
* Self-traps are counted as commited.
*/
virtual void next_instruction(size_t n) {
for (auto i=0; i<n; ++i) {
next_instruction();
}
};
/**
* @brief Convert virtual address to physical address
* @param vaddr Virtual address to convert
@ -129,7 +126,7 @@ class abstract_cpu {
* @note This function will not read from a MMIO address. It tiggers no side-effect like caching neither.
* If a MMIO address is provided, `nullopt` is returned.
*/
virtual std::optional<WORD_T> vmem_peek(WORD_T addr, libvio::width_t width) const {
virtual std::optional<WORD_T> vmem_peek(WORD_T addr, libanemo::width_t width) const {
auto paddr = vaddr_to_paddr(addr);
if (paddr.has_value()) {
return pmem_peek(paddr.value(), width);
@ -146,7 +143,9 @@ class abstract_cpu {
* @note This function will not read from a MMIO address. It tiggers no side-effect like caching neither.
* If a MMIO address is provided, `nullopt` is returned.
*/
virtual std::optional<WORD_T> pmem_peek(WORD_T addr, libvio::width_t width) const = 0;
virtual std::optional<WORD_T> pmem_peek(WORD_T addr, libanemo::width_t width) const {
return data_bus->read(addr, width);
}
/**
* @brief Whether the execution has ended.

View File

@ -1,332 +0,0 @@
#ifndef LIBCPU_CACHE_HH
#define LIBCPU_CACHE_HH
#include <cstddef>
#include <vector>
#include <memory>
#include <cstdint>
#include <optional>
#include <fstream>
#include <libcpu/memory.hh>
namespace libcpu {
/**
* @brief Abstract base class for cache implementations
*
* @tparam WORD_T The type for address and data. Must be an unsigned integral type.
*
* This class extends abstract_memory with cache-specific functionality including
* invalidation and performance statistics tracking.
*/
template <typename WORD_T>
class abstract_cache : public abstract_memory<WORD_T> {
public:
abstract_memory<WORD_T>* underlying_memory; // Public pointer to underlying memory
/**
* @brief Invalidate a specific cache line
* @param addr Address within the cache line to invalidate
*/
virtual void invalidate(WORD_T addr) = 0;
/**
* @brief Invalidate all cache lines
*/
virtual void invalidate() = 0;
/**
* @brief Get total cache hits
* @return Number of successful cache hits since creation or last reset
*/
virtual uint64_t hits() const = 0;
/**
* @brief Get total cache misses
* @return Number of cache misses since creation or last reset
*/
virtual uint64_t misses() const = 0;
// Debugging functions bypass cache
std::optional<WORD_T> peek(WORD_T addr, libvio::width_t width, bool little_endian = true) const override {
return underlying_memory->peek(addr, width, little_endian);
}
bool set(WORD_T addr, libvio::width_t width, WORD_T value, bool little_endian = true) override {
return underlying_memory->set(addr, width, value, little_endian);
}
uint8_t* host_addr(WORD_T addr) override {
return underlying_memory->host_addr(addr);
}
// Save/restore must be implemented by concrete caches
void save(const char* filename) const override = 0;
WORD_T restore(const char* filename) override = 0;
};
/**
* @brief Direct-mapped write-through cache implementation
*
* @tparam WORD_T The type for address and data. Must be an unsigned integral type.
* @tparam OFFSET_BITS Number of bits used for block offset (determines block size)
* @tparam INDEX_BITS Number of bits used for index (determines number of blocks)
*
* @note This class is only a demo of the cache interface. It is not completly tested or optimized for performance.
*/
template <typename WORD_T, uint_fast8_t OFFSET_BITS, uint_fast8_t INDEX_BITS>
class direct_cache: public abstract_cache<WORD_T> {
public:
static constexpr uint_fast8_t offset_bits = OFFSET_BITS;
static constexpr uint_fast8_t index_bits = INDEX_BITS;
static constexpr WORD_T tag_bits = sizeof(WORD_T) * 8 - index_bits - offset_bits;
static constexpr WORD_T tag_mask = (size_t(1) << tag_bits) - 1;
static constexpr size_t num_lines = size_t(1) << index_bits;
static constexpr size_t block_size = size_t(1) << offset_bits;
static_assert(std::is_unsigned_v<WORD_T>, "WORD_T must be unsigned type");
static_assert(offset_bits > 0 && offset_bits < sizeof(WORD_T)*8,
"Invalid offset_bits");
private:
std::unique_ptr<uint8_t[]> data;
std::unique_ptr<bool[]> valid;
std::unique_ptr<WORD_T[]> tags;
uint64_t hit_count = 0;
uint64_t miss_count = 0;
// Address decomposition
struct cache_address {
WORD_T tag;
WORD_T index;
WORD_T offset;
};
cache_address decompose(WORD_T addr) const {
WORD_T offset = addr & (block_size - 1);
WORD_T index = (addr >> offset_bits) & (num_lines - 1);
WORD_T tag = (addr >> (offset_bits + index_bits)) & tag_mask;
return {tag, index, offset};
}
// Check if access is within a single block
bool within_block(WORD_T addr, libvio::width_t width) const {
WORD_T start = addr;
WORD_T end = addr + static_cast<WORD_T>(width) - 1;
return (start >> offset_bits) == (end >> offset_bits);
}
// Fetch block from underlying memory
void fetch_block(WORD_T block_base) {
++miss_count;
auto dec = decompose(block_base);
uint8_t* block_ptr = data.get() + dec.index * block_size;
if (uint8_t* underlying_ptr = this->underlying_memory->host_addr(block_base)) {
std::copy(underlying_ptr, underlying_ptr + block_size, block_ptr);
} else {
for (WORD_T i = 0; i < block_size; ++i) {
auto byte_val = this->underlying_memory->peek(block_base + i,
libvio::width_t::byte);
if (!byte_val) {
valid[dec.index] = false;
return;
}
block_ptr[i] = static_cast<uint8_t>(*byte_val);
}
}
tags[dec.index] = dec.tag;
valid[dec.index] = true;
}
// Read from cache block
std::optional<WORD_T> read_block(WORD_T addr, libvio::width_t width,
bool little_endian) {
auto dec = decompose(addr);
if (!valid[dec.index] || tags[dec.index] != dec.tag) {
fetch_block(addr - dec.offset);
} else {
hit_count++; // Count cache hit
}
uint8_t* block_ptr = data.get() + dec.index * block_size + dec.offset;
size_t w = static_cast<size_t>(width);
WORD_T value = 0;
if (little_endian) {
for (size_t i = 0; i < w; ++i) {
value |= static_cast<WORD_T>(block_ptr[i]) << (i * 8);
}
} else {
for (size_t i = 0; i < w; ++i) {
value = (value << 8) | block_ptr[i];
}
}
return value;
}
// Write to cache block
bool write_block(WORD_T addr, libvio::width_t width,
WORD_T value, bool little_endian) {
auto dec = decompose(addr);
if (!valid[dec.index] || tags[dec.index] != dec.tag) {
fetch_block(addr - dec.offset);
} else {
hit_count++; // Count cache hit
}
uint8_t* block_ptr = data.get() + dec.index * block_size + dec.offset;
size_t w = static_cast<size_t>(width);
if (little_endian) {
for (size_t i = 0; i < w; ++i) {
block_ptr[i] = (value >> (i * 8)) & 0xFF;
}
} else {
for (size_t i = 0; i < w; ++i) {
block_ptr[i] = (value >> ((w - 1 - i) * 8)) & 0xFF;
}
}
// Write-through to underlying memory
return this->underlying_memory->write(addr, width, value, little_endian);
}
public:
direct_cache() {
// Allocate storage
data = std::make_unique<uint8_t[]>(num_lines * block_size);
valid = std::make_unique<bool[]>(num_lines);
tags = std::make_unique<WORD_T[]>(num_lines);
// Initialize cache state
for (uint64_t i = 0; i < num_lines; ++i) {
valid[i] = false;
}
}
// Memory interface functions
std::optional<WORD_T> read(WORD_T addr, libvio::width_t width,
bool little_endian = true) override {
if (within_block(addr, width)) {
return read_block(addr, width, little_endian);
}
// Handle unaligned access
WORD_T block_mask = ~(block_size - 1);
WORD_T block_end = (addr & block_mask) + block_size;
WORD_T first_part_size = block_end - addr;
WORD_T second_addr = block_end;
WORD_T second_part_size = static_cast<WORD_T>(width) - first_part_size;
auto part1 = read_block(addr, static_cast<libvio::width_t>(first_part_size),
little_endian);
auto part2 = read_block(second_addr, static_cast<libvio::width_t>(second_part_size),
little_endian);
if (!part1 || !part2) return std::nullopt;
if (little_endian) {
return *part1 | (*part2 << (first_part_size * 8));
} else {
return (*part1 << (second_part_size * 8)) | *part2;
}
}
bool write(WORD_T addr, libvio::width_t width, WORD_T value,
bool little_endian = true) override {
if (within_block(addr, width)) {
return write_block(addr, width, value, little_endian);
}
// Handle unaligned access
WORD_T block_mask = ~(block_size - 1);
WORD_T block_end = (addr & block_mask) + block_size;
WORD_T first_part_size = block_end - addr;
WORD_T second_addr = block_end;
WORD_T second_part_size = static_cast<WORD_T>(width) - first_part_size;
WORD_T part1, part2;
if (little_endian) {
part1 = value & ((1ULL << (first_part_size * 8)) - 1);
part2 = value >> (first_part_size * 8);
} else {
part1 = value >> (second_part_size * 8);
part2 = value & ((1ULL << (second_part_size * 8)) - 1);
}
bool success1 = write_block(addr, static_cast<libvio::width_t>(first_part_size),
part1, little_endian);
bool success2 = write_block(second_addr, static_cast<libvio::width_t>(second_part_size),
part2, little_endian);
return success1 && success2;
}
// Invalidation functions
void invalidate(WORD_T addr) override {
auto dec = decompose(addr);
valid[dec.index] = false;
}
void invalidate() override {
for (uint64_t i = 0; i < num_lines; ++i) {
valid[i] = false;
}
}
// Hit/miss statistics accessors
uint64_t hits() const override { return hit_count; }
uint64_t misses() const override { return miss_count; }
// Cache state management
void save(const char* filename) const override {
std::ofstream out(filename, std::ios::binary);
if (!out) return;
// Write header (num_lines and block_size)
uint64_t header[2] = {hit_count, miss_count};
out.write(reinterpret_cast<const char*>(header), sizeof(header));
// Write valid flags
std::vector<uint8_t> valid_bytes(num_lines);
for (uint64_t i = 0; i < num_lines; ++i) {
valid_bytes[i] = valid[i] ? 1 : 0;
}
out.write(reinterpret_cast<const char*>(valid_bytes.data()), num_lines);
// Write tags
out.write(reinterpret_cast<const char*>(tags.get()), num_lines * sizeof(WORD_T));
// Write data
out.write(reinterpret_cast<const char*>(data.get()), num_lines * block_size);
}
WORD_T restore(const char* filename) override {
std::ifstream in(filename, std::ios::binary);
if (!in) return 0;
// Read header
uint64_t header[2];
in.read(reinterpret_cast<char*>(header), sizeof(header));
hit_count = header[0];
miss_count = header[1];
// Read valid flags
std::vector<uint8_t> valid_bytes(num_lines);
in.read(reinterpret_cast<char*>(valid_bytes.data()), num_lines);
for (uint64_t i = 0; i < num_lines; ++i) {
valid[i] = (valid_bytes[i] != 0);
}
// Read tags
in.read(reinterpret_cast<char*>(tags.get()), num_lines * sizeof(WORD_T));
// Read data
in.read(reinterpret_cast<char*>(data.get()), num_lines * block_size);
return num_lines * block_size; // Return bytes read
}
};
} // namespace libcpu
#endif

View File

@ -7,8 +7,8 @@
#include <iostream>
#include <libcpu/abstract_cpu.hh>
#include <libcpu/event.hh>
#include <libvio/ringbuffer.hh>
#include <libvio/width.hh>
#include <libanemo/ringbuffer.hh>
#include <libanemo/width.hh>
#include <ostream>
#include <vector>
@ -36,35 +36,35 @@ class abstract_difftest: public abstract_cpu<WORD_T> {
}
virtual const char *gpr_name(uint8_t addr) const override {
return ref->gpr_name(addr);
return dut->gpr_name(addr);
}
virtual uint8_t gpr_addr(const char *name) const override {
return ref->gpr_addr(name);
return dut->gpr_addr(name);
}
WORD_T get_pc(void) const override {
return ref->get_pc();
return dut->get_pc();
}
const WORD_T *get_gpr(void) const override {
return ref->get_gpr();
return dut->get_gpr();
}
WORD_T get_gpr(uint8_t addr) const override {
return ref->get_gpr(addr);
return dut->get_gpr(addr);
}
std::optional<WORD_T> vaddr_to_paddr(WORD_T vaddr) const override {
return ref->vaddr_to_paddr(vaddr);
return dut->vaddr_to_paddr(vaddr);
}
std::optional<WORD_T> vmem_peek(WORD_T addr, libvio::width_t width) const override {
return ref->vmem_peek(addr, width);
std::optional<WORD_T> vmem_peek(WORD_T addr, libanemo::width_t width) const override {
return dut->vmem_peek(addr, width);
}
std::optional<WORD_T> pmem_peek(WORD_T addr, libvio::width_t width) const override {
return ref->pmem_peek(addr, width);
std::optional<WORD_T> pmem_peek(WORD_T addr, libanemo::width_t width) const override {
return dut->pmem_peek(addr, width);
}
/**
@ -75,7 +75,21 @@ class abstract_difftest: public abstract_cpu<WORD_T> {
virtual bool get_difftest_error(void) const = 0;
virtual bool stopped(void) const override {
return get_difftest_error() || ref->stopped();
if (get_difftest_error()) {
return true;
} else if (ref->stopped()) {
if (!dut->stopped()) {
std::cerr << "libcpu: REF has stopped but DUT has not." << std::endl;
}
return true;
} else if (dut->stopped()) {
if (!ref->stopped()) {
std::cerr << "libcpu: DUT has stopped but REF has not." << std::endl;
}
return true;
} else {
return false;
}
}
virtual void reset(WORD_T init_pc) override {
@ -101,7 +115,7 @@ class abstract_difftest: public abstract_cpu<WORD_T> {
template <typename WORD_T>
class simple_difftest: public abstract_difftest<WORD_T> {
public:
using event_buffer_t = libvio::ringbuffer<event_t<WORD_T>>;
using event_buffer_t = libanemo::ringbuffer<event_t<WORD_T>>;
private:
size_t dut_buffer_index = 0;
@ -126,19 +140,17 @@ class simple_difftest: public abstract_difftest<WORD_T> {
return;
}
// This implementation only compares resister writes
// This implementation only compares resister writes and traps
// Because register being written means some instruction has been commited.
// And all instructions are commited in order.
// And traps cannot happen out of ordder.
// This is the only assumption that can be made across all types of CPUs.
auto pull_events = [this](std::vector<event_t<WORD_T>> &dest, event_buffer_t *buffer, size_t begin, bool append=false) {
auto pull_events = [this](std::vector<event_t<WORD_T>> &dest, event_buffer_t *buffer, size_t begin) {
for (size_t i=begin; i<buffer->lastindex(); ++i) {
event_t<WORD_T> event = (*buffer)[i];
if (event.type==event_type_t::reg_write) {
if (event.type==event_type_t::reg_write || event.type==event_type_t::trap || event.type==event_type_t::trap_ret) {
dest.push_back(event);
}
if (append && this->event_buffer!=nullptr) {
this->event_buffer->push_back(event);
}
}
return buffer->lastindex();
};
@ -148,10 +160,10 @@ class simple_difftest: public abstract_difftest<WORD_T> {
std::vector<event_t<WORD_T>> dut_events{};
this->dut->next_cycle();
// record the events of DUT
dut_buffer_index = pull_events(dut_events, this->dut->event_buffer, dut_buffer_index, true);
dut_buffer_index = pull_events(dut_events, this->dut->event_buffer, dut_buffer_index);
// step the ref
std::vector<event_t<WORD_T>> ref_events{};
while (ref_events.size() < dut_events.size()) {
while (ref_events.size()<dut_events.size() && !this->ref->stopped()) {
this->ref->next_instruction();
// record the events of REF
ref_buffer_index = pull_events(ref_events, this->ref->event_buffer, ref_buffer_index);

View File

@ -5,6 +5,7 @@
#include <string>
#include <sstream>
#include <iomanip>
#include <cstdint>
/**
* @file event.hh
@ -19,16 +20,16 @@ namespace libcpu {
*
* Each event type has specific meanings for its val1 and val2 fields
*/
enum class event_type_t {
enum class event_type_t: uint8_t {
empty = 0, ///< Empty event, used for internal purpose only
issue, ///< Instruction issued - val1: instr_part1, val2: instr_part2
issue, ///< Instruction issued - val1: instr_part1, val2: instr_part2 or zero
reg_write, ///< Register written - val1: rd_addr, val2: rd_data
load, ///< Memory load - val1: addr, val2: zero extended data
load, ///< Memory load - val1: addr, val2: zero extended data or zero
store, ///< Memory store - val1: addr, val2: zero extended data
call, ///< Function call - val1: target_addr, val2: stack_pointer
call_ret, ///< Function return - val1: target_addr, val2: stack_pointer
call, ///< Function call - val1: target_addr, val2: source_stack_pointer
call_ret, ///< Function return - val1: target_addr, val2: target_stack_pointer
trap, ///< Trap handling - val1: mcause, val2: mtval
trap_ret, ///< Trap return - val1: target_addr, val2: mstatus
trap_ret, ///< Trap return - val1: target_addr, val2: custom value
diff_error, ///< Difftest error - val1: event_type, val2: instr_part1
n_event_type ///< A place holder indicating the number of event types
};
@ -56,14 +57,34 @@ inline const char *event_type_to_str(event_type_t type) {
/**
* @struct event_t
* @brief Template structure representing a CPU event
*
* `libanemo` does not enforce strict semantics for each field. The implementation treats
* `val1` and `val2` as generic values whose concrete meanings are context-dependent.
* This design choice maintains simplicity, flexibility, and performance,
* However, their interpretation must remain consistent across different implementations
* during differential testing.
*
* For example:
* - For `load` events:
* - When representing a load attempt that might trap: `val2` must be zero
* - When representing a successful load: `val2` must contain the zero-extended data
* - For `store` events:
* - May represent either a store attempt or a successful store operation
* - For `trap_ret` events:
* - May represent either an attempt of xRET on RISC-V, or a successful xRET
* - `val2` is for custom uses
*
* Certain library components may impose additional requirements. For example,
* some `io_dispatcher` subclasses requires `load` to represent only successful loads.
*
* @tparam WORD_T The word type used for event values (typically uint32_t or uint64_t)
*/
template <typename WORD_T>
struct event_t {
event_type_t type; ///< Type of the event
WORD_T pc; ///< Program counter associated with the event
WORD_T val1; ///< First value (meaning depends on event type)
WORD_T val2; ///< Second value (meaning depends on event type)
WORD_T val1; ///< Event-specific primary value (interpretation depends on event type)
WORD_T val2; ///< Event-specific secondary value (interpretation depends on event type)
/**
* @brief Equality comparison operator for event_t
@ -136,7 +157,7 @@ struct event_t {
std::ostringstream oss;
oss << std::left << std::setw(10) << std::setfill(' ') << event_type_to_str(type)
<< " pc:0x" << std::hex << std::setw(sizeof(WORD_T)*2) << std::setfill('0') << pc << " "
<< " pc:0x" << std::hex << std::setw(sizeof(WORD_T)*2) << std::setfill('0') << std::right << pc << " "
<< std::left << std::setw(label_width) << std::setfill(' ') << std::right << label1 << ":0x" << std::hex << std::setw(sizeof(WORD_T)*2) << std::setfill('0') << val1 << " "
<< std::left << std::setw(label_width) << std::setfill(' ') << std::right << label2 << ":0x" << std::hex << std::setw(sizeof(WORD_T)*2) << std::setfill('0') << val2;

View File

@ -1,288 +1,161 @@
#ifndef LIBCPU_MEMORY_HH
#define LIBCPU_MEMORY_HH
#include <algorithm>
#include <cstddef>
#include <cstdint>
#include <elf.h>
#include <fstream>
#include <libvio/frontend.hh>
#include <istream>
#include <ostream>
#include <libanemo/width.hh>
#include <memory>
#include <optional>
namespace libcpu {
/**
* @brief Abstract base class template for memory interfaces.
*
* This class defines the interface for memory operations that can be implemented
* by concrete memory classes. It provides pure virtual functions for reading,
* writing, and accessing memory. This class only provides architecture independent
* physical memory interface. It is the architecture that defines the behavior of
* virtual memory.
*
* @tparam WORD_T The type for address and data. Must be an integral type.
* @brief Abstract base class representing a view into memory.
*
* Provides read/write operations and memory management utilities for a specific
* memory region. Derived classes implement the actual storage mechanism.
*/
template <typename WORD_T>
class abstract_memory {
public:
/**
* @brief Read from memory at the specified address. This might have side-effects like caching.
*
* @param addr The memory address to read from.
* @param width The width of the data to read.
* @param little_endian If true, use little-endian byte ordering (default).
* If false, use big-endian byte ordering.
* @return std::optional<WORD_T> The zero extended read value if successful, or std::nullopt if failed.
*
* @note This function is designed for similating the memory access mechanism.
* To read the memory content for debugging, use `peek()` or `host_addr()`.
*/
virtual std::optional<WORD_T> read(WORD_T addr, libvio::width_t width, bool little_endian=true) = 0;
class memory_view {
public:
/**
* @brief Construct a memory view from another memory view.
*
* This class will map the memory in `src`, from `src_base` with the size of `view_size`
* to the address space of the new memory view from `view_base`.
*
* @param src The source memory view to create a view into
* @param src_base Base address of the new view (in the address space of source memory)
* @param view_base Base address of the new view (in the address space of new memory view)
* @param view_size Size of the new view in bytes
*/
memory_view(const memory_view &src, uint64_t src_base, uint64_t view_base, uint64_t view_size);
/**
* @brief Read from memory at the specified address. This has no side-effects like caching.
*
* @param addr The memory address to read from.
* @param width The width of the data to read.
* @param little_endian If true, use little-endian byte ordering (default).
* If false, use big-endian byte ordering.
* @return std::optional<WORD_T> The zero extended read value if successful, or std::nullopt if failed.
*
* @note This function is designed to access the memory content for debugging.
*/
virtual std::optional<WORD_T> peek(WORD_T addr, libvio::width_t width, bool little_endian=true) const = 0;
/**
* @brief Read data from memory.
*
* @param addr The memory address to read from
* @param width The width of the data to read
* @param little_endian Byte ordering (true for little-endian, false for big-endian)
* @return The read value, or std::nullopt if read failed
*/
std::optional<uint64_t> read(uint64_t addr, libanemo::width_t width, bool little_endian = true);
/**
* @brief Write to memory at the specified address. This might have side-effects like caching.
*
* @param addr The memory address to write to.
* @param width The width of the data to write.
* @param little_endian If true, use little-endian byte ordering (default).
* If false, use big-endian byte ordering.
* @return bool True if the write was successful, false otherwise.
*
* @note This function is designed for similating the memory access mechanism.
* To write the memory content for debugging or initailizing, use `set()` or `host_addr()`.
*/
virtual bool write(WORD_T addr, libvio::width_t width, WORD_T value, bool little_endian=true) = 0;
/**
* @brief Write data to memory.
*
* @param addr The memory address to write to
* @param width The width of the data to write (1-8 bytes)
* @param value The value to write
* @param little_endian Byte ordering (true for little-endian, false for big-endian)
* @return true if write succeeded, false if failed
*/
bool write(uint64_t addr, libanemo::width_t width, uint64_t value, bool little_endian = true);
/**
* @brief Write to memory at the specified address. This has no side-effects like caching.
*
* @param addr The memory address to write to.
* @param width The width of the data to write.
* @param little_endian If true, use little-endian byte ordering (default).
* If false, use big-endian byte ordering.
* @return bool True if the write was successful, false otherwise.
*
* @note This function is designed for debugging or initailizing.
*/
virtual bool set(WORD_T addr, libvio::width_t width, WORD_T value, bool little_endian=true) = 0;
/**
* @brief Get a direct pointer to host memory.
*
* @param addr Memory address to access
* @return Pointer to host memory at the specified address,
* or nullptr if address is invalid
*/
uint8_t* host_addr(uint64_t addr);
/**
* @brief Get a pointer to the host memory at the specified address.
*
* @param addr The memory address to access.
* @param value The value to write.
* @return uint8_t* Pointer to the host memory at the specified address,
* or nullptr if the address is invalid or this operation is not supported.
*
* @note This is intended to provide a convenient and effective way for the debugger to access the memory content.
* It has no magic to trigger side-effects like caching.
*/
virtual uint8_t *host_addr(WORD_T addr) = 0;
/**
* @brief Save memory contents to a file.
*
* @param filename Path to the output file
*/
void save(const char* filename) const;
/**
* @brief Save the memory contents to a file.
*
* @param filename The name of the file to save to.
*/
virtual void save(const char *filename) const = 0;
/**
* @brief Save memory contents to an output stream.
*
* @param out Output stream to write to
*/
void save(std::ostream& out) const;
/**
* @brief Restore the memory contents from a file.
*
* @param filename The name of the file to restore from.
* @returns The actual size loaded.
*
* @note Different subclasses can use different formats for its checkpoint files.
*/
virtual WORD_T restore(const char *filename) = 0;
/**
* @brief Restore memory contents from a file.
*
* @param filename Path to the input file
* @return Number of bytes successfully loaded
*/
uint64_t restore(const char* filename);
using elf_hdr_t = std::conditional_t<sizeof(WORD_T) == 4, Elf32_Ehdr, Elf64_Ehdr>;
using elf_phdr_t = std::conditional_t<sizeof(WORD_T) == 4, Elf32_Phdr, Elf64_Phdr>;
/**
* @brief Restore memory contents from an input stream.
*
* @param in Input stream to read from
* @return Number of bytes successfully loaded
*/
uint64_t restore(std::istream& in);
/**
* @brief Load an ELF binary from memory into the emulated memory space.
*
* This method parses the ELF header and program headers, then loads all loadable
* segments (PT_LOAD) into the emulated memory. The segments are copied from the
* buffer to their specified virtual addresses.
*
* @param buffer Pointer to the ELF binary data in memory
* @return WORD_T The entry point address specified in the ELF header
*
* @note The ELF binary must match the architecture's word size (32-bit or 64-bit)
* @note Only PT_LOAD segments are processed, other segment types are ignored
*/
virtual WORD_T load_elf(const uint8_t *buffer) {
elf_hdr_t *elf_header = (elf_hdr_t*)(buffer);
// load metadata
WORD_T entry = elf_header->e_entry;
// load each segment
elf_phdr_t *segment_headers = (elf_phdr_t*)(buffer+elf_header->e_phoff);
for (size_t i=0; i<elf_header->e_phnum; ++i) {
if (segment_headers[i].p_type != PT_LOAD) {
continue;
}
WORD_T seg_base = segment_headers[i].p_offset;
WORD_T seg_size = segment_headers[i].p_memsz;
WORD_T file_size = segment_headers[i].p_filesz;
// if one of p_paddr and p_vaddr is zero, use the non-zero one
// if both are non-zero but different, the behavior is undefined
uint8_t *target_addr = host_addr(segment_headers[i].p_vaddr | segment_headers[i].p_paddr);
if (target_addr == nullptr) {
continue;
}
// load the content
const uint8_t *seg_content = buffer + segment_headers[i].p_offset;
std::copy(seg_content, seg_content+file_size, target_addr);
// fill the remaining part with zero
if (seg_size > file_size) {
std::fill_n(target_addr+file_size, seg_size-file_size, 0);
}
}
return entry;
}
/**
* @brief Load an ELF binary into memory (auto-detects 32/64-bit format).
*
* @param buffer Pointer to the ELF file data in memory
* @return The entry point address of the loaded ELF
*/
uint64_t load_elf(const uint8_t* buffer);
/**
* @brief Load an ELF binary from a file into the emulated memory space.
*
* This method reads an ELF file from disk and loads it using the same logic
* as load_elf(). The file is read into memory and then processed.
*
* @param filename Path to the ELF file to load
* @return WORD_T The entry point address specified in the ELF header
*/
virtual WORD_T load_elf_from_file(const char *filename) {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
auto filesize = file.tellg();
std::unique_ptr<uint8_t[]> buffer{new uint8_t[filesize]};
file.seekg(0);
file.read((char*)(buffer.get()), filesize);
return load_elf(buffer.get());
}
/**
* @brief Load an ELF file into memory (auto-detects 32/64-bit format).
*
* @param filename Path to the ELF file
* @return The entry point address of the loaded ELF
*/
uint64_t load_elf_from_file(const char* filename);
/**
* @brief Get the size of the memory region.
*
* @return uint64_t Size of the memory region in bytes
*/
uint64_t get_size() const;
/**
* @brief Check if a memory access would be out of bounds.
*
* @param addr The address to check
* @param width The width of the memory access
* @return bool True if the address range [addr, addr+width-1] is invalid,
* false otherwise
*/
bool out_of_bound(uint64_t addr, libanemo::width_t width) const;
protected:
uint64_t offset; ///< Pointer to the memory storage
uint64_t addr_lb; ///< Lowest address (inclusive)
uint64_t addr_ub; ///< Highest address (exclusive)
/**
* @brief Protected default constructor for derived classes.
*/
memory_view();
};
/**
* @class contiguous_memory
* @brief Concrete memory implementation using contiguous byte-array storage.
* @brief Concrete memory implementation using contiguous storage.
*
* This class provides memory operations with 64-bit addressing using a
* contiguous block of memory. All methods are non-virtual for performance.
*/
template <typename WORD_T>
class contiguous_memory: public abstract_memory<WORD_T> {
protected:
WORD_T base;
WORD_T size;
std::unique_ptr<uint8_t[]> mem;
class memory: public memory_view {
public:
/**
* @brief Construct a new vector memory object
* @brief Construct a new memory object with contiguous storage.
*
* @param mem_base Base address of the memory region
* @param mem_size Size of memory region in bytes
*/
contiguous_memory(WORD_T mem_base, size_t mem_size) {
base = mem_base;
size = mem_size;
mem = std::unique_ptr<uint8_t[]>{new uint8_t[mem_size]};
}
memory(uint64_t mem_base, size_t mem_size);
bool out_of_bound(WORD_T addr, libvio::width_t width) const {
size_t up_addr = addr + static_cast<size_t>(width);
return addr < base || up_addr > base+size;
}
WORD_T get_size(void) {
return size;
}
std::optional<WORD_T> read(WORD_T addr, libvio::width_t width, bool little_endian=true) override {
return peek(addr, width, little_endian);
}
std::optional<WORD_T> peek(WORD_T addr, libvio::width_t width, bool little_endian=true) const override {
if (out_of_bound(addr, width)) {
return {};
}
size_t start_offset = addr - base;
size_t w = static_cast<size_t>(width);
WORD_T value = 0;
if (little_endian) {
for (size_t i = 0; i<w; i++) {
value |= static_cast<WORD_T>(mem[start_offset+i]) << (i*8);
}
} else {
for (size_t i = 0; i<w; i++) {
value = (value << 8) | mem[start_offset+i];
}
}
return value;
}
bool write(WORD_T addr, libvio::width_t width, WORD_T value, bool little_endian=true) override {
return set(addr, width, value, little_endian);
}
bool set(WORD_T addr, libvio::width_t width, WORD_T value, bool little_endian=true) override {
size_t start_offset = addr - base;
size_t w = static_cast<size_t>(width);
if (out_of_bound(addr, width)) {
return false;
}
if (little_endian) {
for (size_t i = 0; i<w; i++) {
mem[start_offset+i] = (value >> (i*8)) & 0xFF;
}
} else {
for (size_t i = 0; i<w; i++) {
mem[start_offset+i] = (value >> ((w-1-i)*8)) & 0xFF;
}
}
return true;
}
uint8_t* host_addr(WORD_T addr) override {
if (out_of_bound(addr, libvio::width_t::byte)) {
return nullptr;
} else {
return mem.get() + (addr-base);
}
}
void save(const char* filename) const override {
std::ofstream out(filename, std::ios::binary);
if (!out) return;
out.write(reinterpret_cast<const char*>(mem.get()), size);
}
WORD_T restore(const char* filename) override {
std::ifstream in(filename, std::ios::binary | std::ios::ate);
if (!in) return 0;
size_t file_size = in.tellg();
in.seekg(0);
size_t bytes_to_read = std::min(file_size, size_t(size));
in.read(reinterpret_cast<char*>(mem.get()), bytes_to_read);
return bytes_to_read;
}
protected:
std::unique_ptr<uint8_t[]> mem; ///< Contiguous memory storage
};
}
} // namespace libcpu
#endif

View File

@ -1,80 +0,0 @@
#ifndef LIBCPU_RISCV_HH
#define LIBCPU_RISCV_HH
#include <cstdint>
#include <cstdlib>
#include <cstring>
namespace libcpu::riscv {
enum class priv_level_t {
u = 0,
h = 1,
s = 2,
m = 3,
};
/**
* @brief Enumeration of RISC-V general-purpose register addresses.
*/
enum gpr_addr_t: uint8_t {
X0 = 0, ///< Hard-wired zero (x0). Always returns 0 when read.
RA = 1, ///< Return address (x1). Stores return address for function calls.
SP = 2, ///< Stack pointer (x2). Points to the top of the stack.
GP = 3, ///< Global pointer (x3). Points to global data area.
TP = 4, ///< Thread pointer (x4). Used for thread-local storage.
T0 = 5, ///< Temporary/alternate link register (x5).
T1 = 6, ///< Temporary register (x6).
T2 = 7, ///< Temporary register (x7).
S0 = 8, ///< Saved register/frame pointer (x8). Also called FP.
S1 = 9, ///< Saved register (x9). Preserved across function calls.
A0 = 10, ///< Function argument/return value (x10). First argument.
A1 = 11, ///< Function argument/return value (x11). Second argument.
A2 = 12, ///< Function argument (x12). Third argument.
A3 = 13, ///< Function argument (x13). Fourth argument.
A4 = 14, ///< Function argument (x14). Fifth argument.
A5 = 15, ///< Function argument (x15). Sixth argument.
A6 = 16, ///< Function argument (x16). Seventh argument.
A7 = 17, ///< Function argument (x17). Eighth argument (syscall number).
S2 = 18, ///< Saved register (x18).
S3 = 19, ///< Saved register (x19).
S4 = 20, ///< Saved register (x20).
S5 = 21, ///< Saved register (x21).
S6 = 22, ///< Saved register (x22).
S7 = 23, ///< Saved register (x23).
S8 = 24, ///< Saved register (x24).
S9 = 25, ///< Saved register (x25).
S10 = 26, ///< Saved register (x26).
S11 = 27, ///< Saved register (x27).
T3 = 28, ///< Temporary register (x28).
T4 = 29, ///< Temporary register (x29).
T5 = 30, ///< Temporary register (x30).
T6 = 31 ///< Temporary register (x31).
};
inline constexpr const char *gpr_names[] = {
"x0", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1",
"a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3",
"s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"
};
inline const char* gpr_name(uint8_t addr) {
return gpr_names[addr];
}
inline int8_t gpr_addr(const char* name) {
if (name[0] == 'x') {
auto num = strtoul(name+1, nullptr, 10);
return static_cast<uint8_t>(num);
}
for (uint8_t i=0; i<32; ++i) {
if (strcmp(name,gpr_names[i]) == 0) {
return i;
}
}
return 0;
}
}
#endif

View File

@ -0,0 +1,47 @@
#include <vector>
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <libcpu/riscv/riscv.hh>
#include <libcpu/riscv/user_core.hh>
namespace libcpu::riscv {
template <typename WORD_T, size_t offset_bits, size_t shamt>
class decode_cache {
public:
static constexpr size_t capacity = ~(~size_t(0)<<offset_bits) + 1;
static constexpr WORD_T mask = ~(~WORD_T(0) << (offset_bits+shamt));
std::vector<std::pair<uint32_t, decode_t>> cache{capacity};
decode_cache(void);
void decode(exec_result_t<WORD_T> &op);
};
template <typename WORD_T, size_t offset_bits, size_t shamt>
decode_cache<WORD_T, offset_bits, shamt>::decode_cache(void) {
for (auto &entry: cache) {
entry = {0, {.imm=0, .dispatch=dispatch_t::invalid, .rs1=0, .rs2=0, .rd=0}};
}
}
template <typename WORD_T, size_t offset_bits, size_t shamt>
void decode_cache<WORD_T, offset_bits, shamt>::decode(exec_result_t<WORD_T> &op) {
assert(op.type == exec_result_type_t::fetch);
uint32_t offset = (op.pc & mask) >> shamt;
auto cached = cache[offset];
if (op.instr == cached.first) {
op.type = exec_result_type_t::decode;
op.decode = cached.second;
} else {
user_core<WORD_T>::decode(op);
cache[offset] = {op.instr, op.decode};
}
assert(op.type == exec_result_type_t::decode);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,418 @@
#ifndef LIBCPU_RISCV_RISCV_HH
#define LIBCPU_RISCV_RISCV_HH
#include <climits>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <libanemo/width.hh>
namespace libcpu::riscv {
enum class priv_level_t: uint8_t {
u = 0,
s = 1,
m = 3,
};
/**
* @brief Enumeration of RISC-V general-purpose register addresses.
*/
enum gpr_addr_t: uint8_t {
X0 = 0, RA = 1, SP = 2, GP = 3, TP = 4, T0 = 5, T1 = 6, T2 = 7,
S0 = 8, S1 = 9, A0 = 10, A1 = 11, A2 = 12, A3 = 13, A4 = 14, A5 = 15,
A6 = 16, A7 = 17, S2 = 18, S3 = 19, S4 = 20, S5 = 21, S6 = 22, S7 = 23,
S8 = 24, S9 = 25, S10 = 26, S11 = 27, T3 = 28, T4 = 29, T5 = 30, T6 = 31
};
/**
* @brief Array of general purpose register names
*
* Contains the ABI names for all 32 RISC-V general purpose registers.
* Index corresponds to register number (x0-x31).
*/
inline constexpr const char *gpr_names[] = {
"zero", "ra", "sp", "gp", "tp", "t0", "t1", "t2", "s0", "s1",
"a0", "a1", "a2", "a3", "a4", "a5", "a6", "a7", "s2", "s3",
"s4", "s5", "s6", "s7", "s8", "s9", "s10", "s11", "t3", "t4", "t5", "t6"
};
/**
* @brief Get the name of a general purpose register
* @param addr Register number (0-31)
* @return Register name string
*/
inline constexpr const char* gpr_name(uint8_t addr) {
return gpr_names[addr];
}
/**
* @brief Get the address/number of a general purpose register by name
* @param name Register name (either ABI name like "ra" or numeric like "x1")
* @return Register number (0-31)
*
* @note Returns 0 if name is not found (matches x0 behavior)
*/
inline constexpr uint_fast8_t gpr_addr(const char* name) {
if (name[0] == 'x') {
auto num = strtoul(name+1, nullptr, 10);
return static_cast<uint8_t>(num);
}
for (uint8_t i=0; i<32; ++i) {
if (strcmp(name,gpr_names[i]) == 0) {
return i;
}
}
return 0;
}
/**
* @brief Control and Status Register (CSR) addresses
*
* Contains all standard RISC-V CSR addresses as static constants
*/
struct csr_addr {
// Supervisor-Level CSRs
static constexpr uint16_t sstatus = 0x100; ///< Supervisor status register
static constexpr uint16_t sie = 0x104; ///< Supervisor interrupt enable register
static constexpr uint16_t stvec = 0x105; ///< Supervisor trap handler base address
static constexpr uint16_t scounteren = 0x106; ///< Supervisor counter enable register
static constexpr uint16_t senvcfg = 0x10A; ///< Supervisor environment configuration register
static constexpr uint16_t scountinhibit = 0x120; ///< Supervisor counter inhibit register
static constexpr uint16_t sscratch = 0x140; ///< Supervisor scratch register
static constexpr uint16_t sepc = 0x141; ///< Supervisor exception program counter
static constexpr uint16_t scause = 0x142; ///< Supervisor trap cause register
static constexpr uint16_t stval = 0x143; ///< Supervisor bad address or instruction
static constexpr uint16_t sip = 0x144; ///< Supervisor interrupt pending register
static constexpr uint16_t scountovf = 0xDA0; ///< Supervisor counter overflow register
static constexpr uint16_t satp = 0x180; ///< Supervisor address translation and protection register
static constexpr uint16_t scontext = 0x5A8; ///< Supervisor context register
// Machine Information Registers
static constexpr uint16_t mvendorid = 0xF11; ///< Vendor ID register
static constexpr uint16_t marchid = 0xF12; ///< Architecture ID register
static constexpr uint16_t mimpid = 0xF13; ///< Implementation ID register
static constexpr uint16_t mhartid = 0xF14; ///< Hardware thread ID register
static constexpr uint16_t mconfigptr = 0xF15; ///< Machine configuration pointer register
// Machine Trap Setup
static constexpr uint16_t mstatus = 0x300; ///< Machine status register
static constexpr uint16_t misa = 0x301; ///< ISA and extensions register
static constexpr uint16_t medeleg = 0x302; ///< Machine exception delegation register
static constexpr uint16_t mideleg = 0x303; ///< Machine interrupt delegation register
static constexpr uint16_t mie = 0x304; ///< Machine interrupt enable register
static constexpr uint16_t mtvec = 0x305; ///< Machine trap handler base address
static constexpr uint16_t mcounteren = 0x306; ///< Machine counter enable register
static constexpr uint16_t mstatush = 0x310; ///< Additional machine status (RV32 only)
static constexpr uint16_t medeleg_h = 0x312; ///< Upper 32 bits of medeleg (RV32 only)
// Machine Trap Handling
static constexpr uint16_t mscratch = 0x340; ///< Machine scratch register
static constexpr uint16_t mepc = 0x341; ///< Machine exception program counter
static constexpr uint16_t mcause = 0x342; ///< Machine trap cause register
static constexpr uint16_t mtval = 0x343; ///< Machine bad address or instruction
static constexpr uint16_t mip = 0x344; ///< Machine interrupt pending register
static constexpr uint16_t mtinst = 0x34A; ///< Machine trap instruction register
static constexpr uint16_t mtval2 = 0x34B; ///< Machine bad guest physical address
};
/**
* @brief mcause register bit definitions
* @tparam WORD_T Word type (uint32_t or uint64_t)
*/
template <typename WORD_T>
struct mcause {
static constexpr WORD_T intr_mask = WORD_T(1) << (sizeof(WORD_T) * CHAR_BIT - 1); ///< Interrupt mask bit
static constexpr WORD_T intr_s_software = 1 | intr_mask; ///< Supervisor software interrupt
static constexpr WORD_T intr_m_software = 3 | intr_mask; ///< Machine software interrupt
static constexpr WORD_T intr_s_timer = 5 | intr_mask; ///< Supervisor timer interrupt
static constexpr WORD_T intr_m_timer = 7 | intr_mask; ///< Machine timer interrupt
static constexpr WORD_T intr_s_external = 9 | intr_mask; ///< Supervisor external interrupt
static constexpr WORD_T intr_m_external = 11 | intr_mask; ///< Machine external interrupt
static constexpr WORD_T intr_cnt_overflow = 13 | intr_mask; ///< Counter overflow interrupt
static constexpr WORD_T except_instr_misalign = 0; ///< Instruction address misaligned
static constexpr WORD_T except_instr_fault = 1; ///< Instruction access fault
static constexpr WORD_T except_illegal_instr = 2; ///< Illegal instruction
static constexpr WORD_T except_breakpoint = 3; ///< Breakpoint
static constexpr WORD_T except_load_misalign = 4; ///< Load address misaligned
static constexpr WORD_T except_load_fault = 5; ///< Load access fault
static constexpr WORD_T except_store_misalign = 6; ///< Store address misaligned
static constexpr WORD_T except_store_fault = 7; ///< Store access fault
static constexpr WORD_T except_env_call_u = 8; ///< Environment call from U-mode
static constexpr WORD_T except_env_call_s = 9; ///< Environment call from S-mode
static constexpr WORD_T except_env_call_m = 11; ///< Environment call from M-mode
static constexpr WORD_T except_instr_page_fault = 12; ///< Instruction page fault
static constexpr WORD_T except_load_page_fault = 13; ///< Load page fault
static constexpr WORD_T except_store_page_fault = 15; ///< Store page fault
static constexpr WORD_T except_software_check = 18; ///< Software check failure
static constexpr WORD_T except_hardware_error = 19; ///< Hardware error
};
/**
* @brief mstatus register bit definitions for both rv32 and rv64
* @tparam WORD_T Word type (uint32_t or uint64_t)
*/
template <typename WORD_T>
struct mstatus_common {
static constexpr WORD_T sie = WORD_T(1) << 1; ///< Supervisor interrupt enable
static constexpr WORD_T mie = WORD_T(1) << 3; ///< Machine interrupt enable
static constexpr WORD_T spie = WORD_T(1) << 5; ///< Previous supervisor interrupt enable
static constexpr WORD_T ube = WORD_T(1) << 6; ///< User-mode endianness (1=big-endian)
static constexpr WORD_T mpie = WORD_T(1) << 7; ///< Previous machine interrupt enable
static constexpr WORD_T spp = WORD_T(1) << 8; ///< Supervisor previous privilege mode
static constexpr WORD_T mpp = WORD_T(3) << 11; ///< Previous privilege mode
static constexpr WORD_T mppl = WORD_T(1) << 11; ///< Previous privilege mode (low bit)
static constexpr WORD_T mpph = WORD_T(1) << 12; ///< Previous privilege mode (high bit)
static constexpr WORD_T fs = WORD_T(3) << 12; ///< Floating-point unit status
static constexpr WORD_T fs0 = WORD_T(1) << 12; ///< Floating-point unit status (bit 0)
static constexpr WORD_T fs1 = WORD_T(1) << 13; ///< Floating-point unit status (bit 1)
static constexpr WORD_T xs = WORD_T(3) << 14; ///< Extension status
static constexpr WORD_T xsl = WORD_T(1) << 14; ///< Extension status (bit 0)
static constexpr WORD_T xsh = WORD_T(1) << 15; ///< Extension status (bit 1)
static constexpr WORD_T vs = WORD_T(3) << 16; ///< Vector extension status
static constexpr WORD_T vsl = WORD_T(1) << 16; ///< Vector extension status (bit 0)
static constexpr WORD_T vsh = WORD_T(1) << 17; ///< Vector extension status (bit 1)
static constexpr WORD_T mprv = WORD_T(1) << 17; ///< Modify privilege for memory accesses
static constexpr WORD_T sum = WORD_T(1) << 18; ///< Permit supervisor user memory access
static constexpr WORD_T mxr = WORD_T(1) << 19; ///< Make executable pages readable
static constexpr WORD_T tvm = WORD_T(1) << 20; ///< Trap virtual memory operations
static constexpr WORD_T tw = WORD_T(1) << 21; ///< Timeout wait for WFI instruction
static constexpr WORD_T tsr = WORD_T(1) << 22; ///< Trap SRET instruction
static constexpr WORD_T sd = WORD_T(1) << (sizeof(WORD_T) * CHAR_BIT - 1); ///< State dirty flag
};
template <typename WORD_T>
struct mstatus: mstatus_common<WORD_T> {};
template <>
struct mstatus<uint32_t>: mstatus_common<uint32_t> {};
/**
* @brief mstatus register specialization for 64-bit
*/
template <>
struct mstatus<uint64_t>: mstatus_common<uint64_t> {
static constexpr uint64_t uxl = uint64_t(3) << 32; ///< User XLEN
static constexpr uint64_t uxll = uint64_t(1) << 32; ///< User XLEN low bit
static constexpr uint64_t uxlh = uint64_t(1) << 33; ///< User XLEN high bit
static constexpr uint64_t sxl = uint64_t(3) << 34; ///< Supervisor XLEN
static constexpr uint64_t sxll = uint64_t(1) << 34; ///< Supervisor XLEN low bit
static constexpr uint64_t sxlh = uint64_t(1) << 35; ///< Supervisor XLEN high bit
static constexpr uint64_t sbe = uint64_t(1) << 36; ///< Supervisor endianness bit
static constexpr uint64_t mbe = uint64_t(1) << 37; ///< Machine endianness bit
};
/**
* @brief mstatush register bit definitions (RV32 only)
*/
struct mstatush {
static constexpr uint32_t sbe = uint32_t(1) << 4; ///< Supervisor endianness bit
static constexpr uint32_t mbe = uint32_t(1) << 5; ///< Machine endianness bit
};
/**
* @brief sstatus register bit definitions
* @tparam WORD_T Word type (uint32_t or uint64_t)
*/
template <typename WORD_T>
struct sstatus {
static constexpr WORD_T sie = WORD_T(1) << 1; ///< Supervisor interrupt enable
static constexpr WORD_T spie = WORD_T(1) << 5; ///< Previous supervisor interrupt enable
static constexpr WORD_T ube = WORD_T(1) << 6; ///< User-mode endianness (1=big-endian)
static constexpr WORD_T spp = WORD_T(1) << 8; ///< Previous privilege mode (S=1, U=0)
static constexpr WORD_T vsl = WORD_T(1) << 9; ///< Vector unit status
static constexpr WORD_T vs = WORD_T(3) << 10; ///< Vector unit status
static constexpr WORD_T vsh = WORD_T(1) << 10; ///< Vector unit status
static constexpr WORD_T fs = WORD_T(3) << 13; ///< Floating-point unit status
static constexpr WORD_T fsl = WORD_T(1) << 13; ///< Floating-point unit status
static constexpr WORD_T fsh = WORD_T(1) << 14; ///< Floating-point unit status
static constexpr WORD_T xs = WORD_T(3) << 15; ///< Extension status
static constexpr WORD_T xsl = WORD_T(1) << 15; ///< Extension status
static constexpr WORD_T xsh = WORD_T(1) << 16; ///< Extension status
static constexpr WORD_T sum = WORD_T(1) << 18; ///< Permit supervisor user memory access
static constexpr WORD_T mxr = WORD_T(1) << 19; ///< Make executable pages readable
static constexpr WORD_T sd = WORD_T(1) << (sizeof(WORD_T) * CHAR_BIT - 1); ///< State dirty flag
};
/**
* @brief mtvec register bit definitions
* @tparam WORD_T Word type (uint32_t or uint64_t)
*/
template <typename WORD_T>
struct mtvec {
static constexpr WORD_T vectored = 1; ///< Trap mode: vectored (1) or direct (0)
};
template <typename WORD_T>
using stvec = mtvec<WORD_T>;
/**
* @brief mip register bit definitions
* @tparam WORD_T Word type (uint32_t or uint64_t)
*/
template <typename WORD_T>
struct mip {
static constexpr WORD_T ssip = 1 << 1; ///< Supervisor software interrupt pending
static constexpr WORD_T msip = 1 << 3; ///< Machine software interrupt pending
static constexpr WORD_T stip = 1 << 5; ///< Supervisor timer interrupt pending
static constexpr WORD_T mtip = 1 << 7; ///< Machine timer interrupt pending
static constexpr WORD_T seip = 1 << 9; ///< Supervisor external interrupt pending
static constexpr WORD_T meip = 1 << 11; ///< Machine external interrupt pending
static constexpr WORD_T lcofip = 1 << 13; ///< Local counter overflow interrupt pending
};
template <typename WORD_T>
using stip = mip<WORD_T>;
/**
* @brief mie register bit definitions
* @tparam WORD_T Word type (uint32_t or uint64_t)
*/
template <typename WORD_T>
struct mie {
static constexpr WORD_T ssie = 1 << 1; ///< Supervisor software interrupt enable
static constexpr WORD_T msie = 1 << 3; ///< Machine software interrupt enable
static constexpr WORD_T stie = 1 << 5; ///< Supervisor timer interrupt enable
static constexpr WORD_T mtie = 1 << 7; ///< Machine timer interrupt enable
static constexpr WORD_T seie = 1 << 9; ///< Supervisor external interrupt enable
static constexpr WORD_T meie = 1 << 11; ///< Machine external interrupt enable
static constexpr WORD_T lcofie = 1 << 13; ///< Local counter overflow interrupt enable
};
template <typename WORD_T>
using stie = mie<WORD_T>;
/**
* @brief Enumeration of dispatchable instruction types
*/
enum class dispatch_t: uint8_t {
// Arithmetic & Logical
add, sub, sll, slt, sltu, xor_, srl, sra, or_, and_,
// Immediate Operations
addi, slti, sltiu, xori, ori, andi, slli, srli, srai,
// Memory Operations
lb, lh, lw, lbu, lhu, sb, sh, sw,
// Control Flow
jal, jalr, beq, bne, blt, bge, bltu, bgeu,
// Upper Immediate
lui, auipc,
// Multiply/Divide
mul, mulh, mulhsu, mulhu, div, divu, rem, remu,
// Fence instuctions
fence, fence_i,
// System
ecall, ebreak, mret, sret, sfence_vma,
// RV64 specific instructions
lwu, ld, sd, addiw, slliw, srliw, sraiw, addw, subw, sllw, srlw, sraw, mulw, divw, divuw, remw, remuw,
// Atomic memory operations
lr_w, sc_w, amoswap_w, amoadd_w, amoxor_w, amoand_w, amoor_w, amomin_w, amomax_w, amominu_w, amomaxu_w,
lr_d, sc_d, amoswap_d, amoadd_d, amoxor_d, amoand_d, amoor_d, amomin_d, amomax_d, amominu_d, amomaxu_d,
// CSR functions
csrrw, csrrs, csrrc, csrrwi, csrrsi, csrrci,
// Invalid instruction
invalid,
};
/**
* @brief Execution result types
*/
enum class exec_result_type_t: uint8_t {
fetch, decode,
retire, ///< Committed or trap handled
load, store, amo,
trap, sys_op, csr_op
};
/**
* @brief Atomic memory operation types
*/
enum class amo_type_t: uint8_t {
swap, add, xor_, and_, or_, min, max, min_u, max_u
};
/**
* @brief A decoded RISC-V instruction
*/
struct decode_t {
int32_t imm;
dispatch_t dispatch;
uint8_t rs1; uint8_t rs2; uint8_t rd;
};
template <typename WORD_T>
/**
* @brief Execution result structure
*
* Describes modifications to unprivileged architectural state or operation details
* for privileged operations. The user core handles only unprivileged operations;
* privileged operations must be implemented by dedicated modules.
*
* Note: Memory operations are inherently privileged as they may involve address
* translation and physical memory protection. For flexibility, these operations
* should be implemented by separate modules. The user core is not responsible
* for handling MMIO, address translation, or similar memory-related functions.
*
* Modifications to privileged architectural state must be implemented by the
* privileged module. This clear separation between privileged and unprivileged
* components improves performance and enables code reuse.
*/
struct exec_result_t {
exec_result_type_t type; ///< Type of execution result
WORD_T pc; ///< PC of this instruction
WORD_T next_pc; ///< Expected PC of the next instruction
uint32_t instr;
union {
decode_t decode;
struct {
uint8_t rd; WORD_T value;
} retire;
struct {
WORD_T addr; libanemo::width_t width; bool sign_extend; uint8_t rd; bool reserved;
} load;
struct {
WORD_T addr; libanemo::width_t width; WORD_T data; uint8_t rd; bool conditional;
} store;
struct {
WORD_T addr; libanemo::width_t width; WORD_T data; uint8_t rd; amo_type_t type;
} amo;
struct {
WORD_T cause; WORD_T tval;
} trap;
struct {
bool ecall; bool mret; bool sret; bool sfence_vma;
} sys_op;
struct {
uint16_t addr; uint8_t rd;
bool read; bool write; bool set; bool clear; WORD_T value;
} csr_op;
};
};
/**
* @brief RISC-V address translation modes.
*/
enum class satp_mode_t: uint8_t {
bare=0, sv32=1, sv39=8, sv48=9, sv57=10, sv64=11
};
}
/**
* @brief page table entry mask
* @tparam WORD_T Word type (uint32_t or uint64_t)
*/
template <typename WORD_T>
struct pte_mask {
static constexpr WORD_T v = 1 << 0;
static constexpr WORD_T r = 1 << 1;
static constexpr WORD_T w = 1 << 2;
static constexpr WORD_T x = 1 << 3;
static constexpr WORD_T u = 1 << 4;
static constexpr WORD_T g = 1 << 5;
static constexpr WORD_T a = 1 << 6;
static constexpr WORD_T d = 1 << 7;
static constexpr WORD_T rsw = 3 << 8;
};
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,190 @@
#ifndef LIBCPU_RISCV_CPU_SYSYTEM_HH
#define LIBCPU_RISCV_CPU_SYSYTEM_HH
#include <cstdint>
#include <libcpu/abstract_cpu.hh>
#include <libcpu/riscv/riscv.hh>
#include <libcpu/riscv/user_core.hh>
#include <libcpu/riscv/privilege_module.hh>
namespace libcpu {
template <typename WORD_T>
class riscv_cpu_system: public abstract_cpu<WORD_T> {
public:
using dispatch_t = riscv::dispatch_t;
using decode_t = riscv::decode_t;
using exec_result_type_t = riscv::exec_result_type_t;
using exec_result_t = riscv::exec_result_t<WORD_T>;
virtual uint8_t n_gpr(void) const override;
virtual const char *gpr_name(uint8_t addr) const override;
virtual uint8_t gpr_addr(const char *name) const override;
virtual void reset(WORD_T init_pc) override;
virtual WORD_T get_pc(void) const override;
virtual const WORD_T *get_gpr(void) const override;
virtual WORD_T get_gpr(uint8_t addr) const override;
virtual void next_cycle(void) override;
virtual void next_instruction(void) override;
virtual bool stopped(void) const override;
virtual std::optional<WORD_T> get_trap(void) const override;
private:
exec_result_t exec_result;
riscv::user_core<WORD_T> user_core;
riscv::privilege_module<WORD_T> privilege_module;
std::optional<WORD_T> last_trap;
bool is_stopped;
};
template <typename WORD_T>
uint8_t riscv_cpu_system<WORD_T>::n_gpr(void) const {
return 32;
}
template <typename WORD_T>
const char *riscv_cpu_system<WORD_T>::gpr_name(uint8_t addr) const {
return riscv::gpr_name(addr);
}
template <typename WORD_T>
uint8_t riscv_cpu_system<WORD_T>::gpr_addr(const char *name) const {
return riscv::gpr_addr(name);
}
template <typename WORD_T>
void riscv_cpu_system<WORD_T>::reset(WORD_T init_pc) {
privilege_module.instr_bus = this->instr_bus;
privilege_module.data_bus = this->data_bus;
privilege_module.mmio_bus = this->mmio_bus;
user_core.reset();
privilege_module.reset();
exec_result.type = riscv::exec_result_type_t::retire;
exec_result.pc = init_pc;
last_trap = std::nullopt;
is_stopped = false;
}
template <typename WORD_T>
WORD_T riscv_cpu_system<WORD_T>::get_pc(void) const {
return exec_result.pc;
}
template <typename WORD_T>
const WORD_T *riscv_cpu_system<WORD_T>::get_gpr(void) const {
return user_core.gpr;
}
template <typename WORD_T>
WORD_T riscv_cpu_system<WORD_T>::get_gpr(uint8_t addr) const {
return user_core.gpr[addr];
}
template <typename WORD_T>
void riscv_cpu_system<WORD_T>::next_cycle(void) {
next_instruction();
}
template <typename WORD_T>
void riscv_cpu_system<WORD_T>::next_instruction(void) {
privilege_module.vaddr_fetch_instruction(exec_result);
if (exec_result.type == exec_result_type_t::fetch) {
if (this->event_buffer!=nullptr) {
this->event_buffer->push_back({.type=event_type_t::issue, .pc=exec_result.pc, .val1=exec_result.instr, .val2=0});
}
riscv::user_core<WORD_T>::decode(exec_result);
}
if (exec_result.type == exec_result_type_t::decode) {
user_core.execute(exec_result);
}
// Do privileged operations
if (exec_result.type == exec_result_type_t::load) {
if (this->event_buffer!=nullptr) {
auto [addr, width, sign_extend, rd, reserved] = exec_result.load;
privilege_module.vaddr_load(exec_result);
if (exec_result.type == exec_result_type_t::retire) {
this->event_buffer->push_back({.type=event_type_t::load, .pc=exec_result.pc, .val1=addr, .val2=libanemo::zero_truncate(exec_result.retire.value, width)});
}
} else {
privilege_module.vaddr_load(exec_result);
}
} else if (exec_result.type == exec_result_type_t::store) {
if (this->event_buffer!=nullptr) {
auto [addr, width, data, rd, conditional] = exec_result.store;
privilege_module.vaddr_store(exec_result);
if (exec_result.type == exec_result_type_t::retire) {
this->event_buffer->push_back({.type=event_type_t::store, .pc=exec_result.pc, .val1=addr, .val2=libanemo::zero_truncate(data, width)});
}
} else {
privilege_module.vaddr_store(exec_result);
}
} else if (exec_result.type == exec_result_type_t::amo) {
if (this->event_buffer!=nullptr) {
auto [addr, width, data, rd, type] = exec_result.amo;
privilege_module.vaddr_amo(exec_result);
if (exec_result.type == exec_result_type_t::retire) {
this->event_buffer->push_back({.type=event_type_t::load, .pc=exec_result.pc, .val1=addr, .val2=libanemo::zero_truncate(exec_result.retire.value, width)});
this->event_buffer->push_back({.type=event_type_t::store, .pc=exec_result.pc, .val1=addr, .val2=libanemo::zero_truncate(privilege_module.amo_op(type, width, data, exec_result.retire.value), width)});
}
} else {
privilege_module.vaddr_amo(exec_result);
}
} else if (exec_result.type == exec_result_type_t::csr_op) {
privilege_module.csr_op(exec_result);
} else if (exec_result.type == exec_result_type_t::sys_op) {
privilege_module.sys_op(exec_result);
if (this->event_buffer!=nullptr && exec_result.type==exec_result_type_t::retire) {
if (exec_result.sys_op.mret) {
this->event_buffer->push_back({.type=event_type_t::trap_ret, .pc=exec_result.pc, .val1=privilege_module.mepc, .val2=0});
} else if (exec_result.sys_op.sret) {
this->event_buffer->push_back({.type=event_type_t::trap_ret, .pc=exec_result.pc, .val1=privilege_module.sepc, .val2=0});
}
}
}
if (exec_result.type == exec_result_type_t::trap) {
if (exec_result.trap.cause == riscv::mcause<WORD_T>::except_breakpoint) {
is_stopped = true;
return;
}
if (this->event_buffer != nullptr) {
this->event_buffer->push_back({.type=event_type_t::trap, .pc=exec_result.pc, .val1=exec_result.trap.cause, .val2=exec_result.trap.tval});
}
last_trap = exec_result.trap.cause;
privilege_module.handle_exception(exec_result);
} else {
last_trap = std::nullopt;
privilege_module.handle_interrupt(exec_result);
}
assert(exec_result.type == exec_result_type_t::retire);
if (exec_result.retire.rd!=0) {
if (this->event_buffer!=nullptr) {
this->event_buffer->push_back({.type=event_type_t::reg_write, .pc=exec_result.pc, .val1=exec_result.retire.rd, .val2=exec_result.retire.value});
}
user_core.gpr[exec_result.retire.rd] = exec_result.retire.value;
}
exec_result.pc = exec_result.next_pc;
if (this->mmio_bus != nullptr) {
this->mmio_bus->next_cycle();
}
}
template <typename WORD_T>
bool riscv_cpu_system<WORD_T>::stopped(void) const {
return is_stopped;
}
template <typename WORD_T>
std::optional<WORD_T> riscv_cpu_system<WORD_T>::get_trap(void) const {
return last_trap;
}
}
#endif

View File

@ -1,114 +0,0 @@
#ifndef LIBCPU_RV32I_HH
#define LIBCPU_RV32I_HH
// In this file, some architecture dependent but implementation independent constants are defined
#include <cstdint>
#include <libcpu/riscv.hh>
#include <libvio/frontend.hh>
namespace libcpu::rv32i {
using width_t = libvio::width_t;
using priv_level_t = riscv::priv_level_t;
using gpr_addr_t = riscv::gpr_addr_t;
enum csr_addr_t {
CSR_ADDR_MSTATUS = 0x300, // Purr-ivilege mode status
CSR_ADDR_MISA = 0x301,
CSR_ADDR_MIE = 0x304, // Meow-terrupt Enable
CSR_ADDR_MTVEC = 0x305, // Meow-achine Trap Vector
CSR_ADDR_MSTATUSH = 0x310, // Upper bits for RV32 (tail twitch)
CSR_ADDR_MSCRATCH = 0x340, // Scratch reg for catnip calculations
CSR_ADDR_MEPC = 0x341, // Where to resume after nap... err, trap
CSR_ADDR_MCAUSE = 0x342, // "Why did I get interrupted?" register
CSR_ADDR_MTVAL = 0x343, // Bad address or instruction (hairball info)
CSR_ADDR_MIP = 0x344 // Meow-Interrupt Pending (laser pointer detected!)
};
enum csr_bitmask_t: uint32_t {
// MTVEC bits
MTVEC_BIT_VECTORED = 1,
// MIP bits (0x344) - Interrupt Pending
MIP_BIT_SSIP = 1 << 1, // Supervisor Soft-paw Interrupt Pending
MIP_BIT_MSIP = 1 << 3, // Machine Soft-paw Interrupt Pending
MIP_BIT_STIP = 1 << 5, // Supervisor Tuna Timer Interrupt Pending
MIP_BIT_MTIP = 1 << 7, // Machine Tuna Timer Interrupt Pending
MIP_BIT_SEIP = 1 << 9, // Supervisor External Laser Pointer Pending
MIP_BIT_MEIP = 1 << 11, // Machine External Laser Pointer Pending
MIP_BIT_LCOFIP = 1 << 13, // Local Catnip Overflow Interrupt (if implemented)
// MIE bits (0x304) - Interrupt Enable
MIE_BIT_SSIE = 1 << 1, // Enable Supervisor Belly Rub Requests
MIE_BIT_MSIE = 1 << 3, // Enable Machine Belly Rub Requests
MIE_BIT_STIE = 1 << 5, // Enable Supervisor Nap Timer
MIE_BIT_MTIE = 1 << 7, // Enable Machine Nap Timer
MIE_BIT_SEIE = 1 << 9, // Enable Supervisor Door Opening Detection
MIE_BIT_MEIE = 1 << 11, // Enable Machine Door Opening Detection
MIE_BIT_LCOFIE = 1 << 13, // Enable Catnip Overflow Notifications
// MSTATUS bits (0x300) - Status Reg
MSTATUS_BIT_SIE = 1 << 1, // Supervisor Interrupt Enable (for cat staff)
MSTATUS_BIT_MIE = 1 << 3, // Machine Interrupt Enable (for sysadmin cats)
MSTATUS_BIT_SPIE = 1 << 5, // Previous S-mode Interrupt State
MSTATUS_BIT_UBE = 1 << 6, // User-mode Big-Endian (rare for cats)
MSTATUS_BIT_MPIE = 1 << 7, // Previous M-mode Interrupt State
MSTATUS_BIT_MPPL = 1 << 11, // Previous Privilege Level Low
MSTATUS_BIT_MPPH = 1 << 12, // Previous Privilige Level High
MSTATUS_BIT_MPRV = 1 << 17, // Memory Privilege Mode (for cat burglary)
MSTATUS_BIT_SUM = 1 << 18, // Supervisor User Memory access (controlled sharing)
MSTATUS_BIT_MXR = 1 << 19, // Make eXecutable Readable (code = data to cats)
MSTATUS_BIT_TVM = 1 << 20, // Trap Virtual Memory ops (protect nap spaces)
MSTATUS_BIT_TW = 1 << 21, // Timeout Wait (don't let hoomans wait too long)
MSTATUS_BIT_TSR = 1 << 22, // Trap SRET (secure return from cat staff)
MSTATUS_BIT_SD = (uint32_t)1 << 31, // Dirty state flag
// MCAUSE bits
MCAUSE_BIT_INTERRUPT = (uint32_t)1<<31,
};
enum mcause_t: uint32_t {
// =^..^= Interrupts (bit 31 set) =^..^=
INTERRUPT_S_SOFTWARE = (1U << 31) | 1, // Supervisor's yarn ball
INTERRUPT_M_SOFTWARE = (1U << 31) | 3, // Machine's laser pointer
INTERRUPT_S_TIMER = (1U << 31) | 5, // Supurrvisor alarm clock
INTERRUPT_M_TIMER = (1U << 31) | 7, // Meal time interrupt 😼
INTERRUPT_S_EXTERNAL = (1U << 31) | 9, // Doorbell ring detected
INTERRUPT_M_EXTERNAL = (1U << 31) | 11, // Vacuum cleaner alert!
INTERRUPT_M_COUNTER_OVERFLOW = (1U << 31) | 13,// Too many mice counted
// Reserved holes in the interrupt fabric 😾
// 0,2,4,6,8,10,12,14-15 - treat like forbidden catnip
// =^..^= Exceptions (bit 31 clear) =^..^=
EXCEPTION_INSTRUCTION_ADDRESS_MISALIGNED = 0, // Cat stepped on keyboard
EXCEPTION_INSTRUCTION_ACCESS_FAULT = 1, // Forbidden sunbeam area
EXCEPTION_ILLEGAL_INSTRUCTION = 2, // Tried to bark 🐶?!
EXCEPTION_BREAKPOINT = 3, // Paws on keyboard event
EXCEPTION_LOAD_ADDRESS_MISALIGNED = 4, // Bad tuna can alignment
EXCEPTION_LOAD_ACCESS_FAULT = 5, // Empty food bowl error
EXCEPTION_STORE_AMO_ADDRESS_MISALIGNED = 6, // Litter box placement fail
EXCEPTION_STORE_AMO_ACCESS_FAULT = 7, // Closed door exception
EXCEPTION_U_ECALL = 8, // User-mode meow request
EXCEPTION_S_ECALL = 9, // Supervisor pets needed
EXCEPTION_M_ECALL = 11, // Machine wants lap time
EXCEPTION_INSTRUCTION_PAGE_FAULT = 12, // Book fell off shelf
EXCEPTION_LOAD_PAGE_FAULT = 13, // Blanket not found 😿
EXCEPTION_STORE_AMO_PAGE_FAULT = 15, // Toy storage full
EXCEPTION_SOFTWARE_CHECK = 18, // Hairball detected
EXCEPTION_HARDWARE_ERROR = 19, // Scratched furniture
// Reserved territories (no napping allowed!)
// 10,14,16-17,20-23,32-47,≥64 - like off-limit counters
// Custom scratch posts (24-31, 48-63) 😻
};
}
#endif

View File

@ -1,184 +0,0 @@
#ifndef LIBCPU_RV32I_CPU_SYSTEM_HH
#define LIBCPU_RV32I_CPU_SYSTEM_HH
#include <libcpu/abstract_cpu.hh>
#include <libcpu/event.hh>
#include <libcpu/memory.hh>
#include <libcpu/rv32i.hh>
#include <libvio/ringbuffer.hh>
#include <libvio/frontend.hh>
#include <libvio/bus.hh>
#include <array>
#include <cstdint>
#include <cstddef>
#include <vector>
namespace libcpu {
class rv32i_cpu_system: public abstract_cpu<uint32_t> {
public:
static constexpr size_t n_csr = 10;
static constexpr size_t n_interrupt = 16;
using decode_t = struct decode_t {
void (*op)(rv32i_cpu_system* cpu, const decode_t& decode);
word_t instr;
word_t imm;
uint_fast8_t rs1;
uint_fast8_t rs2;
uint_fast8_t rd;
};
using csr_info_t = struct csr_info_t {
word_t init_value; ///< Initial value on boot
word_t wpri_mask; ///< The and mask when writing with csr operation instructions.
const char *name; ///< Name of the CSR
rv32i::csr_addr_t addr; ///< Address of the CSR
};
static const csr_info_t csr_info[n_csr];
private:
// architectural state
std::array<word_t, 32> gpr;
std::array<word_t, n_csr> csr;
word_t pc;
word_t next_pc;
rv32i::priv_level_t priv_level;
word_t instruction;
std::vector<decode_t> decode_cache;
word_t decode_cache_addr_mask;
bool exception_flag = false;
bool ebreak_flag = false;
word_t exception_cause;
word_t exception_mtval;
std::optional<word_t> next_trap = {};
// helper functions for memory operations
void load(const decode_t &decode, libvio::width_t width, bool sign_extend);
void store(const decode_t &decode, libvio::width_t width);
void raise_exception(rv32i::mcause_t mcause, word_t mtval);
word_t handle_trap(void);
// csr operations with no permmission check
// used for emulating a hardware writing a csr
bool csr_check_read_access(rv32i::csr_addr_t addr) const;
bool csr_check_write_access(rv32i::csr_addr_t addr) const;
void csr_write(rv32i::csr_addr_t addr, word_t value);
void csr_write_bits(rv32i::csr_addr_t addr, word_t value, word_t bit_mask);
void csr_set_bits(rv32i::csr_addr_t addr, word_t bits);
void csr_clear_bits(rv32i::csr_addr_t addr, word_t bits);
public:
void reset(word_t init_pc) override;
uint8_t n_gpr(void) const override;
const char* gpr_name(uint8_t addr) const override;
uint8_t gpr_addr(const char* name) const override;
word_t get_gpr(uint8_t gpr_addr) const override;
const word_t *get_gpr(void) const override;
word_t get_pc(void) const override;
rv32i::priv_level_t get_priv_level(void) const;
void next_cycle(void) override;
void next_instruction(void) override;
bool stopped(void) const override;
word_t csr_read(rv32i::csr_addr_t addr) const;
word_t csr_read_bits(rv32i::csr_addr_t addr, word_t bit_mask) const;
static decode_t decode_instruction(word_t instruction);
std::optional<word_t>pmem_peek(word_t addr, libvio::width_t width) const override;
std::optional<word_t> get_trap(void) const override;
protected:
void raise_interrupt(rv32i::mcause_t mcause);
private:
// ========== Instruction Implementations ==========
// Arithmetic & Logical
static void add(rv32i_cpu_system* cpu, const decode_t& decode);
static void sub(rv32i_cpu_system* cpu, const decode_t& decode);
static void sll(rv32i_cpu_system* cpu, const decode_t& decode);
static void slt(rv32i_cpu_system* cpu, const decode_t& decode);
static void sltu(rv32i_cpu_system* cpu, const decode_t& decode);
static void xor_(rv32i_cpu_system* cpu, const decode_t& decode);
static void srl(rv32i_cpu_system* cpu, const decode_t& decode);
static void sra(rv32i_cpu_system* cpu, const decode_t& decode);
static void or_(rv32i_cpu_system* cpu, const decode_t& decode);
static void and_(rv32i_cpu_system* cpu, const decode_t& decode);
// Immediate Operations
static void addi(rv32i_cpu_system* cpu, const decode_t& decode);
static void slti(rv32i_cpu_system* cpu, const decode_t& decode);
static void sltiu(rv32i_cpu_system* cpu, const decode_t& decode);
static void xori(rv32i_cpu_system* cpu, const decode_t& decode);
static void ori(rv32i_cpu_system* cpu, const decode_t& decode);
static void andi(rv32i_cpu_system* cpu, const decode_t& decode);
static void slli(rv32i_cpu_system* cpu, const decode_t& decode);
static void srli(rv32i_cpu_system* cpu, const decode_t& decode);
static void srai(rv32i_cpu_system* cpu, const decode_t& decode);
// Memory Operations
static void lb(rv32i_cpu_system* cpu, const decode_t& decode);
static void lh(rv32i_cpu_system* cpu, const decode_t& decode);
static void lw(rv32i_cpu_system* cpu, const decode_t& decode);
static void lbu(rv32i_cpu_system* cpu, const decode_t& decode);
static void lhu(rv32i_cpu_system* cpu, const decode_t& decode);
static void sb(rv32i_cpu_system* cpu, const decode_t& decode);
static void sh(rv32i_cpu_system* cpu, const decode_t& decode);
static void sw(rv32i_cpu_system* cpu, const decode_t& decode);
// Control Flow
static void jal(rv32i_cpu_system* cpu, const decode_t& decode);
static void jalr(rv32i_cpu_system* cpu, const decode_t& decode);
static void beq(rv32i_cpu_system* cpu, const decode_t& decode);
static void bne(rv32i_cpu_system* cpu, const decode_t& decode);
static void blt(rv32i_cpu_system* cpu, const decode_t& decode);
static void bge(rv32i_cpu_system* cpu, const decode_t& decode);
static void bltu(rv32i_cpu_system* cpu, const decode_t& decode);
static void bgeu(rv32i_cpu_system* cpu, const decode_t& decode);
// Upper Immediate
static void lui(rv32i_cpu_system* cpu, const decode_t& decode);
static void auipc(rv32i_cpu_system* cpu, const decode_t& decode);
// Multiply/Divide
static void mul(rv32i_cpu_system* cpu, const decode_t& decode);
static void mulh(rv32i_cpu_system* cpu, const decode_t& decode);
static void mulhsu(rv32i_cpu_system* cpu, const decode_t& decode);
static void mulhu(rv32i_cpu_system* cpu, const decode_t& decode);
static void div(rv32i_cpu_system* cpu, const decode_t& decode);
static void divu(rv32i_cpu_system* cpu, const decode_t& decode);
static void rem(rv32i_cpu_system* cpu, const decode_t& decode);
static void remu(rv32i_cpu_system* cpu, const decode_t& decode);
// System
static void ecall(rv32i_cpu_system* cpu, const decode_t& decode);
static void ebreak(rv32i_cpu_system* cpu, const decode_t& decode);
static void mret(rv32i_cpu_system* cpu, const decode_t& decode);
// csr functions with permission check
// functions emulating the csr accesing instructions
static void csrrw(rv32i_cpu_system* cpu, const decode_t& decode);
static void csrrs(rv32i_cpu_system* cpu, const decode_t& decode);
static void csrrc(rv32i_cpu_system* cpu, const decode_t& decode);
static void csrrwi(rv32i_cpu_system* cpu, const decode_t& decode);
static void csrrsi(rv32i_cpu_system* cpu, const decode_t& decode);
static void csrrci(rv32i_cpu_system* cpu, const decode_t& decode);
};
}
#endif

View File

@ -1,106 +0,0 @@
#ifndef LIBCPU_RV64I_HH
#define LIBCPU_RV64I_HH
#include <cstdint>
#include <libcpu/riscv.hh>
#include <libvio/frontend.hh>
namespace libcpu::rv64i {
using width_t = libvio::width_t;
using priv_level_t = riscv::priv_level_t;
using gpr_addr_t = riscv::gpr_addr_t;
enum csr_addr_t {
CSR_ADDR_MSTATUS = 0x300, // Purr-ivilege mode status
CSR_ADDR_MISA = 0x301,
CSR_ADDR_MIE = 0x304, // Meow-terrupt Enable
CSR_ADDR_MTVEC = 0x305, // Meow-achine Trap Vector
CSR_ADDR_MSCRATCH = 0x340, // Scratch reg for catnip calculations
CSR_ADDR_MEPC = 0x341, // Where to resume after nap... err, trap
CSR_ADDR_MCAUSE = 0x342, // "Why did I get interrupted?" register
CSR_ADDR_MTVAL = 0x343, // Bad address or instruction (hairball info)
CSR_ADDR_MIP = 0x344 // Meow-Interrupt Pending (laser pointer detected!)
};
enum csr_bitmask_t: uint64_t { // Changed to uint64_t for 64-bit registers
// MTVEC bits
MTVEC_BIT_VECTORED = 1,
// MIP bits (0x344) - Interrupt Pending
MIP_BIT_SSIP = 1ULL << 1, // Supervisor Soft-paw Interrupt Pending
MIP_BIT_MSIP = 1ULL << 3, // Machine Soft-paw Interrupt Pending
MIP_BIT_STIP = 1ULL << 5, // Supervisor Tuna Timer Interrupt Pending
MIP_BIT_MTIP = 1ULL << 7, // Machine Tuna Timer Interrupt Pending
MIP_BIT_SEIP = 1ULL << 9, // Supervisor External Laser Pointer Pending
MIP_BIT_MEIP = 1ULL << 11, // Machine External Laser Pointer Pending
MIP_BIT_LCOFIP = 1ULL << 13, // Local Catnip Overflow Interrupt (if implemented)
// MIE bits (0x304) - Interrupt Enable
MIE_BIT_SSIE = 1ULL << 1, // Enable Supervisor Belly Rub Requests
MIE_BIT_MSIE = 1ULL << 3, // Enable Machine Belly Rub Requests
MIE_BIT_STIE = 1ULL << 5, // Enable Supervisor Nap Timer
MIE_BIT_MTIE = 1ULL << 7, // Enable Machine Nap Timer
MIE_BIT_SEIE = 1ULL << 9, // Enable Supervisor Door Opening Detection
MIE_BIT_MEIE = 1ULL << 11, // Enable Machine Door Opening Detection
MIE_BIT_LCOFIE = 1ULL << 13, // Enable Catnip Overflow Notifications
// MSTATUS bits (0x300) - Status Reg
MSTATUS_BIT_SIE = 1ULL << 1, // Supervisor Interrupt Enable
MSTATUS_BIT_MIE = 1ULL << 3, // Machine Interrupt Enable
MSTATUS_BIT_SPIE = 1ULL << 5, // Previous S-mode Interrupt State
MSTATUS_BIT_UBE = 1ULL << 6, // User-mode Big-Endian
MSTATUS_BIT_MPIE = 1ULL << 7, // Previous M-mode Interrupt State
MSTATUS_BIT_MPP_SHIFT = 11, // MPP field shift position
MSTATUS_BIT_MPP_MASK = 3ULL << MSTATUS_BIT_MPP_SHIFT,
MSTATUS_BIT_MPRV = 1ULL << 17, // Memory Privilege Mode
MSTATUS_BIT_SUM = 1ULL << 18, // Supervisor User Memory access
MSTATUS_BIT_MXR = 1ULL << 19, // Make eXecutable Readable
MSTATUS_BIT_TVM = 1ULL << 20, // Trap Virtual Memory ops
MSTATUS_BIT_TW = 1ULL << 21, // Timeout Wait
MSTATUS_BIT_TSR = 1ULL << 22, // Trap SRET
MSTATUS_BIT_UXL = 1ULL << 32, // User XLEN (fixed to 64 in U-mode)
MSTATUS_BIT_SXL = 1ULL << 34, // Supervisor XLEN (fixed to 64 in S-mode)
MSTATUS_BIT_SD = 1ULL << 63, // Dirty state flag (moved to bit 63)
// MCAUSE bits
MCAUSE_BIT_INTERRUPT = 1ULL << 63, // Interrupt flag at bit 63
MCAUSE_CODE_MASK = (1ULL << 63) - 1, // Mask for exception code
};
enum mcause_t: uint64_t { // Changed to uint64_t for 64-bit cause register
// =^..^= Interrupts (bit 63 set) =^..^=
INTERRUPT_S_SOFTWARE = (1ULL << 63) | 1, // Supervisor's yarn ball
INTERRUPT_M_SOFTWARE = (1ULL << 63) | 3, // Machine's laser pointer
INTERRUPT_S_TIMER = (1ULL << 63) | 5, // Supurrvisor alarm clock
INTERRUPT_M_TIMER = (1ULL << 63) | 7, // Meal time interrupt 😼
INTERRUPT_S_EXTERNAL = (1ULL << 63) | 9, // Doorbell ring detected
INTERRUPT_M_EXTERNAL = (1ULL << 63) | 11, // Vacuum cleaner alert!
INTERRUPT_M_COUNTER_OVERFLOW = (1ULL << 63) | 13,// Too many mice counted
// =^..^= Exceptions (bit 63 clear) =^..^=
EXCEPTION_INSTRUCTION_ADDRESS_MISALIGNED = 0, // Cat stepped on keyboard
EXCEPTION_INSTRUCTION_ACCESS_FAULT = 1, // Forbidden sunbeam area
EXCEPTION_ILLEGAL_INSTRUCTION = 2, // Tried to bark 🐶?!
EXCEPTION_BREAKPOINT = 3, // Paws on keyboard event
EXCEPTION_LOAD_ADDRESS_MISALIGNED = 4, // Bad tuna can alignment
EXCEPTION_LOAD_ACCESS_FAULT = 5, // Empty food bowl error
EXCEPTION_STORE_AMO_ADDRESS_MISALIGNED = 6, // Litter box placement fail
EXCEPTION_STORE_AMO_ACCESS_FAULT = 7, // Closed door exception
EXCEPTION_U_ECALL = 8, // User-mode meow request
EXCEPTION_S_ECALL = 9, // Supervisor pets needed
EXCEPTION_M_ECALL = 11, // Machine wants lap time
EXCEPTION_INSTRUCTION_PAGE_FAULT = 12, // Book fell off shelf
EXCEPTION_LOAD_PAGE_FAULT = 13, // Blanket not found 😿
EXCEPTION_STORE_AMO_PAGE_FAULT = 15, // Toy storage full
EXCEPTION_SOFTWARE_CHECK = 18, // Hairball detected
EXCEPTION_HARDWARE_ERROR = 19, // Scratched furniture
};
}
#endif

View File

@ -8,7 +8,7 @@
#include <vector>
#include <string>
#include <optional>
#include <libvio/width.hh>
#include <libanemo/width.hh>
namespace libsdb {
@ -179,20 +179,20 @@ std::optional<WORD_T> evaluate_expression(const std::vector<token_t> &postfix_ex
} else if (strcmp(token.op.str, "-") == 0) {
stack.push_back(0 - operand);
} else if (strcmp(token.op.str, "byte") == 0) {
stack.push_back(libvio::zero_truncate(operand, libvio::width_t::byte));
stack.push_back(libanemo::zero_truncate(operand, libanemo::width_t::byte));
} else if (strcmp(token.op.str, "half") == 0) {
stack.push_back(libvio::zero_truncate(operand, libvio::width_t::half));
stack.push_back(libanemo::zero_truncate(operand, libanemo::width_t::half));
} else if (strcmp(token.op.str, "word") == 0) {
stack.push_back(libvio::zero_truncate(operand, libvio::width_t::word));
stack.push_back(libanemo::zero_truncate(operand, libanemo::width_t::word));
} else if (strcmp(token.op.str, "sbyte") == 0) {
stack.push_back(libvio::sign_extend(operand, libvio::width_t::byte));
stack.push_back(libanemo::sign_extend(operand, libanemo::width_t::byte));
} else if (strcmp(token.op.str, "shalf") == 0) {
stack.push_back(libvio::sign_extend(operand, libvio::width_t::half));
stack.push_back(libanemo::sign_extend(operand, libanemo::width_t::half));
} else if (strcmp(token.op.str, "sword") == 0) {
stack.push_back(libvio::sign_extend(operand, libvio::width_t::word));
stack.push_back(libanemo::sign_extend(operand, libanemo::width_t::word));
} else if (strcmp(token.op.str, "pmem") == 0) {
if (cpu != nullptr) {
std::optional<WORD_T> val = cpu->pmem_peek(operand, static_cast<libvio::width_t>(sizeof(WORD_T)));
std::optional<WORD_T> val = cpu->pmem_peek(operand, static_cast<libanemo::width_t>(sizeof(WORD_T)));
if (val.has_value()) {
stack.push_back(val.value());
} else {
@ -203,7 +203,7 @@ std::optional<WORD_T> evaluate_expression(const std::vector<token_t> &postfix_ex
}
} else if (strcmp(token.op.str, "vmem") == 0) {
if (cpu != nullptr) {
std::optional<WORD_T> val = cpu->vmem_peek(operand, static_cast<libvio::width_t>(sizeof(WORD_T)));
std::optional<WORD_T> val = cpu->vmem_peek(operand, static_cast<libanemo::width_t>(sizeof(WORD_T)));
if (val.has_value()) {
stack.push_back(val.value());
} else {

View File

@ -0,0 +1,200 @@
#ifndef LIBSDB_GDB_SERVER_HH
#define LIBSDB_GDB_SERVER_HH
#include <cstddef>
#include <cstdint>
#include <cstring>
#include <string>
#include <unordered_set>
#include <vector>
#include <libanemo/log.hh>
#include <libanemo/width.hh>
#include <libcpu/abstract_cpu.hh>
namespace libsdb {
class gdb_server_base {
public:
enum class action_t { continue_, step, kill };
virtual ~gdb_server_base();
protected:
gdb_server_base();
bool create_server(int port);
bool accept_client();
void close_client();
void close_server();
action_t process();
virtual std::vector<uint8_t> read_all_regs() = 0;
virtual std::vector<uint8_t> read_reg(size_t index) = 0;
virtual std::vector<uint8_t> read_mem(size_t addr, size_t len) = 0;
virtual void set_breakpoint(size_t addr) = 0;
virtual void clear_breakpoint(size_t addr) = 0;
virtual void on_kill() = 0;
bool signal_on_entry;
void send_packet(const char* data);
void send_packet(const std::string& data) { send_packet(data.c_str()); }
std::string recv_packet();
static void compute_checksum(const char* data, size_t len, char out[3]);
static void hex_encode_byte(uint8_t val, char out[2]);
void send_hex_data(const std::vector<uint8_t>& data);
void send_signal();
void process_breakpoint(const std::string& pkt, bool set);
int server_fd;
int client_fd;
};
template <typename WORD_T>
class gdb_server : public gdb_server_base {
public:
explicit gdb_server(libcpu::abstract_cpu<WORD_T>* cpu)
: cpu(cpu), reg_buffer((cpu->n_gpr() + 1) * sizeof(WORD_T)) {}
void listen(int port) {
#ifdef __unix__
if (!create_server(port)) {
libanemo::log_error("gdb_server", "Failed to create server socket on port %d\n", port);
return;
}
libanemo::log_info("gdb_server", "Listening on port %d\n", port);
if (!accept_client()) {
libanemo::log_error("gdb_server", "Failed to accept client connection\n");
close_server();
return;
}
bool running = true;
while (running) {
copy_cpu_to_regs();
signal_on_entry = true;
action_t action = process();
if (action == action_t::kill) {
running = false;
} else if (action == action_t::continue_) {
WORD_T pc = cpu->get_pc();
libanemo::log_info("gdb_server", "Continuing execution from 0x%llx\n", static_cast<unsigned long long>(pc));
bool skipped = breakpoints.erase(pc) > 0;
cpu->next_instruction();
if (skipped) breakpoints.insert(pc);
while (!cpu->stopped() && !check_breakpoint()) {
cpu->next_instruction();
}
if (cpu->stopped()) {
libanemo::log_info("gdb_server", "CPU stopped (ebreak/trap) at 0x%llx\n", static_cast<unsigned long long>(cpu->get_pc()));
}
} else if (action == action_t::step) {
WORD_T pc = cpu->get_pc();
libanemo::log_info("gdb_server", "Single stepping from 0x%llx\n", static_cast<unsigned long long>(pc));
bool skipped = breakpoints.erase(pc) > 0;
cpu->next_instruction();
if (skipped) breakpoints.insert(pc);
if (cpu->stopped()) {
libanemo::log_info("gdb_server", "CPU stopped (ebreak/trap) at 0x%llx\n", static_cast<unsigned long long>(cpu->get_pc()));
} else if (check_breakpoint()) {
// already logged
} else {
libanemo::log_info("gdb_server", "Stepped to 0x%llx\n", static_cast<unsigned long long>(cpu->get_pc()));
}
}
}
close_client();
libanemo::log_info("gdb_server", "GDB disconnected\n");
close_server();
#else
libanemo::log_critical("gdb_server", "gdbserver is not supported on this platform\n");
#endif
}
std::vector<uint8_t> read_all_regs() override {
copy_cpu_to_regs();
std::vector<uint8_t> result(reg_buffer.size());
std::memcpy(result.data(), reg_buffer.data(), reg_buffer.size());
return result;
}
std::vector<uint8_t> read_reg(size_t index) override {
size_t reg_width = sizeof(WORD_T);
size_t num_gpr = cpu->n_gpr();
if (index >= num_gpr + 1) {
return {};
}
copy_cpu_to_regs();
std::vector<uint8_t> result(reg_width);
std::memcpy(result.data(), &reg_buffer[index * reg_width], reg_width);
return result;
}
std::vector<uint8_t> read_mem(size_t addr, size_t len) override {
std::vector<uint8_t> result;
result.reserve(len);
for (size_t i = 0; i < len; ++i) {
auto val = cpu->vmem_peek(static_cast<WORD_T>(addr + i), libanemo::width_t::byte);
if (val.has_value()) {
result.push_back(static_cast<uint8_t>(val.value()));
} else {
result.push_back(0);
}
}
return result;
}
void set_breakpoint(size_t addr) override {
WORD_T waddr = static_cast<WORD_T>(addr);
if (breakpoints.insert(waddr).second) {
libanemo::log_info("gdb_server", "Breakpoint set at 0x%llx\n", static_cast<unsigned long long>(waddr));
}
}
void clear_breakpoint(size_t addr) override {
WORD_T waddr = static_cast<WORD_T>(addr);
if (breakpoints.erase(waddr) > 0) {
libanemo::log_info("gdb_server", "Breakpoint cleared at 0x%llx\n", static_cast<unsigned long long>(waddr));
}
}
void on_kill() override {
libanemo::log_info("gdb_server", "Kill session requested by GDB\n");
}
private:
libcpu::abstract_cpu<WORD_T>* cpu;
std::vector<char> reg_buffer;
std::unordered_set<WORD_T> breakpoints;
void copy_cpu_to_regs() {
const WORD_T* gpr = cpu->get_gpr();
size_t reg_width = sizeof(WORD_T);
size_t n = cpu->n_gpr();
for (size_t i = 0; i < n; ++i) {
std::memcpy(&reg_buffer[i * reg_width], &gpr[i], reg_width);
}
WORD_T pc = cpu->get_pc();
std::memcpy(&reg_buffer[n * reg_width], &pc, reg_width);
}
bool check_breakpoint() {
WORD_T pc = cpu->get_pc();
if (breakpoints.count(pc) > 0) {
libanemo::log_info("gdb_server", "Breakpoint hit at 0x%llx\n", static_cast<unsigned long long>(pc));
return true;
}
return false;
}
};
} // namespace libsdb
#endif

View File

@ -1,6 +1,7 @@
#ifndef LIBSDB_SDB_HH
#define LIBSDB_SDB_HH
#include <climits>
#include <cstddef>
#include <cstdint>
#include <cstring>
@ -9,6 +10,7 @@
#include <iostream>
#include <libcpu/event.hh>
#include <ostream>
#include <stddef.h>
#include <vector>
#include <string>
#include <libsdb/commandline.hh>
@ -56,6 +58,7 @@ public:
static void cmd_break(std::vector<std::string> args, sdb<WORD_T>* sdb_inst, std::ostream& os);
static void cmd_eval(std::vector<std::string> args, sdb<WORD_T>* sdb_inst, std::ostream& os);
static void cmd_trace(std::vector<std::string> args, sdb<WORD_T>* sdb_inst, std::ostream& os);
static void cmd_reset(std::vector<std::string> args, sdb<WORD_T>* sdb_inst, std::ostream& os);
/**
* @var commands
@ -64,22 +67,35 @@ public:
*/
static inline command_def_t commands[] = {
{cmd_help, (const char* const[]){"help", "h", nullptr},
"help: Show help for commands\nUsage:\nhelp [command]"},
"help: Show help for commands\n"
"Usage:\n"
" help [command]"},
{cmd_quit, (const char* const[]){"quit", "q", nullptr},
"quit: Exit the debugger\nUsage:\nquit"},
"quit: Exit the debugger\n"
"Usage:\n"
" quit"},
{cmd_continue, (const char* const[]){"continue", "c", nullptr},
"continue: Continue execution until breakpoint, watchpoint, or program end\nUsage:\ncontinue"},
"continue: Continue execution until breakpoint, watchpoint, or program end\n"
"Usage:\n"
" continue"},
{cmd_step, (const char* const[]){"step", "s", "si", nullptr},
"step: Execute one or more instructions\nUsage:\nstep [n=1]"},
"step: Execute one or more instructions\n"
"Usage:\n"
" step [n=1]"},
{cmd_status, (const char* const[]){"status", "st", "regs", "r", nullptr},
"status: Show current PC and general purpose registers\nUsage:\nstatus"},
"status: Show current PC and general purpose registers\n"
"Usage:\n"
" status"},
{cmd_examine, (const char* const[]){"examine", "x", nullptr},
"examine: Dump memory\nUsage:\nexamine <base> <length> <word_sz>\n"
"examine: Dump memory\n"
"Usage:\n"
" examine <base> <length> <word_sz>\n"
" <base> - Starting address (expression)\n"
" <length> - Number of words to display (expression)\n"
" <word_sz> - Word size in bytes (1, 2, 4, or 8)"},
{cmd_watch, (const char* const[]){"watch", "w", nullptr},
"watch: Manage watchpoints\nUsage:\n"
"watch: Manage watchpoints\n"
"Usage:\n"
" watch <expr> - Set a watchpoint on an expression\n"
" watch ls - List all watchpoints\n"
" watch rm <n> - Remove watchpoint by index\n"
@ -97,10 +113,20 @@ public:
" <n> - Index of breakpoint to remove\n"
" on|off - Enable or disable trap breakpoints"},
{cmd_eval, (const char* const[]){"evaluate", "eval", "e", "expr", nullptr},
"eval: Evaluate an expression\nUsage:\nevaluate <expression>"
"eval: Evaluate an expression\n"
"Usage:\n"
" evaluate <expression>"
},
{cmd_trace, (const char* const[]){"trace", "t", "log", "events", nullptr},
"trace: show event logs\nUsage:\ntrace [instr] [mem] [func] [trap]"
"trace: show event logs\nUsage:\n"
" trace [instr] [mem] [func] [trap]"
},
{cmd_reset, (const char* const[]){"reset", "rst", nullptr},
"reset: reset the cpu\n"
"Usage:\n"
" reset <init_pc>\n"
"Note:\n"
" This will not reset the content of the memory."
}
};
@ -138,6 +164,12 @@ public:
*/
virtual void execute_command(command_t cmd);
/**
* @brief Get the command prompt of SDB, may change according to the state.
* @return A constant string containing the command prompt.
*/
virtual const char *get_prompt(void) const;
protected:
bool is_stopped = false; /**< Internal stopped state flag */
@ -181,15 +213,15 @@ bool sdb<WORD_T>::stopped(void) const {
template <typename WORD_T>
void sdb<WORD_T>::show_command_help(const command_def_t &def, std::ostream &os) {
os << def.names[0] << std::endl;
os << def.help << std::endl;
if (def.names[1] != nullptr) {
os << "Alias:";
os << "Alias:" << std::endl;
os << " ";
for (size_t i=1; def.names[i]!=nullptr; ++i) {
os << ' ' << def.names[i];
}
os << std::endl;
}
os << def.help << std::endl;
}
template <typename WORD_T>
@ -299,11 +331,12 @@ void sdb<WORD_T>::cmd_status(std::vector<std::string> args, sdb<WORD_T> *sdb_ins
return;
}
os << " pc=0x" << std::hex << sdb_inst->cpu->get_pc() << "\n";
constexpr size_t word_size = sizeof(WORD_T) * CHAR_BIT;
os << " pc=0x" << std::setw(word_size/4) << std::setfill('0') << std::hex << sdb_inst->cpu->get_pc() << "\n";
for (uint8_t i=0; i<sdb_inst->cpu->n_gpr(); ++i) {
os << std::right << std::setw(4) << std::setfill(' ') << sdb_inst->cpu->gpr_name(i) << "=0x"
<< std::hex << std::setw(sizeof(WORD_T)*2) << std::setfill('0') << sdb_inst->cpu->get_gpr(i) << ' ';
if (i%8 == 7) {
<< std::hex << std::setw(word_size/4) << std::setfill('0') << sdb_inst->cpu->get_gpr(i) << ' ';
if ((word_size<=32)&&(i%8==7) || (word_size>32)&&(i%4==3)) {
os << std::endl;
}
}
@ -337,7 +370,7 @@ void sdb<WORD_T>::cmd_examine(std::vector<std::string> args, sdb<WORD_T> *sdb_in
if (addr%16 == 0) {
os << "0x" << std::hex << addr << ":";
}
auto val = sdb_inst->cpu->vmem_peek(addr, static_cast<libvio::width_t>(word_sz));
auto val = sdb_inst->cpu->vmem_peek(addr, static_cast<libanemo::width_t>(word_sz));
if (val.has_value()) {
os << " " << std::setfill('0') << std::setw(word_sz * 2)
<< std::hex << val.value();
@ -421,6 +454,7 @@ template <typename WORD_T>
void sdb<WORD_T>::cmd_break(std::vector<std::string> args, sdb<WORD_T> *sdb_inst, std::ostream &os) {
if (args.empty()) {
show_command_help("break", os);
return;
}
if (args[0] == "ls") {
@ -548,6 +582,7 @@ void sdb<WORD_T>::cmd_trace(std::vector<std::string> args, sdb<WORD_T> *sdb_inst
if (args.size() == 0) {
instr = mem = func = trap = true;
} else {
instr = mem = func = trap = false;
for (auto s: args) {
if (s == "instr") {
instr = true;
@ -599,6 +634,26 @@ void sdb<WORD_T>::cmd_trace(std::vector<std::string> args, sdb<WORD_T> *sdb_inst
}
}
template <typename WORD_T>
void sdb<WORD_T>::cmd_reset(std::vector<std::string> args, sdb<WORD_T> *sdb_inst, std::ostream &os) {
if (args.size() == 0) {
show_command_help("reset", os);
} else {
std::string expr_str;
for (const auto& arg: args) {
expr_str += arg + " ";
}
auto init_pc_opt = evaluate_expression(expr_str, sdb_inst->cpu);
if (init_pc_opt.has_value()) {
WORD_T init_pc = init_pc_opt.value();
sdb_inst->cpu->reset(init_pc);
} else {
os << "libsdb: Invalid expression in arguments." << std::endl;
return;
}
}
}
template <typename WORD_T>
bool sdb<WORD_T>::check_watchpoints(std::ostream &os) {
for (auto &wp : watchpoints) {
@ -642,6 +697,11 @@ void sdb<WORD_T>::execute_steps(size_t n, std::ostream &os) {
}
}
template <typename WORD_T>
const char* sdb<WORD_T>::get_prompt(void) const {
return "sdb> ";
}
}
#endif

View File

@ -0,0 +1,65 @@
#ifndef LIBSDB_SDB_DIFFTEST_HH
#define LIBSDB_SDB_DIFFTEST_HH
#include <libsdb/sdb.hh>
#include <libcpu/difftest.hh>
namespace libsdb {
/**
* @class sdb_difftest
* @brief Extension of sdb with differential testing capabilities
* @tparam WORD_T The word type of the target CPU (e.g., uint32_t)
*/
template <typename WORD_T>
class sdb_difftest: public sdb<WORD_T> {
public:
libcpu::abstract_difftest<WORD_T>* difftest = nullptr; /**< Differential testing interface */
using sdb<WORD_T>::execute_command;
/**
* @brief Execute a pre-parsed command (overrides base class)
* @param cmd Command token to execute
*/
virtual void execute_command(command_t cmd) override;
virtual const char *get_prompt(void) const override;
};
template <typename WORD_T>
void sdb_difftest<WORD_T>::execute_command(command_t cmd) {
if (this->difftest == nullptr) {
std::cerr << "libsdb: `sdb_difftest.difftest` and `sdb_difftest.cpu` cannot be nullptr." << std::endl;
} else if (cmd.sdb_command == "dut") {
this->cpu = this->difftest->dut;
} else if (cmd.sdb_command == "ref") {
this->cpu = this->difftest->ref;
} else if (cmd.sdb_command == "difftest") {
this->cpu = this->difftest;
} else {
sdb<WORD_T>::execute_command(cmd);
}
}
template <typename WORD_T>
const char* sdb_difftest<WORD_T>::get_prompt(void) const {
if (this->difftest==nullptr || this->cpu==nullptr) {
std::cerr << "libsdb: `sdb_difftest.difftest` and `sdb_difftest.cpu` cannot be nullptr." << std::endl;
return "sdb|error> ";
} else if (this->cpu == this->difftest) {
return "sdb|difftest> ";
} else if (this->cpu == this->difftest->dut) {
return "sdb|dut> ";
} else if (this->cpu == this->difftest->ref) {
return "sdb|ref> ";
} else {
std::cerr << "libsdb: invalid value for`sdb_difftest.cpu`." << std::endl;
return "sdb|error> ";
}
}
} // namespace libsdb
#endif // LIBSDB_SDB_DIFFTEST_HH

View File

@ -3,7 +3,7 @@
#include <optional>
#include <cstdint>
#include <libvio/width.hh>
#include <libanemo/width.hh>
namespace libvio {
@ -19,7 +19,7 @@ class io_agent{
* @return std::optional<uint64_t> Read data if successful,
* std::nullopt otherwise
*/
virtual std::optional<uint64_t> read(uint64_t addr, width_t width) = 0;
virtual std::optional<uint64_t> read(uint64_t addr, libanemo::width_t width) = 0;
/**
* @brief Perform a write operation on the bus
@ -28,7 +28,7 @@ class io_agent{
* @param data Data to write (least significant bits used according to width)
* @return `true` if write succeeded, `false` otherwise
*/
virtual bool write(uint64_t addr, width_t width, uint64_t data) = 0;
virtual bool write(uint64_t addr, libanemo::width_t width, uint64_t data) = 0;
/**
* @brief Tell the agent that the simulated processor that it's attached to has steped a cycle.

View File

@ -3,67 +3,44 @@
#include <cstdint>
namespace libvio {
namespace libvio {
/**
* @brief Abstract base class for I/O backends
*
* This class does the actual IO operations. It also acts as a producer of input data.
* Each member function requires an argument `req`, whose meaning is defined by subclasses.
* The behavior is undefined if `req` is invalid.
* @see namespace `libvio::reqval` for `req` values of each subclass
*/
class io_backend {
public:
/**
* @brief Blocking read request
*
* Retrieves input data. Blocks until data becomes available.
* This function is used by the frontends when the processor explicitly reads input via MMIO.
* The blocking behavior makes sure that simple programs assuming the input data is always available will work.
*
* @param req MMIO operation description
* @return uint64_t The input data
*/
virtual uint64_t request(uint64_t req) = 0;
uint64_t cycle_count = 0;
/**
* @brief Blocking input availability check
*
* Checks if input data is available. This may block if data is not immediately available,
* This interface is used by the frontend when the processor expilcitly checks whether input is available via MMIO.
* Some backends are synchronous, they must block to wait for data.
* If they do not block and return with "not available", the processor might think the device is busy and will never do an input.
*
* @param req MMIO operation description
* @return whether the requested input data is available
*/
virtual bool poll(uint64_t req) = 0;
virtual uint64_t reg_read(uint64_t reg_num) = 0;
/**
* @brief Non-blocking input availability check
*
* Check whether input data is available. This never blocks.
* This interface is used by the frontend when the frontend itself needs to know whether the input is available.
*
* @param req MMIO operation description
* @return whether requested input data is currently available
*/
virtual bool check(uint64_t req) = 0;
virtual void reg_write(uint64_t reg_num, uint64_t data) = 0;
/**
* @brief Non-blocking write
*
* Sends output data to the backend. This never blocks.
*
* @param req MMIO operation description
* @param data Output data
*/
virtual void put(uint64_t req, uint64_t data) = 0;
virtual bool iflow_valid(uint64_t iflow_num) const = 0;
virtual uint64_t iflow_read(uint64_t iflow_num) = 0;
virtual bool oflow_ready(uint64_t oflow_num) const = 0;
virtual void oflow_write(uint64_t oflow_num, uint64_t data) = 0;
virtual ~io_backend() = default;
};
namespace regs {
// mtime backends
static constexpr uint64_t mtime = 0;
static constexpr uint64_t mtimecmp = 1;
}
namespace iflows {
// ioflow backends
static constexpr uint64_t rx = 0;
}
#endif
namespace oflows {
// ioflow backends
static constexpr uint64_t tx = 0;
}
}
#endif

View File

@ -0,0 +1,4 @@
#ifndef LIBVIO_BACKEND_IOFLOW_FILES_HH
#define LIBVIO_BACKEND_IOFLOW_FILES_HH
#endif

View File

@ -0,0 +1,26 @@
#ifndef LIBVIO_BACKEND_IOFLOW_IOSTREAM_HH
#define LIBVIO_BACKEND_IOFLOW_IOSTREAM_HH
#include <libvio/backend.hh>
#include <cstdint>
namespace libvio {
/**
* @brief Console I/O backend
*
* This backend implements console input/output using standard C++ streams.
*/
class ioflow_backend_iostream: public io_backend {
public:
uint64_t reg_read(uint64_t reg_num) override;
void reg_write(uint64_t reg_num, uint64_t data) override;
bool iflow_valid(uint64_t iflow_num) const override;
uint64_t iflow_read(uint64_t iflow_num) override;
bool oflow_ready(uint64_t oflow_num) const override;
void oflow_write(uint64_t oflow_num, uint64_t data) override;
};
}
#endif

View File

@ -0,0 +1,40 @@
#ifndef LIBVIO_BACKEND_IOFLOW_PTY_HH
#define LIBVIO_BACKEND_IOFLOW_PTY_HH
#ifndef __unix__
#error "ioflow_pty is only available on UNIX platforms"
#endif
#include <libvio/backend.hh>
#include <cstdint>
#include <string>
namespace libvio {
/**
* @brief PTY-based virtual serial port backend
*
* This backend implements input/output over a pseudo-terminal (PTY),
* similar to QEMU's virtual serial port. On startup, it prints the path
* to the slave PTY device so an external terminal can be attached.
*/
class ioflow_backend_pty : public io_backend {
public:
ioflow_backend_pty();
~ioflow_backend_pty() override;
uint64_t reg_read(uint64_t reg_num) override;
void reg_write(uint64_t reg_num, uint64_t data) override;
bool iflow_valid(uint64_t iflow_num) const override;
uint64_t iflow_read(uint64_t iflow_num) override;
bool oflow_ready(uint64_t oflow_num) const override;
void oflow_write(uint64_t oflow_num, uint64_t data) override;
private:
int fd_;
std::string pty_path_;
};
}
#endif

View File

@ -0,0 +1,27 @@
#ifndef LIBVIO_BACKEND_MTIME_CYCLES_HH
#define LIBVIO_BACKEND_MTIME_CYCLES_HH
#include <cstdint>
#include <libvio/backend.hh>
namespace libvio {
/**
* @brief mtime backend based on current cycle count
*/
class mtime_backend_cycles: public io_backend {
public:
uint64_t reg_read(uint64_t reg_num) override;
void reg_write(uint64_t reg_num, uint64_t data) override;
bool iflow_valid(uint64_t iflow_num) const override;
uint64_t iflow_read(uint64_t iflow_num) override;
bool oflow_ready(uint64_t oflow_num) const override;
void oflow_write(uint64_t oflow_num, uint64_t data) override;
private:
uint64_t mtime_offset = 0;
uint64_t mtimecmp = 0;
};
}
#endif

View File

@ -12,8 +12,8 @@
#include <libvio/agent.hh>
#include <libvio/frontend.hh>
#include <libvio/backend.hh>
#include <libvio/ringbuffer.hh>
#include <libvio/width.hh>
#include <libanemo/ringbuffer.hh>
#include <libanemo/width.hh>
#include <optional>
#include <tuple>
#include <vector>
@ -25,8 +25,8 @@ class io_dispatcher;
class mmio_agent: public io_agent {
public:
std::optional<uint64_t> read(uint64_t addr, width_t width) override;
bool write(uint64_t addr, width_t width, uint64_t data) override;
std::optional<uint64_t> read(uint64_t addr, libanemo::width_t width) override;
bool write(uint64_t addr, libanemo::width_t width, uint64_t data) override;
void next_cycle(void) override;
friend class io_dispatcher;
private:
@ -81,9 +81,9 @@ class io_dispatcher {
* @brief Construct a bus with attached devices
* @param device_list Initializer list of device configurations specified as tuples:
* (frontend_ptr, backend_ptr, base_address, address_span)
* @param buffer_size Size of internal request buffer (default: 32)
* @param buffer_size_log The base-2 logarithm of request buffer size
*/
io_dispatcher(std::initializer_list<std::tuple<io_frontend*, io_backend*, uint64_t, uint64_t>> device_list, size_t buffer_size=32);
io_dispatcher(std::initializer_list<std::tuple<io_frontend*, io_backend*, uint64_t, uint64_t>> device_list, size_t buffer_size_log2=5);
/**
* @brief Issue a read request to the I/O bus
@ -97,7 +97,7 @@ class io_dispatcher {
* @param req_no Requesst number, must start with 0 and increase 1 by a request
* @return std::optional<uint64_t> The read data if successful, empty if failed.
*/
std::optional<uint64_t> request_read(uint64_t addr, width_t width, size_t req_no);
std::optional<uint64_t> request_read(uint64_t addr, libanemo::width_t width, size_t req_no);
/**
* @brief Issue a write request to the I/O bus
@ -112,7 +112,7 @@ class io_dispatcher {
* @param data Data to be written
* @return bool True if write succeeded, false otherwise
*/
bool request_write(uint64_t addr, width_t width, size_t req_no, uint64_t data);
bool request_write(uint64_t addr, libanemo::width_t width, size_t req_no, uint64_t data);
/**
* @brief Create a new agent attached to this dispatcher
@ -127,12 +127,14 @@ class io_dispatcher {
*/
std::vector<mmio_device_def> devices;
uint64_t cycle_count = 0; ///< count of cycles, used by backends and interrupts
friend class mmio_agent;
protected:
ringbuffer<std::tuple<uint64_t, width_t, std::optional<uint64_t>>> read_request_buffer; ///< Read request history buffer
ringbuffer<std::tuple<uint64_t, width_t, uint64_t, bool>> write_request_buffer; ///< Write request history buffer
libanemo::ringbuffer<std::tuple<uint64_t, libanemo::width_t, std::optional<uint64_t>>> read_request_buffer; ///< Read request history buffer
libanemo::ringbuffer<std::tuple<uint64_t, libanemo::width_t, uint64_t, bool>> write_request_buffer; ///< Write request history buffer
std::vector<std::unique_ptr<mmio_agent>> agents; ///< Active agents attached to this dispatcher
};

View File

@ -1,55 +0,0 @@
#ifndef LIBVIO_CONSOLE_HH
#define LIBVIO_CONSOLE_HH
#include <istream>
#include <ostream>
#include <libvio/ringbuffer.hh>
#include <libvio/frontend.hh>
#include <libvio/backend.hh>
#include <cstddef>
#include <cstdint>
#include <optional>
namespace libvio {
namespace reqval {
inline static constexpr uint64_t console_rx = 1<<0; ///< reading from rx
inline static constexpr uint64_t console_tx = 1<<1; ///< writing to tx
inline static constexpr uint64_t console_prescaler = 1<<2; ///< getting or setting prescaler
}
/**
* @brief `io_frontend` implementation for console device
*
* This class follows the behavior of the uart emulator in NEMU.
*/
class console_frontend : public io_frontend {
public:
ioreq_t resolve_read(uint64_t offset, width_t width) const override;
ioreq_t resolve_write(uint64_t offset, width_t width, uint64_t data) const override;
uint64_t ioctl_get(uint64_t req) override;
void ioctl_set(uint64_t req, uint64_t value) override;
};
/**
* @brief Console I/O backend using C++ iostream
*
* This backend implements console input/output using standard C++ streams.
*/
class console_backend_iostream: public io_backend {
public:
console_backend_iostream(std::istream &is, std::ostream &os);
uint64_t request(uint64_t req) override;
bool poll(uint64_t req) override;
bool check(uint64_t req) override;
void put(uint64_t req, uint64_t data) override;
private:
std::istream &istream;
std::ostream &ostream;
std::optional<uint64_t> input_data = {};
};
}
#endif

View File

@ -5,37 +5,13 @@
#ifndef LIBVIO_FRONTEND_HH
#define LIBVIO_FRONTEND_HH
#include <libvio/width.hh>
#include <libanemo/width.hh>
#include <libvio/backend.hh>
#include <cstdint>
#include <optional>
namespace libvio {
/**
* @enum ioreq_type_t
* @brief Type of I/O request being made
*/
enum class ioreq_type_t {
read, ///< Memory read operation
write, ///< Memory write operation
poll_in, ///< Input availability check
poll_out, ///< Output readiness check
ioctl_get, ///< Get control parameter
ioctl_set, ///< Set control parameter
invalid ///< Invalid/uninitialized request
};
/**
* @struct ioreq_t
* @brief Encapsulates a resolved I/O request
* @see namespace `libvio::reqval` for `req` values of each type of device
*/
struct ioreq_t {
ioreq_type_t type; ///< Type of I/O operation requested
uint64_t req; ///< Frontend-specific request identifier/parameter
};
/**
* @class io_frontend
* @brief Abstract base class for I/O frontend implementations
@ -53,63 +29,23 @@ public:
io_backend *backend; ///< Associated backend for I/O operations
/**
* @brief Resolve a read request to backend-specific operation
* @param offset Memory address offset
* @param width Data access width
* @return Resolved I/O request structure
*/
virtual ioreq_t resolve_read(uint64_t offset, width_t width) const = 0;
/**
* @brief Resolve a write request to backend-specific operation
* @param offset Memory address offset
* @param width Data access width
* @param data Data to be written
* @return Resolved I/O request structure
*/
virtual ioreq_t resolve_write(uint64_t offset, width_t width, uint64_t data) const = 0;
/**
* @brief Execute read operation (with cycle caching)
* @brief Execute read operation
* @param offset Memory address offset
* @param width Data access width
* @return Read data or nullopt if request fails
*/
virtual std::optional<uint64_t> read(uint64_t offset, width_t width);
virtual std::optional<uint64_t> read(uint64_t offset, libanemo::width_t width) = 0;
/**
* @brief Execute write operation (with cycle caching)
* @brief Execute write operatio
* @param offset Memory address offset
* @param width Data access width
* @param data Data to be written
* @return true if write succeeded, false otherwise
*/
virtual bool write(uint64_t offset, width_t width, uint64_t data);
virtual bool write(uint64_t offset, libanemo::width_t width, uint64_t data) = 0;
virtual ~io_frontend() = default;
protected:
uint64_t write_data; ///< Cached write data for current cycle
bool write_result; ///< Cached write status for current cycle
/**
* @brief Handle control parameter get operation
* @param req Control request identifier
* @return Current parameter value
*
* Called automatically by read() for ioctl_get requests
*/
virtual uint64_t ioctl_get(uint64_t req) = 0;
/**
* @brief Handle control parameter set operation
* @param req Control request identifier
* @param value New parameter value
*
* Called automatically by write() for ioctl_set requests.
*/
virtual void ioctl_set(uint64_t req, uint64_t value) = 0;
};
}

View File

@ -0,0 +1,24 @@
#ifndef LIBVIO_FRONTEND_CLINT_HH
#define LIBVIO_FRONTEND_CLINT_HH
#include <libvio/frontend.hh>
#include <libvio/backend.hh>
#include <cstdint>
#include <optional>
namespace libvio {
/**
* @brief `io_frontend` implementation for RISC-V clint memory map
*/
class clint: public io_frontend {
public:
std::optional<uint64_t> read(uint64_t offset, libanemo::width_t width) override;
bool write(uint64_t offset, libanemo::width_t width, uint64_t data) override;
private:
bool sip = false;
};
}
#endif

View File

@ -0,0 +1,23 @@
#ifndef LIBVIO_FRONTEND_MTIME_HH
#define LIBVIO_FRONTEND_MTIME_HH
#include <libvio/frontend.hh>
#include <libvio/backend.hh>
#include <cstdint>
#include <optional>
namespace libvio {
/**
* @brief `io_frontend` implementation for RISC-V mtime memory map
*/
class mtime: public io_frontend {
public:
std::optional<uint64_t> read(uint64_t offset, libanemo::width_t width) override;
bool write(uint64_t offset, libanemo::width_t width, uint64_t data) override;
};
}
#endif

View File

@ -0,0 +1,31 @@
#ifndef LIBVIO_FRONTEND_UART16550_HH
#define LIBVIO_FRONTEND_UART16550_HH
#include <libvio/frontend.hh>
#include <libvio/backend.hh>
#include <cstdint>
#include <optional>
namespace libvio {
/**
* @brief `io_frontend` implementation for console device with UART 16550 memory map
*/
class uart16550: public io_frontend {
public:
std::optional<uint64_t> read(uint64_t offset, libanemo::width_t width) override;
bool write(uint64_t offset, libanemo::width_t width, uint64_t data) override;
private:
uint16_t divisor = 1; ///< not used for real IO, just let software read what it wrote
bool dlab = false;
bool irq_enabled = false;
bool rx_irq_enabled = false;
bool tx_irq_enabled = false;
uint8_t lcr = 0x03; ///< not used for real IO, just let software read what it wrote
uint8_t mcr = 0x00; ///< not used for real IO, just let software read what it wrote
uint8_t scratch = 0x00; ///< not used for real IO, just let software read what it wrote
};
}
#endif

View File

@ -0,0 +1,24 @@
#ifndef LIBVIO_FRONTEND_UARTLITE_HH
#define LIBVIO_FRONTEND_UARTLITE_HH
#include <libvio/frontend.hh>
#include <libvio/backend.hh>
#include <cstdint>
#include <optional>
namespace libvio {
/**
* @brief `io_frontend` implementation for console device with Xilinx UART Lite memory map
*/
class uartlite: public io_frontend {
public:
std::optional<uint64_t> read(uint64_t offset, libanemo::width_t width) override;
bool write(uint64_t offset, libanemo::width_t width, uint64_t data) override;
private:
bool intr_enabled = false;
};
}
#endif

View File

@ -1,58 +0,0 @@
/**
* @file mtime.hh
* @brief RISC-V system timer (mtime) frontend interface
*/
#ifndef LIBVIO_MTIME_HH
#define LIBVIO_MTIME_HH
#include <libvio/frontend.hh>
#include <libvio/backend.hh>
#include <cstdint>
#include <chrono>
namespace libvio {
namespace reqval {
inline static constexpr uint64_t mtime_l = 1<<0; ///< Reading lower part of mtime register
inline static constexpr uint64_t mtime_h = 1<<1; ///< Reading higher part of mtime register
inline static constexpr uint64_t mtimecmp_l = 1<<2; ///< Reading/writing lower part of mtimecmp register
inline static constexpr uint64_t mtimecmp_h = 1<<3; ///< Reading/writing higher part of mtimecmp register
}
/**
* @brief `io_frontend` implementation for RISC-V system timer (mtime/mtimecmp)
*
* This class handles the memory-mapped accesses to the mtime and mtimecmp registers
* as defined in the RISC-V privileged specification.
*/
class mtime_frontend : public io_frontend {
public:
ioreq_t resolve_read(uint64_t offset, width_t width) const override;
ioreq_t resolve_write(uint64_t offset, width_t width, uint64_t data) const override;
uint64_t ioctl_get(uint64_t req) override;
void ioctl_set(uint64_t req, uint64_t value) override;
};
/**
* @brief Timer backend implementation using std::chrono
*
* This backend provides the actual timer functionality using C++'s chrono library.
* It implements the mtime and mtimecmp functionality as specified in RISC-V.
*/
class mtime_backend_chrono : public io_backend {
public:
mtime_backend_chrono(void);
uint64_t request(uint64_t req) override;
bool poll(uint64_t req) override;
bool check(uint64_t req) override;
void put(uint64_t req, uint64_t data) override;
private:
// 我叫达拉崩吧斑得贝迪卜多比鲁翁
std::chrono::high_resolution_clock::time_point mtime_offset;
uint64_t mtimecmp;
};
}
#endif

View File

@ -1,126 +0,0 @@
/**
* @file width.hh
* @brief Provides width-related operations for integer types
*/
#ifndef LIBVIO_WIDTH_HH
#define LIBVIO_WIDTH_HH
#include <cstdint>
#include <string>
namespace libvio {
/**
* @enum width_t
* @brief Enumeration representing different data widths
*/
enum class width_t {
byte = 1, ///< 1-byte width (8 bits)
half = 2, ///< 2-byte width (16 bits)
word = 4, ///< 4-byte width (32 bits)
dword = 8 ///< 8-byte width (64 bits)
};
/**
* @brief Truncates a value to the specified width by zeroing upper bits
* @tparam WORD_T The word type (uint32_t or uint64_t)
* @param value The input value to truncate
* @param width The target width to truncate to
* @return The truncated value with upper bits zeroed
*/
template <typename WORD_T>
constexpr WORD_T zero_truncate(WORD_T value, width_t width);
/**
* @brief Specialization of zero_truncate for uint32_t
* @param value The 32-bit input value to truncate
* @param width The target width to truncate to
* @return The truncated 32-bit value with upper bits zeroed
*/
template <>
constexpr uint32_t zero_truncate<uint32_t>(uint32_t value, width_t width) {
return value & ((1ull << (8 * static_cast<uint32_t>(width))) - 1);
}
/**
* @brief Specialization of zero_truncate for uint64_t
* @param value The 64-bit input value to truncate
* @param width The target width to truncate to
* @return The truncated 64-bit value with upper bits zeroed
*/
template <>
constexpr uint64_t zero_truncate<uint64_t>(uint64_t value, width_t width) {
return value & ((1ull << (8 * static_cast<uint64_t>(width))) - 1);
}
/**
* @brief Sign-extends a value to the full width of the type
* @tparam WORD_T The word type (uint32_t or uint64_t)
* @param value The input value to sign-extend
* @param width The original width of the input value
* @return The sign-extended value
*/
template <typename WORD_T>
constexpr WORD_T sign_extend(WORD_T value, width_t width);
/**
* @brief Specialization of sign_extend for uint32_t
* @param value The 32-bit input value to sign-extend
* @param width The original width of the input value
* @return The sign-extended 32-bit value
*/
template <>
constexpr uint32_t sign_extend<uint32_t>(uint32_t value, width_t width) {
switch (width) {
case width_t::byte:
return uint32_t(int32_t(int8_t(value)));
case width_t::half:
return uint32_t(int32_t(int16_t(value)));
default:
return value;
}
}
/**
* @brief Specialization of sign_extend for uint64_t
* @param value The 64-bit input value to sign-extend
* @param width The original width of the input value
* @return The sign-extended 64-bit value
*/
template <>
constexpr uint64_t sign_extend<uint64_t>(uint64_t value, width_t width) {
switch (width) {
case width_t::byte:
return uint64_t(int64_t(int8_t(value)));
case width_t::half:
return uint64_t(int64_t(int16_t(value)));
case width_t::word:
return uint64_t(int64_t(int32_t(value)));
default:
return value;
}
}
}
namespace std {
/**
* @brief Converts a width_t enum value to its string representation
* @param width The width enum value to convert
* @return String representation of the width
*/
inline string to_string(libvio::width_t width) noexcept {
switch (width) {
case libvio::width_t::byte: return "byte";
case libvio::width_t::half: return "half";
case libvio::width_t::word: return "word";
case libvio::width_t::dword: return "dword";
default: return "unknown";
}
}
}
#endif

View File

@ -0,0 +1,61 @@
/**
* @file An example of the core functionalities of this library. This file
* assumes the same memory layout with NEMU. It is compatible with binaries
* compiled for `riscv32-nemu`.
*/
#include <cstddef>
#include <cstdint>
#include <iostream>
#include <libcpu/memory.hh>
#include <libcpu/riscv_cpu_system.hh>
#include <libsdb/sdb.hh>
#include <libvio/bus.hh>
#include <libvio/frontend/clint.hh>
#include <libvio/frontend/uart16550.hh>
#include <libvio/frontend/mtime.hh>
#include <libvio/backend/ioflow_iostream.hh>
#include <libvio/backend/mtime_cycles.hh>
#include <libanemo/ringbuffer.hh>
#include <libvio/frontend/uartlite.hh>
#include <string>
int main(int argc, char** argv) {
if (argc < 2) {
std::cerr << "Usage: " << argv[0] << " <elf_file> [sdb_command]...\n";
return 1;
}
libvio::io_dispatcher bus{{
{new libvio::uart16550{}, new libvio::ioflow_backend_iostream{}, 0xa00003f8, 8},
{new libvio::mtime{}, new libvio::mtime_backend_cycles{}, 0xa0000048, 16}
}};
using word_t = uint64_t;
libcpu::riscv_cpu_system<word_t> cpu;
libcpu::memory memory{0x80000000, 128*1024*1024};
memory.load_elf_from_file(argv[1]);
cpu.instr_bus = &memory;
cpu.data_bus = &memory;
cpu.mmio_bus = bus.new_agent();
libanemo::ringbuffer<libcpu::event_t<word_t>> events{12};
cpu.event_buffer = &events;
cpu.reset(0x80000000);
libsdb::sdb<word_t> sdb {};
sdb.cpu = &cpu;
for (size_t i=2; i<argc; ++i) {
sdb.execute_command(argv[i]);
}
while (!sdb.stopped()) {
std::cout << sdb.get_prompt();
std::string cmd;
std::getline(std::cin, cmd);
sdb.execute_command(cmd);
}
sdb.execute_command("status");
return 0;
}

View File

@ -0,0 +1,116 @@
/**
* @file A minimal RISC-V emulator based on the emulator backend.
*
* The purpose of this file is showing the usage and internals of the emulator
* backend. The backend itself is not compliant with the `abstract_cpu` API. The
* backend may have breaking changes within a single major version. This file
* assumes the same memory mapping with NEMU.
*
*/
#include <cassert>
#include <cstdint>
#include <iostream>
#include <chrono>
#include <libcpu/memory.hh>
#include <libvio/bus.hh>
#include <libcpu/riscv/riscv.hh>
#include <libcpu/riscv/user_core.hh>
#include <libcpu/riscv/privilege_module.hh>
#include <libcpu/riscv/decode_cache.hh>
#include <libvio/frontend/uart16550.hh>
#include <libvio/frontend/mtime.hh>
#include <libvio/backend/ioflow_iostream.hh>
#include <libvio/backend/mtime_cycles.hh>
int main(int argc, char **argv) {
if (argc != 2) {
std::cerr << "Usage: " << argv[0] << " <elf_file>\n";
return 1;
}
using word_t = uint64_t;
libcpu::riscv::user_core<word_t> user_core;
libcpu::riscv::privilege_module<word_t> privilege_module;
libcpu::memory memory{0x80000000, 128*1024*1024};
memory.load_elf_from_file(argv[1]);
privilege_module.instr_bus = &memory;
privilege_module.data_bus = &memory;
libvio::io_dispatcher bus{{
{new libvio::uart16550{}, new libvio::ioflow_backend_iostream{}, 0xa00003f8, 8},
{new libvio::mtime{}, new libvio::mtime_backend_cycles{}, 0xa0000048, 16}
}};
privilege_module.mmio_bus = bus.new_agent();
libcpu::riscv::decode_cache<word_t, 24, 2> decode_cache;
libcpu::riscv::exec_result_t<word_t> exec_result;
exec_result.type = libcpu::riscv::exec_result_type_t::retire;
exec_result.pc = 0x80000000;
user_core.reset();
privilege_module.reset();
// Variables for performance measurement
uint64_t instruction_count = 0;
auto start_time = std::chrono::high_resolution_clock::now();
while (true) {
privilege_module.vaddr_fetch_instruction(exec_result);
if (exec_result.type == libcpu::riscv::exec_result_type_t::fetch) {
// libcpu::riscv::user_core<word_t>::decode(exec_result);
decode_cache.decode(exec_result);
}
if (exec_result.type == libcpu::riscv::exec_result_type_t::decode) {
user_core.execute(exec_result);
}
// Do privileged operations
if (exec_result.type == libcpu::riscv::exec_result_type_t::load) {
privilege_module.vaddr_load(exec_result);
} else if (exec_result.type == libcpu::riscv::exec_result_type_t::store) {
privilege_module.vaddr_store(exec_result);
} else if (exec_result.type == libcpu::riscv::exec_result_type_t::amo) {
privilege_module.vaddr_amo(exec_result);
} else if (exec_result.type == libcpu::riscv::exec_result_type_t::csr_op) {
privilege_module.csr_op(exec_result);
} else if (exec_result.type == libcpu::riscv::exec_result_type_t::sys_op) {
privilege_module.sys_op(exec_result);
}
if (exec_result.type == libcpu::riscv::exec_result_type_t::trap) {
if (exec_result.trap.cause == libcpu::riscv::mcause<word_t>::except_breakpoint) {
break;
} else {
privilege_module.handle_exception(exec_result);
}
}
assert(exec_result.type == libcpu::riscv::exec_result_type_t::retire);
if (exec_result.retire.rd != 0) {
user_core.gpr[exec_result.retire.rd] = exec_result.retire.value;
}
exec_result.pc = exec_result.next_pc;
privilege_module.mmio_bus->next_cycle();
// Count retired instructions
instruction_count++;
}
// Calculate and print performance statistics
auto end_time = std::chrono::high_resolution_clock::now();
auto duration = std::chrono::duration_cast<std::chrono::microseconds>(end_time - start_time);
double seconds = duration.count() / 1000000.0;
double ips = instruction_count / seconds;
std::cout << "\n--- Performance Statistics ---\n";
std::cout << "Instructions executed: " << instruction_count << "\n";
std::cout << "Execution time: " << seconds << " seconds\n";
std::cout << "Simulated instructions per second: " << uint32_t(ips) << " IPS\n";
return 0;
}

162
src/libcpu/memory.cc Normal file
View File

@ -0,0 +1,162 @@
#include <cassert>
#include <cstddef>
#include <cstdint>
#include <libcpu/memory.hh>
#include <algorithm>
#include <elf.h>
#include <fstream>
#include <libanemo/width.hh>
#include <memory>
namespace libcpu {
memory_view::memory_view(const memory_view &src, uint64_t src_base, uint64_t view_base, uint64_t view_size) {
offset = src.offset + view_base - src_base;
addr_lb = view_base;
addr_ub = view_base + view_size;
}
memory_view::memory_view() {}
memory::memory(uint64_t mem_base, size_t mem_size) {
mem = std::unique_ptr<uint8_t[]>{new uint8_t[mem_size]};
offset = reinterpret_cast<uintptr_t>(mem.get()) - mem_base;
addr_lb = mem_base;
addr_ub = mem_base + mem_size;
// This keeps us from pointer arithmetics related undefined behaviors
assert(mem.get() == reinterpret_cast<uint8_t*>(addr_lb+offset));
assert(mem.get()+(addr_ub-addr_lb-1) == reinterpret_cast<uint8_t*>(addr_ub+offset-1));
}
std::optional<uint64_t> memory_view::read(uint64_t addr, libanemo::width_t width, bool little_endian) {
if (out_of_bound(addr, width)) {
return {};
}
uint8_t *host_addr = reinterpret_cast<uint8_t*>(addr+offset);
const size_t w = static_cast<size_t>(width);
uint64_t value = 0;
if (little_endian) {
for (size_t i = 0; i < w; i++) {
value |= static_cast<uint64_t>(host_addr[i]) << (i * 8);
}
} else {
for (size_t i = 0; i < w; i++) {
value = (value << 8) | host_addr[i];
}
}
return value;
}
bool memory_view::write(uint64_t addr, libanemo::width_t width, uint64_t value, bool little_endian) {
uint8_t *host_addr = reinterpret_cast<uint8_t*>(addr+offset);
const size_t w = static_cast<size_t>(width);
if (out_of_bound(addr, width)) {
return false;
}
if (little_endian) {
for (size_t i=0; i < w; i++) {
host_addr[i] = (value >> (i * 8)) & 0xFF;
}
} else {
for (size_t i=0; i < w; i++) {
host_addr[i] = (value >> ((w - 1 - i) * 8)) & 0xFF;
}
}
return true;
}
uint8_t* memory_view::host_addr(uint64_t addr) {
if (out_of_bound(addr, libanemo::width_t::byte)) {
return nullptr;
}
return reinterpret_cast<uint8_t*>(addr+offset);
}
void memory_view::save(const char* filename) const {
std::ofstream out(filename, std::ios::binary);
if (!out) return;
save(out);
}
void memory_view::save(std::ostream& out) const {
char* mem_ptr = reinterpret_cast<char*>(addr_lb+offset);
size_t mem_size =static_cast<size_t>(addr_ub-addr_lb);
out.write(mem_ptr, mem_size);
}
uint64_t memory_view::restore(const char* filename) {
std::ifstream in(filename, std::ios::binary);
if (!in) return 0;
return restore(in);
}
uint64_t memory_view::restore(std::istream& in) {
char* mem_ptr = reinterpret_cast<char*>(addr_lb+offset);
size_t mem_size =static_cast<size_t>(addr_ub-addr_lb);
in.seekg(0, std::ios::end);
const size_t file_size = in.tellg();
in.seekg(0);
const size_t bytes_to_read = std::min(file_size, mem_size);
in.read(reinterpret_cast<char*>(mem_ptr), bytes_to_read);
return bytes_to_read;
}
template <typename WORD_T, typename EHDR_T, typename PHDR_T>
static inline WORD_T load_elf_impl(uint8_t *dest, const uint8_t *src, size_t offset) {
EHDR_T *elf_header = (EHDR_T*)(src);
// load metadata
WORD_T entry = elf_header->e_entry;
// load each segment
PHDR_T *segment_headers = (PHDR_T*)(src+elf_header->e_phoff);
for (size_t i=0; i<elf_header->e_phnum; ++i) {
if (segment_headers[i].p_type != PT_LOAD) {
continue;
}
WORD_T seg_base = segment_headers[i].p_offset;
WORD_T seg_size = segment_headers[i].p_memsz;
WORD_T file_size = segment_headers[i].p_filesz;
uint8_t *target_addr = dest + segment_headers[i].p_vaddr - offset;
// load the content
const uint8_t *seg_content = src + seg_base;
std::copy(seg_content, seg_content+file_size, target_addr);
// fill the remaining part with zero
if (seg_size > file_size) {
std::fill_n(target_addr+file_size, seg_size-file_size, 0);
}
}
return entry;
}
uint64_t memory_view::load_elf(const uint8_t* buffer) {
uint8_t* mem_ptr = reinterpret_cast<uint8_t*>(addr_lb+offset);
if (buffer[4] == ELFCLASS32) {
return load_elf_impl<uint32_t, Elf32_Ehdr, Elf32_Phdr>(mem_ptr, buffer, addr_lb);
} else if (buffer[4] == ELFCLASS64) {
return load_elf_impl<uint64_t, Elf64_Ehdr, Elf64_Phdr>(mem_ptr, buffer, addr_lb);
}
return 0;
}
uint64_t memory_view::load_elf_from_file(const char* filename) {
std::ifstream file(filename, std::ios::binary | std::ios::ate);
const auto file_size = file.tellg();
file.seekg(0);
std::unique_ptr<uint8_t[]> buffer(new uint8_t[file_size]);
file.read(reinterpret_cast<char*>(buffer.get()), file_size);
return load_elf(buffer.get());
}
uint64_t memory_view::get_size() const {
return addr_ub - addr_lb;
}
bool memory_view::out_of_bound(uint64_t addr, libanemo::width_t width) const {
const size_t up_addr = addr + static_cast<size_t>(width);
return addr < addr_lb || up_addr > addr_ub;
}
} // namespace libcpu

View File

@ -1,62 +0,0 @@
#include <cstddef>
#include <cstdint>
#include <libcpu/riscv.hh>
#include <libcpu/rv32i.hh>
#include <libcpu/rv32i_cpu_system.hh>
#include <libvio/ringbuffer.hh>
using namespace libcpu;
using namespace libcpu::rv32i;
void rv32i_cpu_system::reset(word_t init_pc) {
for (size_t i=0; i<32; ++i) {
gpr[i] = 0;
}
for (size_t i=0; i<n_csr; ++i) {
csr[i] = csr_info[i].init_value;
}
pc = init_pc;
priv_level = priv_level_t::m;
decode_cache.resize(4096);
decode_cache_addr_mask = 0xfff;
};
uint8_t rv32i_cpu_system::n_gpr(void) const {
return 32;
}
const char* rv32i_cpu_system::gpr_name(uint8_t addr) const {
return riscv::gpr_name(addr);
}
uint8_t rv32i_cpu_system::gpr_addr(const char* name) const {
return riscv::gpr_addr(name);
}
uint32_t rv32i_cpu_system::get_gpr(uint8_t gpr_addr) const {
return gpr[gpr_addr];
}
uint32_t rv32i_cpu_system::get_pc(void) const {
return pc;
}
priv_level_t rv32i_cpu_system::get_priv_level() const {
return priv_level;
}
const uint32_t* rv32i_cpu_system::get_gpr(void) const {
return gpr.data();
};
std::optional<rv32i_cpu_system::word_t> rv32i_cpu_system::pmem_peek(word_t addr, libvio::width_t width) const {
return data_bus->peek(addr, width);
}
bool rv32i_cpu_system::stopped(void) const {
return ebreak_flag;
}
std::optional<rv32i_cpu_system::word_t> rv32i_cpu_system::get_trap(void) const {
return next_trap;
}

View File

@ -1,261 +0,0 @@
#include <libcpu/rv32i.hh>
#include <libcpu/rv32i_cpu_system.hh>
// In this file, csr operations are implemented.
using namespace libcpu;
using namespace libcpu::rv32i;
// orded in accessing frequnecy to boost performance
const rv32i_cpu_system::csr_info_t rv32i_cpu_system::csr_info[n_csr] = {
// init_value wpri_mask name addr | Cat Commentary
{0x00000000, 0x0000ffff, "mip", CSR_ADDR_MIP}, // 0s? Purrfect for ignoring mice interrupts 🐭
{0x00000000, 0x0000ffff, "mie", CSR_ADDR_MIE}, // MIE? More like "Meow-Interrupts-Enabled"
{0x00001800, 0x00001888, "mstatus", CSR_ADDR_MSTATUS}, // "mstatus" = "I own this CPU" mode 🐈⬛
{0x80000000, 0xfffffffd, "mtvec", CSR_ADDR_MTVEC}, // MTVec: High-bit set = "Jump to bed, not code" 🛏️
{0x00000000, 0xffffffff, "mscratch", CSR_ADDR_MSCRATCH}, // Scratch register? *sharpens claws*
{0x00000000, 0xfffffffe, "mepc", CSR_ADDR_MEPC}, // EPC = "Emergency Nap Return Address" 😴
{0x00000000, 0x8000000f, "mcause", CSR_ADDR_MCAUSE}, // Cause: 0x0 = "Human disturbed my nap"
{0x00000000, 0xffffffff, "mtval", CSR_ADDR_MTVAL}, // Trap value = location of spilled milk 🥛
{0x40101100, 0x00000000, "misa", CSR_ADDR_MISA}, // MISA: "Meow-Approved ISA Settings" (RV32IMAC)
{0x00000000, 0x00000000, "mstatush", CSR_ADDR_MSTATUSH}, // Extended status: "Still napping" (64-bit edition)
};
// CSR access with no permission checking
// simulator internal use only
uint32_t rv32i_cpu_system::csr_read(csr_addr_t addr) const {
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == addr) {
return csr[i];
}
}
return 0;
}
uint32_t rv32i_cpu_system::csr_read_bits(csr_addr_t addr, uint32_t bit_mask) const {
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == addr) {
return csr[i] & bit_mask;
}
}
return 0;
}
void rv32i_cpu_system::csr_write(csr_addr_t addr, uint32_t value) {
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == addr) {
csr[i] = value;
break;
}
}
}
void rv32i_cpu_system::csr_write_bits(csr_addr_t addr, uint32_t value, uint32_t bit_mask) {
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == addr) {
uint32_t oldval = csr[i];
csr[i] = (oldval & ~bit_mask) | (value & bit_mask);
break;
}
}
}
void rv32i_cpu_system::csr_set_bits(csr_addr_t addr, uint32_t bits) {
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == addr) {
uint32_t oldval = csr[i];
uint32_t newval = oldval | bits;
csr[i] = newval;
break;
}
}
}
void rv32i_cpu_system::csr_clear_bits(csr_addr_t addr, uint32_t bits) {
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == addr) {
uint32_t oldval = csr[i];
uint32_t newval = oldval & (~bits);
csr[i] = newval;
break;
}
}
}
// functions that simulate behaviros of CSR accesing instructions
bool rv32i_cpu_system::csr_check_read_access(csr_addr_t addr) const {
return static_cast<int>(priv_level) >= (addr>>8 & 0x3);
}
bool rv32i_cpu_system::csr_check_write_access(csr_addr_t addr) const {
return csr_check_read_access(addr) && (addr>>10)!=0x3;
}
#define CSR_ACCESS_FAULT cpu->raise_exception(EXCEPTION_ILLEGAL_INSTRUCTION, decode.instr); return;
void rv32i_cpu_system::csrrw(rv32i_cpu_system* cpu, const decode_t& decode) {
csr_addr_t csr_addr = static_cast<csr_addr_t>(decode.imm&0xfff);
if (!cpu->csr_check_write_access(csr_addr)) {
CSR_ACCESS_FAULT;
}
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == csr_addr) {
uint32_t oldval = cpu->csr[i];
uint32_t newval = decode.rs1==0 ? 0 : cpu->gpr[decode.rs1];
if (decode.rd != 0) {
// write access implies read access
// so no need to check here
cpu->gpr[decode.rd] = oldval;
// read side effects here
}
cpu->csr[i] = (oldval & ~csr_info[i].wpri_mask) | (newval & csr_info[i].wpri_mask);
// write side effects here
return;
}
}
// trap if attempting to access a non-existing CSR
CSR_ACCESS_FAULT;
}
void rv32i_cpu_system::csrrs(rv32i_cpu_system* cpu, const decode_t& decode) {
csr_addr_t csr_addr = static_cast<csr_addr_t>(decode.imm&0xfff);
if (!cpu->csr_check_read_access(csr_addr)) {
CSR_ACCESS_FAULT;
}
if (decode.rs1!=0 && !cpu->csr_check_write_access(csr_addr)) {
CSR_ACCESS_FAULT;
}
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == csr_addr) {
uint32_t oldval = cpu->csr[i];
uint32_t bitmask = decode.rs1==0 ? 0 : cpu->gpr[decode.rs1];
if (decode.rd != 0) {
cpu->gpr[decode.rd] = oldval;
}
// read side effects here
if (decode.rs1 != 0) {
bitmask &= csr_info[i].wpri_mask;
cpu->csr[i] = oldval | bitmask;
// write side effects here
}
return;
}
}
// trap if attempting to access a non-existing CSR
CSR_ACCESS_FAULT;
}
void rv32i_cpu_system::csrrc(rv32i_cpu_system* cpu, const decode_t& decode) {
csr_addr_t csr_addr = static_cast<csr_addr_t>(decode.imm&0xfff);
if (!cpu->csr_check_read_access(csr_addr)) {
CSR_ACCESS_FAULT;
}
if (decode.rs1!=0 && !cpu->csr_check_write_access(csr_addr)) {
CSR_ACCESS_FAULT;
}
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == csr_addr) {
uint32_t oldval = cpu->csr[i];
uint32_t bitmask = decode.rs1==0 ? 0 : cpu->gpr[decode.rs1];
// write access implies read access
// so no need to check here
if (decode.rd != 0) {
cpu->gpr[decode.rd] = oldval;
}
// read side effects here
if (decode.rs1 != 0) {
bitmask &= csr_info[i].wpri_mask;
cpu->csr[i] = oldval & ~bitmask;
// write side effects here
}
return;
}
}
// trap if attempting to access a non-existing CSR
CSR_ACCESS_FAULT;
}
void rv32i_cpu_system::csrrwi(rv32i_cpu_system* cpu, const decode_t& decode) {
csr_addr_t csr_addr = static_cast<csr_addr_t>(decode.imm&0xfff);
if (!cpu->csr_check_write_access(csr_addr)) {
CSR_ACCESS_FAULT;
}
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == csr_addr) {
uint32_t oldval = cpu->csr[i];
uint32_t newval = decode.rs1;
if (decode.rd != 0) {
// write access implies read access
// so no need to check here
cpu->gpr[decode.rd] = oldval;
// read side effects here
}
cpu->csr[i] = (oldval & ~csr_info[i].wpri_mask) | (newval & csr_info[i].wpri_mask);
// write side effects here
return;
}
}
// trap if attempting to access a non-existing CSR
CSR_ACCESS_FAULT;
}
void rv32i_cpu_system::csrrsi(rv32i_cpu_system* cpu, const decode_t& decode) {
csr_addr_t csr_addr = static_cast<csr_addr_t>(decode.imm&0xfff);
if (!cpu->csr_check_read_access(csr_addr)) {
CSR_ACCESS_FAULT;
}
if (decode.rs1!=0 && !cpu->csr_check_write_access(csr_addr)) {
CSR_ACCESS_FAULT;
}
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == csr_addr) {
uint32_t oldval = cpu->csr[i];
uint32_t bitmask = decode.rs1;
if (decode.rd != 0) {
cpu->gpr[decode.rd] = oldval;
}
// read side effects here
if (decode.rs1 != 0) {
bitmask &= csr_info[i].wpri_mask;
cpu->csr[i] = oldval | bitmask;
// write side effects here
}
return;
}
}
// trap if attempting to access a non-existing CSR
CSR_ACCESS_FAULT;
}
void rv32i_cpu_system::csrrci(rv32i_cpu_system* cpu, const decode_t& decode) {
csr_addr_t csr_addr = static_cast<csr_addr_t>(decode.imm&0xfff);
if (!cpu->csr_check_read_access(csr_addr)) {
CSR_ACCESS_FAULT;
}
if (decode.rs1!=0 && !cpu->csr_check_write_access(csr_addr)) {
CSR_ACCESS_FAULT;
}
for (size_t i=0; i<rv32i_cpu_system::n_csr; ++i) {
if (csr_info[i].addr == csr_addr) {
uint32_t oldval = cpu->csr[i];
uint32_t bitmask = decode.rs1;
// write access implies read access
// so no need to check here
if (decode.rd != 0) {
cpu->gpr[decode.rd] = oldval;
}
// read side effects here
if (decode.rs1 != 0) {
bitmask &= csr_info[i].wpri_mask;
cpu->csr[i] = oldval & ~bitmask;
// write side effects here
}
return;
}
}
// trap if attempting to access a non-existing CSR
CSR_ACCESS_FAULT;
}

View File

@ -1,138 +0,0 @@
#include <cstdint>
#include <libcpu/rv32i.hh>
#include <libcpu/rv32i_cpu_system.hh>
using namespace libcpu;
using namespace libcpu::rv32i;
#define INSTPAT(pattern, mask, type, operation) \
if (((instruction^pattern)&mask) == 0) { \
decode.op = operation; \
decode.imm = imm_##type(instruction); \
decode.rs1 = rs1_##type(instruction);\
decode.rs2 = rs2_##type(instruction);\
decode.rd = rd_##type(instruction);\
return decode; \
}
// R-type
static inline uint32_t imm_r(uint32_t instr) { return 0; }
static inline uint32_t rs1_r(uint32_t instr) { return (instr >> 15) & 0x1F; }
static inline uint32_t rs2_r(uint32_t instr) { return (instr >> 20) & 0x1F; }
static inline uint32_t rd_r(uint32_t instr) { return (instr >> 7) & 0x1F; }
// I-type
static inline uint32_t imm_i(uint32_t instr) { return int32_t(instr) >> 20; }
static inline uint32_t rs1_i(uint32_t instr) { return (instr >> 15) & 0x1F; }
static inline uint32_t rs2_i(uint32_t) { return 0; }
static inline uint32_t rd_i(uint32_t instr) { return (instr >> 7) & 0x1F; }
// S-type
static inline uint32_t imm_s(uint32_t instr) { return (int32_t(instr & 0xfe000000) >> 20) | ((instr >> 7) & 0x1f); }
static inline uint32_t rs1_s(uint32_t instr) { return (instr >> 15) & 0x1F; }
static inline uint32_t rs2_s(uint32_t instr) { return (instr >> 20) & 0x1F; }
static inline uint32_t rd_s(uint32_t) { return 0; }
// B-type
static inline uint32_t imm_b(uint32_t instr) { return (int32_t(instr & 0x80000000) >> 19) | ((instr & 0x80) << 4) | ((instr >> 20) & 0x7e0) | ((instr >> 7) & 0x1e); }
static inline uint32_t rs1_b(uint32_t instr) { return (instr >> 15) & 0x1F; }
static inline uint32_t rs2_b(uint32_t instr) { return (instr >> 20) & 0x1F; }
static inline uint32_t rd_b(uint32_t) { return 0; }
// U-type
static inline uint32_t imm_u(uint32_t instr) { return instr & 0xfffff000; }
static inline uint32_t rs1_u(uint32_t) { return 0; }
static inline uint32_t rs2_u(uint32_t) { return 0; }
static inline uint32_t rd_u(uint32_t instr) { return (instr >> 7) & 0x1F; }
// J-type
static inline uint32_t imm_j(uint32_t instr) { return (int32_t(instr & 0x80000000) >> 11) | (instr & 0xff000) | ((instr >> 9) & 0x800) | ((instr >> 20) & 0x7fe); }
static inline uint32_t rs1_j(uint32_t) { return 0; }
static inline uint32_t rs2_j(uint32_t) { return 0; }
static inline uint32_t rd_j(uint32_t instr) { return (instr >> 7) & 0x1F; }
rv32i_cpu_system::decode_t rv32i_cpu_system::decode_instruction(uint32_t instruction) {
decode_t decode{.imm=0, .rd=0};
decode.instr = instruction;
// U-type instructions
INSTPAT(0b00000000000000000000000000110111, 0b00000000000000000000000001111111, u, lui);
INSTPAT(0b00000000000000000000000000010111, 0b00000000000000000000000001111111, u, auipc);
// J-type
INSTPAT(0b00000000000000000000000001101111, 0b00000000000000000000000001111111, j, jal);
// I-type (jalr)
INSTPAT(0b00000000000000000000000001100111, 0b00000000000000000111000001111111, i, jalr);
// B-type
INSTPAT(0b00000000000000000000000001100011, 0b00000000000000000111000001111111, b, beq);
INSTPAT(0b00000000000000000001000001100011, 0b00000000000000000111000001111111, b, bne);
INSTPAT(0b00000000000000000100000001100011, 0b00000000000000000111000001111111, b, blt);
INSTPAT(0b00000000000000000101000001100011, 0b00000000000000000111000001111111, b, bge);
INSTPAT(0b00000000000000000110000001100011, 0b00000000000000000111000001111111, b, bltu);
INSTPAT(0b00000000000000000111000001100011, 0b00000000000000000111000001111111, b, bgeu);
// Loads (I-type)
INSTPAT(0b00000000000000000000000000000011, 0b00000000000000000111000001111111, i, lb);
INSTPAT(0b00000000000000000001000000000011, 0b00000000000000000111000001111111, i, lh);
INSTPAT(0b00000000000000000010000000000011, 0b00000000000000000111000001111111, i, lw);
INSTPAT(0b00000000000000000100000000000011, 0b00000000000000000111000001111111, i, lbu);
INSTPAT(0b00000000000000000101000000000011, 0b00000000000000000111000001111111, i, lhu);
// Stores (S-type)
INSTPAT(0b00000000000000000000000000100011, 0b00000000000000000111000001111111, s, sb);
INSTPAT(0b00000000000000000001000000100011, 0b00000000000000000111000001111111, s, sh);
INSTPAT(0b00000000000000000010000000100011, 0b00000000000000000111000001111111, s, sw);
// I-type ALU
INSTPAT(0b00000000000000000000000000010011, 0b00000000000000000111000001111111, i, addi);
INSTPAT(0b00000000000000000010000000010011, 0b00000000000000000111000001111111, i, slti);
INSTPAT(0b00000000000000000011000000010011, 0b00000000000000000111000001111111, i, sltiu);
INSTPAT(0b00000000000000000100000000010011, 0b00000000000000000111000001111111, i, xori);
INSTPAT(0b00000000000000000110000000010011, 0b00000000000000000111000001111111, i, ori);
INSTPAT(0b00000000000000000111000000010011, 0b00000000000000000111000001111111, i, andi);
INSTPAT(0b00000000000000000001000000010011, 0b11111110000000000111000001111111, i, slli);
INSTPAT(0b00000000000000000101000000010011, 0b11111110000000000111000001111111, i, srli);
INSTPAT(0b01000000000000000101000000010011, 0b11111110000000000111000001111111, i, srai);
// R-type
INSTPAT(0b00000000000000000000000000110011, 0b11111110000000000111000001111111, r, add);
INSTPAT(0b01000000000000000000000000110011, 0b11111110000000000111000001111111, r, sub);
INSTPAT(0b00000000000000000001000000110011, 0b11111110000000000111000001111111, r, sll);
INSTPAT(0b00000000000000000010000000110011, 0b11111110000000000111000001111111, r, slt);
INSTPAT(0b00000000000000000011000000110011, 0b11111110000000000111000001111111, r, sltu);
INSTPAT(0b00000000000000000100000000110011, 0b11111110000000000111000001111111, r, xor_);
INSTPAT(0b00000000000000000101000000110011, 0b11111110000000000111000001111111, r, srl);
INSTPAT(0b01000000000000000101000000110011, 0b11111110000000000111000001111111, r, sra);
INSTPAT(0b00000000000000000110000000110011, 0b11111110000000000111000001111111, r, or_);
INSTPAT(0b00000000000000000111000000110011, 0b11111110000000000111000001111111, r, and_);
// M-extension
INSTPAT(0b00000010000000000000000000110011, 0b11111110000000000111000001111111, r, mul);
INSTPAT(0b00000010000000000001000000110011, 0b11111110000000000111000001111111, r, mulh);
INSTPAT(0b00000010000000000010000000110011, 0b11111110000000000111000001111111, r, mulhsu);
INSTPAT(0b00000010000000000011000000110011, 0b11111110000000000111000001111111, r, mulhu);
INSTPAT(0b00000010000000000100000000110011, 0b11111110000000000111000001111111, r, div);
INSTPAT(0b00000010000000000101000000110011, 0b11111110000000000111000001111111, r, divu);
INSTPAT(0b00000010000000000110000000110011, 0b11111110000000000111000001111111, r, rem);
INSTPAT(0b00000010000000000111000000110011, 0b11111110000000000111000001111111, r, remu);
// CSR operations
INSTPAT(0b00000000000000000001000001110011, 0b00000000000000000111000001111111, i, csrrw);
INSTPAT(0b00000000000000000010000001110011, 0b00000000000000000111000001111111, i, csrrs);
INSTPAT(0b00000000000000000011000001110011, 0b00000000000000000111000001111111, i, csrrc);
INSTPAT(0b00000000000000000101000001110011, 0b00000000000000000111000001111111, i, csrrwi);
INSTPAT(0b00000000000000000110000001110011, 0b00000000000000000111000001111111, i, csrrsi);
INSTPAT(0b00000000000000000111000001110011, 0b00000000000000000111000001111111, i, csrrci);
// Environment calls
INSTPAT(0b00000000000000000000000001110011, 0b11111111111111111111111111111111, r, ecall);
INSTPAT(0b00110000001000000000000001110011, 0b11111111111111111111111111111111, r, mret);
INSTPAT(0b00000000000100000000000001110011, 0b11111111111111111111111111111111, r, ebreak);
// Default case for invalid instructions
decode.op = nullptr;
return decode;
}

View File

@ -1,62 +0,0 @@
#include <cstddef>
#include <cstdint>
#include <libcpu/abstract_cpu.hh>
#include <libcpu/rv32i.hh>
#include <libcpu/rv32i_cpu_system.hh>
using namespace libcpu;
using namespace libcpu::rv32i;
void rv32i_cpu_system::next_cycle(void) {
next_instruction();
}
void rv32i_cpu_system::next_instruction(void) {
if (pc%4 != 0) {
raise_exception(EXCEPTION_INSTRUCTION_ADDRESS_MISALIGNED, pc);
return;
}
auto instruction_opt = instr_bus->read(pc, libvio::width_t::word);
if (!instruction_opt.has_value()) {
raise_exception(rv32i::EXCEPTION_INSTRUCTION_ACCESS_FAULT, pc);
return;
}
uint32_t instruction = instruction_opt.value();
size_t decode_cache_offset = (pc>>2) & decode_cache_addr_mask;
decode_t decode;
// if there is a cache invalidation, update the cache
decode = decode_cache[decode_cache_offset];
if (decode.instr != instruction) {
decode = decode_instruction(instruction);
decode_cache[decode_cache_offset] = decode;
}
// log the instruction
if (event_buffer!=nullptr) {
event_buffer->push_back({.type=event_type_t::issue, .pc=pc, .val1=instruction, .val2=0});
}
// execute the instuction
// decode.op() may change `next_pc`
next_pc = pc + 4;
if (decode.op != nullptr) {
decode.op(this, decode);
gpr[0] = 0;
} else {
raise_exception(rv32i::EXCEPTION_ILLEGAL_INSTRUCTION, instruction);
}
// log register writing
// memory events are logged by implements of memory accessing instructions
// traps are logged by handle_trap()
if (event_buffer!=nullptr && !exception_flag && decode.rd!=0) {
event_buffer->push_back({.type=event_type_t::reg_write, .pc=pc, .val1=decode.rd, .val2=gpr[decode.rd]});
}
// handle traps
next_pc = handle_trap();
pc = next_pc;
if (mmio_bus != nullptr) {
mmio_bus->next_cycle();
}
}

View File

@ -1,182 +0,0 @@
#include <libcpu/rv32i_cpu_system.hh>
#include <libcpu/rv32i.hh>
// In this file, arithmetic and control flow instructions are implemented.
using namespace libcpu;
using namespace libcpu::rv32i;
// ==================== Arithmetic & Logic ====================
void rv32i_cpu_system::add(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] + cpu->gpr[decode.rs2];
}
void rv32i_cpu_system::sub(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] - cpu->gpr[decode.rs2];
}
void rv32i_cpu_system::sll(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] << (cpu->gpr[decode.rs2] & 0x1f);
}
void rv32i_cpu_system::slt(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = ((int32_t)cpu->gpr[decode.rs1] < (int32_t)cpu->gpr[decode.rs2]) ? 1 : 0;
}
void rv32i_cpu_system::sltu(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = (cpu->gpr[decode.rs1] < cpu->gpr[decode.rs2]) ? 1 : 0;
}
void rv32i_cpu_system::xor_(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] ^ cpu->gpr[decode.rs2];
}
void rv32i_cpu_system::srl(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] >> (cpu->gpr[decode.rs2] & 0x1f);
}
void rv32i_cpu_system::sra(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = (int32_t)cpu->gpr[decode.rs1] >> (cpu->gpr[decode.rs2] & 0x1f);
}
void rv32i_cpu_system::or_(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] | cpu->gpr[decode.rs2];
}
void rv32i_cpu_system::and_(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] & cpu->gpr[decode.rs2];
}
// ==================== Immediate Operations ====================
void rv32i_cpu_system::addi(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] + decode.imm;
}
void rv32i_cpu_system::slti(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = ((int32_t)cpu->gpr[decode.rs1] < (int32_t)decode.imm) ? 1 : 0;
}
void rv32i_cpu_system::sltiu(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = (cpu->gpr[decode.rs1] < decode.imm) ? 1 : 0;
}
void rv32i_cpu_system::xori(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] ^ decode.imm;
}
void rv32i_cpu_system::ori(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] | decode.imm;
}
void rv32i_cpu_system::andi(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] & decode.imm;
}
void rv32i_cpu_system::slli(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] << (decode.imm & 0x1f);
}
void rv32i_cpu_system::srli(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] >> (decode.imm & 0x1f);
}
void rv32i_cpu_system::srai(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = (int32_t)cpu->gpr[decode.rs1] >> (decode.imm & 0x1f);
}
// ==================== Control Flow ====================
void rv32i_cpu_system::jal(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->pc + 4;
cpu->next_pc = cpu->pc + decode.imm;
}
void rv32i_cpu_system::jalr(rv32i_cpu_system* cpu, const decode_t& decode) {
uint32_t target = (cpu->gpr[decode.rs1] + decode.imm) & ~1;
cpu->gpr[decode.rd] = cpu->pc + 4;
cpu->next_pc = target;
}
void rv32i_cpu_system::beq(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->next_pc = (cpu->gpr[decode.rs1] == cpu->gpr[decode.rs2])
? cpu->pc + decode.imm
: cpu->pc + 4;
}
void rv32i_cpu_system::bne(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->next_pc = (cpu->gpr[decode.rs1] != cpu->gpr[decode.rs2])
? cpu->pc + decode.imm
: cpu->pc + 4;
}
void rv32i_cpu_system::blt(rv32i_cpu_system* cpu, const decode_t& decode) {
bool taken = (int32_t)cpu->gpr[decode.rs1] < (int32_t)cpu->gpr[decode.rs2];
cpu->next_pc = taken ? cpu->pc + decode.imm : cpu->pc + 4;
}
void rv32i_cpu_system::bge(rv32i_cpu_system* cpu, const decode_t& decode) {
bool taken = (int32_t)cpu->gpr[decode.rs1] >= (int32_t)cpu->gpr[decode.rs2];
cpu->next_pc = taken ? cpu->pc + decode.imm : cpu->pc + 4;
}
void rv32i_cpu_system::bltu(rv32i_cpu_system* cpu, const decode_t& decode) {
bool taken = cpu->gpr[decode.rs1] < cpu->gpr[decode.rs2];
cpu->next_pc = taken ? cpu->pc + decode.imm : cpu->pc + 4;
}
void rv32i_cpu_system::bgeu(rv32i_cpu_system* cpu, const decode_t& decode) {
bool taken = cpu->gpr[decode.rs1] >= cpu->gpr[decode.rs2];
cpu->next_pc = taken ? cpu->pc + decode.imm : cpu->pc + 4;
}
// ==================== Upper Immediate ====================
void rv32i_cpu_system::lui(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = decode.imm;
}
void rv32i_cpu_system::auipc(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->pc + decode.imm;
}
// ==================== Multiply/Divide ====================
void rv32i_cpu_system::mul(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->gpr[decode.rd] = cpu->gpr[decode.rs1] * cpu->gpr[decode.rs2];
}
void rv32i_cpu_system::mulh(rv32i_cpu_system* cpu, const decode_t& decode) {
int64_t result = (int64_t)(int32_t)cpu->gpr[decode.rs1] * (int64_t)(int32_t)cpu->gpr[decode.rs2];
cpu->gpr[decode.rd] = (result >> 32) & 0xffffffff;
}
void rv32i_cpu_system::mulhsu(rv32i_cpu_system* cpu, const decode_t& decode) {
int64_t result = (int64_t)(int32_t)cpu->gpr[decode.rs1] * (uint64_t)cpu->gpr[decode.rs2];
cpu->gpr[decode.rd] = (result >> 32) & 0xffffffff;
}
void rv32i_cpu_system::mulhu(rv32i_cpu_system* cpu, const decode_t& decode) {
uint64_t result = (uint64_t)cpu->gpr[decode.rs1] * (uint64_t)cpu->gpr[decode.rs2];
cpu->gpr[decode.rd] = (result >> 32) & 0xffffffff;
}
void rv32i_cpu_system::div(rv32i_cpu_system* cpu, const decode_t& decode) {
int32_t a = (int32_t)cpu->gpr[decode.rs1];
int32_t b = (int32_t)cpu->gpr[decode.rs2];
cpu->gpr[decode.rd] = (b == 0) ? -1 : (a / b);
}
void rv32i_cpu_system::divu(rv32i_cpu_system* cpu, const decode_t& decode) {
uint32_t a = cpu->gpr[decode.rs1];
uint32_t b = cpu->gpr[decode.rs2];
cpu->gpr[decode.rd] = (b == 0) ? 0xffffffff : (a / b);
}
void rv32i_cpu_system::rem(rv32i_cpu_system* cpu, const decode_t& decode) {
int32_t a = (int32_t)cpu->gpr[decode.rs1];
int32_t b = (int32_t)cpu->gpr[decode.rs2];
cpu->gpr[decode.rd] = (b == 0) ? a : (a % b);
}
void rv32i_cpu_system::remu(rv32i_cpu_system* cpu, const decode_t& decode) {
uint32_t a = cpu->gpr[decode.rs1];
uint32_t b = cpu->gpr[decode.rs2];
cpu->gpr[decode.rd] = (b == 0) ? a : (a % b);
}

View File

@ -1,96 +0,0 @@
#include <cstdint>
#include <libcpu/rv32i.hh>
#include <libcpu/rv32i_cpu_system.hh>
#include <libvio/frontend.hh>
#include <libvio/width.hh>
#include <optional>
// In this file, memory access instructions are implemented.
using namespace libvio;
using namespace libcpu;
using namespace libcpu::rv32i;
void rv32i_cpu_system::load(const decode_t &decode, width_t width, bool sign_extend) {
uint32_t addr = gpr[decode.rs1] + decode.imm;
auto data_opt = data_bus->read(addr, width);
// fall back to MMIO if the address is out of RAM
if (!data_opt.has_value() && mmio_bus!=nullptr) {
data_opt = mmio_bus->read(addr, width);
}
if (data_opt.has_value()) {
// `data` is zero extended
word_t data = data_opt.value();
// log the memory operation with *zero extended* data
if (event_buffer != nullptr) {
event_buffer->push_back({.type=event_type_t::load, .pc=pc, .val1=addr, .val2=data_opt.value()});
}
// sign extend the data
if (sign_extend) {
switch (width) {
case width_t::byte:
data = uint32_t(int32_t(int8_t(data)));
break;
case width_t::half:
data = uint32_t(int32_t(int16_t(data)));
break;
default:
break;
}
}
gpr[decode.rd] = data;
} else {
// both RAM and MMIO failed
raise_exception(EXCEPTION_LOAD_ACCESS_FAULT, addr);
}
}
void rv32i_cpu_system::store(const decode_t &decode, width_t width) {
word_t addr = gpr[decode.rs1] + decode.imm;
word_t data = zero_truncate<uint32_t>(gpr[decode.rs2], width);
bool success = data_bus->write(addr, width, data);
// fall back to MMIO
if (!success && mmio_bus!=nullptr) {
success = mmio_bus->write(addr, width, data);
}
if (success) {
if (event_buffer!=nullptr) {
event_buffer->push_back({.type=event_type_t::store, .pc=pc, .val1=addr, .val2=data});
}
} else {
// both RAM and MMIO failed
raise_exception(EXCEPTION_STORE_AMO_ACCESS_FAULT, addr);
}
}
void rv32i_cpu_system::lb(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->load(decode, width_t::byte, true);
}
void rv32i_cpu_system::lh(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->load(decode, width_t::half, true);
}
void rv32i_cpu_system::lw(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->load(decode, width_t::word, false);
}
void rv32i_cpu_system::lbu(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->load(decode, width_t::byte, false);
}
void rv32i_cpu_system::lhu(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->load(decode, width_t::half, false);
}
void rv32i_cpu_system::sb(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->store(decode, width_t::byte);
}
void rv32i_cpu_system::sh(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->store(decode, width_t::half);
}
void rv32i_cpu_system::sw(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->store(decode, width_t::word);
}

View File

@ -1,128 +0,0 @@
#include <cstdint>
#include <libcpu/rv32i.hh>
#include <libcpu/rv32i_cpu_system.hh>
// In this file, trap related instructions and functions are defined.
using namespace libcpu;
using namespace libcpu::rv32i;
void rv32i_cpu_system::ecall(rv32i_cpu_system* cpu, const decode_t& decode) {
switch (cpu->priv_level) {
case priv_level_t::m:
cpu->raise_exception(EXCEPTION_M_ECALL, 0);
break;
case priv_level_t::h:
cpu->raise_exception(EXCEPTION_M_ECALL, 0);
break;
case priv_level_t::s:
cpu->raise_exception(EXCEPTION_S_ECALL, 0);
break;
case priv_level_t::u:
cpu->raise_exception(EXCEPTION_U_ECALL, 0);
break;
}
}
void rv32i_cpu_system::ebreak(rv32i_cpu_system* cpu, const decode_t& decode) {
cpu->ebreak_flag = true;
}
void rv32i_cpu_system::mret(rv32i_cpu_system* cpu, const decode_t& decode) {
uint32_t mepc = cpu->csr_read(CSR_ADDR_MEPC);
if (cpu->event_buffer!=nullptr) {
uint32_t mstatus = cpu->csr_read(CSR_ADDR_MSTATUS);
cpu->event_buffer->push_back({.type=event_type_t::trap_ret, .pc=cpu->pc, .val1=mepc, .val2=mstatus});
}
// retore pc
cpu->next_pc = mepc;
cpu->priv_level = static_cast<priv_level_t>(cpu->csr_read_bits(CSR_ADDR_MSTATUS, MSTATUS_BIT_MPPH|MSTATUS_BIT_MPPL)>>11);
// restore MIE bit
uint32_t mie = cpu->csr_read_bits(CSR_ADDR_MSTATUS, MSTATUS_BIT_MPIE) >> 4;
cpu->csr_write_bits(CSR_ADDR_MSTATUS, mie, MSTATUS_BIT_MIE);
// restore the MPIE bit
cpu->csr_set_bits(CSR_ADDR_MSTATUS, MSTATUS_BIT_MPIE);
// retsore MPP bits
cpu->csr_clear_bits(CSR_ADDR_MSTATUS, MSTATUS_BIT_MPPH|MSTATUS_BIT_MPPL);
}
// returns the pc of the next instruction
uint32_t rv32i_cpu_system::handle_trap(void) {
// handle exceptions
if (exception_flag) {
exception_flag = false;
// set MPP and MPIE
csr_write_bits(CSR_ADDR_MSTATUS, static_cast<uint32_t>(priv_level)<<11, MSTATUS_BIT_MPPH|MSTATUS_BIT_MPPL);
uint32_t mpie = csr_read_bits(CSR_ADDR_MSTATUS, MSTATUS_BIT_MIE) << 4;
csr_write_bits(CSR_ADDR_MSTATUS, mpie, MSTATUS_BIT_MPIE);
// clear MIE
csr_clear_bits(CSR_ADDR_MSTATUS, MSTATUS_BIT_MIE);
// set other CSRs
csr_write(CSR_ADDR_MEPC, pc);
csr_write(CSR_ADDR_MCAUSE, exception_cause&(~MCAUSE_BIT_INTERRUPT));
csr_write(CSR_ADDR_MTVAL, exception_mtval);
// exception handler is never vectored
uint32_t mtvec = csr_read(CSR_ADDR_MTVEC);
uint32_t vector_base = mtvec & (~MTVEC_BIT_VECTORED);
if (event_buffer!=nullptr) {
event_buffer->push_back({.type=event_type_t::trap, .pc=pc, .val1=exception_cause, .val2=exception_mtval});
}
priv_level = priv_level_t::m;
next_trap = {csr_read(CSR_ADDR_MCAUSE)};
return vector_base;
}
// handle interrupts
uint32_t mip = csr[0]; // csr_read(CSR_ADDR_MIP) is slower
if (mip != 0) {
uint32_t mstatus = csr_read(CSR_ADDR_MSTATUS);
// if MIE=0 and the privilege mode is M, do not handle interrupts
if (priv_level==priv_level_t::m && !(mstatus&MSTATUS_BIT_MIE)) {
return next_pc;
}
uint32_t mie = csr_read(CSR_ADDR_MIE);
for (uint32_t cause=0; cause<n_interrupt; ++cause) {
if ((mip>>cause)&1 && (mie>>cause)&1) {
// set MPP and MPIE
csr_write_bits(CSR_ADDR_MSTATUS, static_cast<uint32_t>(priv_level)<<11, MSTATUS_BIT_MPPH|MSTATUS_BIT_MPPL);
uint32_t mpie = csr_read_bits(CSR_ADDR_MSTATUS, MSTATUS_BIT_MIE) << 4;
csr_write_bits(CSR_ADDR_MSTATUS, mpie, MSTATUS_BIT_MPIE);
// clear MIE
csr_clear_bits(CSR_ADDR_MSTATUS, MSTATUS_BIT_MIE);
// set other CSRs
csr_write(CSR_ADDR_MEPC, next_pc);
csr_write(CSR_ADDR_MCAUSE, cause|MCAUSE_BIT_INTERRUPT);
csr_write(CSR_ADDR_MTVAL, 0);
// jump to the interrupt handler
// interrupt handler can be vectored
uint32_t mtvec = csr_read(CSR_ADDR_MTVEC);
uint32_t vector_base = mtvec & (~MTVEC_BIT_VECTORED);
uint32_t is_vectord = mtvec & MTVEC_BIT_VECTORED;
if (event_buffer!=nullptr) {
event_buffer->push_back({.type=event_type_t::trap, .pc=next_pc, .val1=cause|MCAUSE_BIT_INTERRUPT, .val2=0});
}
priv_level = priv_level_t::m;
next_trap = {csr_read(CSR_ADDR_MCAUSE)};
if (is_vectord) {
return vector_base + cause*4;
} else {
return vector_base;
}
}
}
}
next_trap = {};
return next_pc;
}
void rv32i_cpu_system::raise_exception(mcause_t mcause_code, uint32_t mtval) {
exception_flag = true;
exception_cause = mcause_code;
exception_mtval = mtval;
}
void rv32i_cpu_system::raise_interrupt(mcause_t mcause_code) {
if (mcause_code < n_interrupt) {
csr_set_bits(CSR_ADDR_MIP, 1<<mcause_code);
}
}

329
src/libsdb/gdb_server.cc Normal file
View File

@ -0,0 +1,329 @@
// This file heavily references https://github.com/annestrand/aca/blob/main/aca_gdbstub.h (MIT License).
// Licensed under Mulan PSL 2.0; see LICENSE file.
#include <libsdb/gdb_server.hh>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <string>
#ifdef __unix__
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <unistd.h>
#endif
namespace libsdb {
gdb_server_base::gdb_server_base()
: server_fd(-1), client_fd(-1), signal_on_entry(false) {}
gdb_server_base::~gdb_server_base() {
close_client();
close_server();
}
bool gdb_server_base::create_server(int port) {
#ifdef __unix__
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd < 0) {
libanemo::log_info("gdb_server", "Failed to create socket\n");
return false;
}
int opt = 1;
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
libanemo::log_info("gdb_server", "setsockopt failed\n");
close(server_fd);
server_fd = -1;
return false;
}
sockaddr_in addr{};
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = INADDR_ANY;
addr.sin_port = htons(port);
if (bind(server_fd, reinterpret_cast<sockaddr*>(&addr), sizeof(addr)) < 0) {
libanemo::log_info("gdb_server", "Failed to bind to port %d\n", port);
close(server_fd);
server_fd = -1;
return false;
}
if (listen(server_fd, 1) < 0) {
libanemo::log_info("gdb_server", "listen failed\n");
close(server_fd);
server_fd = -1;
return false;
}
return true;
#else
(void)port;
return false;
#endif
}
bool gdb_server_base::accept_client() {
#ifdef __unix__
if (server_fd < 0) {
return false;
}
sockaddr_in client_addr{};
socklen_t client_len = sizeof(client_addr);
client_fd = accept(server_fd, reinterpret_cast<sockaddr*>(&client_addr), &client_len);
if (client_fd < 0) {
libanemo::log_info("gdb_server", "accept failed\n");
return false;
}
libanemo::log_info("gdb_server", "GDB connected from %s:%d\n",
inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
return true;
#else
return false;
#endif
}
void gdb_server_base::close_client() {
#ifdef __unix__
if (client_fd >= 0) {
close(client_fd);
client_fd = -1;
}
#endif
}
void gdb_server_base::close_server() {
#ifdef __unix__
if (server_fd >= 0) {
close(server_fd);
server_fd = -1;
}
#endif
}
void gdb_server_base::compute_checksum(const char* data, size_t len, char out[3]) {
unsigned int sum = 0;
for (size_t i = 0; i < len; ++i) {
sum = (sum + static_cast<unsigned char>(data[i])) % 256;
}
std::snprintf(out, 3, "%02x", sum);
}
void gdb_server_base::hex_encode_byte(uint8_t val, char out[2]) {
static const char hex_digits[] = "0123456789abcdef";
out[0] = hex_digits[val >> 4];
out[1] = hex_digits[val & 0xf];
}
static void send_raw(int fd, const char* data, size_t len) {
#ifdef __unix__
if (fd < 0) {
return;
}
size_t sent = 0;
while (sent < len) {
ssize_t n = send(fd, data + sent, len - sent, 0);
if (n <= 0) {
return;
}
sent += static_cast<size_t>(n);
}
#else
(void)fd;
(void)data;
(void)len;
#endif
}
static bool recv_char(int fd, char* c) {
#ifdef __unix__
if (fd < 0) {
return false;
}
ssize_t n = recv(fd, c, 1, 0);
return n == 1;
#else
(void)fd;
(void)c;
return false;
#endif
}
void gdb_server_base::send_packet(const char* data) {
char checksum[3];
compute_checksum(data, std::strlen(data), checksum);
std::string out = "$";
out += data;
out += "#";
out += checksum[0];
out += checksum[1];
libanemo::log_trace("gdb_server", "GDB <--- %s\n", out.c_str());
send_raw(client_fd, out.c_str(), out.size());
}
std::string gdb_server_base::recv_packet() {
while (true) {
char c;
if (!recv_char(client_fd, &c)) {
libanemo::log_info("gdb_server", "Client closed connection\n");
return "";
}
if (c != '$') {
continue;
}
std::string data;
while (true) {
if (!recv_char(client_fd, &c)) {
return "";
}
if (c == '#') {
break;
}
data.push_back(c);
}
char expected[3];
if (!recv_char(client_fd, &expected[0])) {
return "";
}
if (!recv_char(client_fd, &expected[1])) {
return "";
}
expected[2] = 0;
char actual[3];
compute_checksum(data.c_str(), data.size(), actual);
if (std::strcmp(expected, actual) != 0) {
send_raw(client_fd, "-", 1);
continue;
}
send_raw(client_fd, "+", 1);
libanemo::log_trace("gdb_server", "GDB ---> $%s#%s\n", data.c_str(), expected);
return data;
}
}
void gdb_server_base::send_signal() {
char buf[16];
std::snprintf(buf, sizeof(buf), "S%02x", 5);
send_packet(buf);
}
void gdb_server_base::send_hex_data(const std::vector<uint8_t>& data) {
std::string hex;
hex.reserve(data.size() * 2);
for (uint8_t b : data) {
char out[2];
hex_encode_byte(b, out);
hex.push_back(out[0]);
hex.push_back(out[1]);
}
send_packet(hex.c_str());
}
gdb_server_base::action_t gdb_server_base::process() {
if (signal_on_entry) {
send_signal();
signal_on_entry = false;
}
while (true) {
std::string pkt = recv_packet();
if (pkt.empty()) {
return action_t::kill;
}
char cmd = pkt[0];
switch (cmd) {
case 'g': {
auto regs = read_all_regs();
send_hex_data(regs);
break;
}
case 'G': {
send_packet("E00");
break;
}
case 'p': {
size_t index = std::strtoull(pkt.c_str() + 1, nullptr, 16);
auto reg = read_reg(index);
if (reg.empty()) {
send_packet("E00");
} else {
send_hex_data(reg);
}
break;
}
case 'P': {
send_packet("E00");
break;
}
case 'm': {
size_t comma = pkt.find(',');
if (comma == std::string::npos || comma == 1) {
send_packet("E00");
break;
}
size_t addr = std::strtoull(pkt.c_str() + 1, nullptr, 16);
size_t len = std::strtoull(pkt.c_str() + comma + 1, nullptr, 16);
auto mem = read_mem(addr, len);
send_hex_data(mem);
break;
}
case 'M': {
send_packet("E00");
break;
}
case 'c': {
return action_t::continue_;
}
case 's': {
return action_t::step;
}
case 'Z': {
process_breakpoint(pkt, true);
break;
}
case 'z': {
process_breakpoint(pkt, false);
break;
}
case 'k': {
on_kill();
return action_t::kill;
}
case '?': {
send_signal();
break;
}
default: {
send_packet("");
break;
}
}
}
}
void gdb_server_base::process_breakpoint(const std::string& pkt, bool set) {
size_t comma1 = pkt.find(',');
size_t comma2 = pkt.find(',', comma1 + 1);
if (comma1 == std::string::npos || comma2 == std::string::npos) {
send_packet("E00");
return;
}
size_t addr = std::strtoull(pkt.c_str() + comma1 + 1, nullptr, 16);
if (set) {
set_breakpoint(addr);
} else {
clear_breakpoint(addr);
}
send_packet("OK");
}
} // namespace libsdb

View File

@ -0,0 +1,31 @@
#include <libvio/backend/ioflow_iostream.hh>
#include <iostream>
#include <cstdint>
namespace libvio {
uint64_t ioflow_backend_iostream::reg_read(uint64_t reg_num) {
return 0;
}
void ioflow_backend_iostream::reg_write(uint64_t reg_num, uint64_t data) {
return;
}
bool ioflow_backend_iostream::iflow_valid(uint64_t iflow_num) const {
return !std::cin.eof();
}
uint64_t ioflow_backend_iostream::iflow_read(uint64_t iflow_num) {
return std::cin.get();
}
bool ioflow_backend_iostream::oflow_ready(uint64_t oflow_num) const {
return true;
}
void ioflow_backend_iostream::oflow_write(uint64_t oflow_num, uint64_t data) {
std::cout.put(data);
}
}

View File

@ -0,0 +1,84 @@
#include <libvio/backend/ioflow_pty.hh>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <iostream>
#include <cstdint>
namespace libvio {
ioflow_backend_pty::ioflow_backend_pty() : fd_(-1) {
fd_ = posix_openpt(O_RDWR | O_NOCTTY);
if (fd_ < 0) {
std::cerr << "failed to open PTY\n";
return;
}
if (grantpt(fd_) < 0 || unlockpt(fd_) < 0) {
std::cerr << "failed to setup PTY\n";
close(fd_);
fd_ = -1;
return;
}
char* pts = ptsname(fd_);
if (pts) {
pty_path_ = pts;
std::cout << "virtual serial port available on " << pty_path_ << "\n";
}
}
ioflow_backend_pty::~ioflow_backend_pty() {
if (fd_ >= 0) {
close(fd_);
}
}
uint64_t ioflow_backend_pty::reg_read(uint64_t reg_num) {
return 0;
}
void ioflow_backend_pty::reg_write(uint64_t reg_num, uint64_t data) {
return;
}
bool ioflow_backend_pty::iflow_valid(uint64_t iflow_num) const {
if (fd_ < 0) {
return false;
}
int available = 0;
if (ioctl(fd_, FIONREAD, &available) < 0) {
return false;
}
return available > 0;
}
uint64_t ioflow_backend_pty::iflow_read(uint64_t iflow_num) {
if (fd_ < 0) {
return 0;
}
uint8_t ch = 0;
if (read(fd_, &ch, 1) == 1) {
return ch;
}
return 0;
}
bool ioflow_backend_pty::oflow_ready(uint64_t oflow_num) const {
return fd_ >= 0;
}
void ioflow_backend_pty::oflow_write(uint64_t oflow_num, uint64_t data) {
if (fd_ < 0) {
return;
}
uint8_t ch = static_cast<uint8_t>(data);
write(fd_, &ch, 1);
}
}

View File

@ -0,0 +1,47 @@
#include <cstdint>
#include <libvio/backend.hh>
#include <libvio/backend/mtime_cycles.hh>
namespace libvio {
uint64_t mtime_backend_cycles::reg_read(uint64_t reg_num) {
switch (reg_num) {
case regs::mtime:
return cycle_count + mtime_offset;
case regs::mtimecmp:
return mtimecmp;
default:
return 0;
}
}
void mtime_backend_cycles::reg_write(uint64_t reg_num, uint64_t data) {
switch (reg_num) {
case regs::mtime:
mtime_offset = data - cycle_count;
break;
case regs::mtimecmp:
mtimecmp = data;
break;
default:
break;
}
}
bool mtime_backend_cycles::iflow_valid(uint64_t iflow_num) const {
return false;
}
uint64_t mtime_backend_cycles::iflow_read(uint64_t iflow_num) {
return 0;
}
bool mtime_backend_cycles::oflow_ready(uint64_t oflow_num) const {
return false;
}
void mtime_backend_cycles::oflow_write(uint64_t oflow_num, uint64_t data) {
return;
}
}

View File

@ -20,7 +20,7 @@ io_dispatcher::io_dispatcher(std::initializer_list<io_device> device_list, size_
}
}
std::optional<uint64_t> io_dispatcher::request_read(uint64_t addr, width_t width, size_t req_no) {
std::optional<uint64_t> io_dispatcher::request_read(uint64_t addr, libanemo::width_t width, size_t req_no) {
if (req_no < read_request_buffer.firstindex()) {
std::cerr << "libvio: Read buffer underflow." << std::endl;
return {};
@ -45,6 +45,7 @@ std::optional<uint64_t> io_dispatcher::request_read(uint64_t addr, width_t width
std::optional<uint64_t> req_data = {};
for (auto &dev: devices) {
if (addr>=dev.addr_begin && addr<dev.addr_begin+dev.byte_span) {
dev.backend->cycle_count = cycle_count;
req_data = dev.frontend->read(addr-dev.addr_begin, width);
break;
}
@ -57,7 +58,7 @@ std::optional<uint64_t> io_dispatcher::request_read(uint64_t addr, width_t width
}
}
bool io_dispatcher::request_write(uint64_t addr, width_t width, size_t req_no, uint64_t data) {
bool io_dispatcher::request_write(uint64_t addr, libanemo::width_t width, size_t req_no, uint64_t data) {
if (req_no < write_request_buffer.firstindex()) {
std::cerr << "libvio: Write buffer underflow." << std::endl;
return {};
@ -80,6 +81,7 @@ bool io_dispatcher::request_write(uint64_t addr, width_t width, size_t req_no, u
bool result = false;
for (auto &dev: devices) {
if (addr>=dev.addr_begin && addr<dev.addr_begin+dev.byte_span) {
dev.backend->cycle_count = cycle_count;
result = dev.frontend->write(addr-dev.addr_begin, width, data);
break;
}
@ -98,7 +100,7 @@ mmio_agent *io_dispatcher::new_agent(void) {
return agents.back().get();
}
std::optional<uint64_t> mmio_agent::read(uint64_t addr, width_t width) {
std::optional<uint64_t> mmio_agent::read(uint64_t addr, libanemo::width_t width) {
// check if there are already read requests in this cycle
const auto &buffer = dispatcher->read_request_buffer;
for (size_t i=old_read_count; i<read_count; ++i) {
@ -119,7 +121,7 @@ std::optional<uint64_t> mmio_agent::read(uint64_t addr, width_t width) {
return dispatcher->request_read(addr, width, read_count++);
}
bool mmio_agent::write(uint64_t addr, width_t width, uint64_t data) {
bool mmio_agent::write(uint64_t addr, libanemo::width_t width, uint64_t data) {
// check if there are already write requests in this cycle
const auto &buffer = dispatcher->write_request_buffer;
for (size_t i=old_write_count; i<write_count; ++i) {
@ -143,6 +145,7 @@ bool mmio_agent::write(uint64_t addr, width_t width, uint64_t data) {
void mmio_agent::next_cycle(void) {
old_read_count = read_count;
old_write_count = write_count;
dispatcher->cycle_count += 1;
}
}

View File

@ -1,49 +0,0 @@
#include <cstdint>
#include <libvio/console.hh>
namespace libvio {
console_backend_iostream::console_backend_iostream(std::istream &is, std::ostream &os): istream(is), ostream(os) {}
uint64_t console_backend_iostream::request(uint64_t req) {
if (req != reqval::console_rx) {
return 0;
}
if (input_data.has_value()) {
uint64_t data = input_data.value();
input_data = {};
return data;
} else {
return istream.get();
}
}
bool console_backend_iostream::poll(uint64_t req) {
if (req != reqval::console_rx) {
return true;
}
if (input_data.has_value()) {
return true;
} else {
input_data = istream.get();
return true;
}
}
bool console_backend_iostream::check(uint64_t req) {
if (req != reqval::console_rx) {
return true;
}
return input_data.has_value();
}
void console_backend_iostream::put(uint64_t req, uint64_t data) {
if (req == reqval::console_tx) {
ostream << static_cast<char>(data);
}
}
}

View File

@ -1,45 +0,0 @@
#include <cstdint>
#include <libvio/frontend.hh>
#include <libvio/console.hh>
namespace libvio {
ioreq_t console_frontend::resolve_read(uint64_t offset, width_t width) const {
if (offset==0 && width==width_t::byte) {
// receiving
return {ioreq_type_t::read, reqval::console_rx};
} else if (offset==1 && width==width_t::byte) {
// querying device state
// bit 0: output ready
// bit 1: input valid
return {ioreq_type_t::ioctl_get, reqval::console_rx|reqval::console_tx};
} else {
return {ioreq_type_t::invalid, 0};
}
}
ioreq_t console_frontend::resolve_write(size_t offset, width_t width, uint64_t data) const {
if (offset==0 && width==width_t::byte) {
// sending
return {ioreq_type_t::write, reqval::console_tx};
} else if (offset==2 && width==width_t::half) {
// trying to set prescaler
// ignore this
return {ioreq_type_t::ioctl_set, reqval::console_prescaler};
} else {
return {ioreq_type_t::invalid, 0};;
}
}
uint64_t console_frontend::ioctl_get(uint64_t req) {
// tx is always ready when using software emulated console
uint64_t tx_ready = 1;
uint64_t rx_valid = backend->poll(reqval::console_rx);
return (rx_valid<<1) | (tx_ready<<0);
}
void console_frontend::ioctl_set(uint64_t req, uint64_t data) {
return;
}
}

View File

@ -1,65 +0,0 @@
#include <cstdint>
#include <libvio/backend.hh>
#include <libvio/frontend.hh>
#include <libvio/width.hh>
#include <optional>
#include <iostream>
#include <ostream>
namespace libvio {
std::optional<uint64_t> io_frontend::read(uint64_t offset, width_t width) {
std::optional<uint64_t> read_data;
auto req = resolve_read(offset, width);
switch (req.type) {
case ioreq_type_t::read:
read_data = backend->request(req.req);
break;
case ioreq_type_t::poll_in:
read_data = backend->poll(req.req);
break;
case ioreq_type_t::poll_out:
read_data = 1;
break;
case ioreq_type_t::ioctl_get:
read_data = ioctl_get(req.req);
break;
case ioreq_type_t::ioctl_set:
case ioreq_type_t::write:
std::cerr << "MMIO read resolved as write request types." << std::endl;
case ioreq_type_t::invalid:
read_data = {};
break;
}
// make sure the higher bits are set to zero
if (read_data.has_value()) {
read_data = zero_truncate<uint64_t>(read_data.value(), width);
}
return read_data;
}
bool io_frontend::write(uint64_t offset, width_t width, uint64_t data) {
auto req = resolve_write(offset, width, data);
write_data = data;
switch (req.type) {
case ioreq_type_t::write:
backend->put(req.req, data);
write_result = true;
break;
case ioreq_type_t::ioctl_set:
ioctl_set(req.req, data);
write_result = true;
break;
case ioreq_type_t::read:
case ioreq_type_t::poll_in:
case ioreq_type_t::poll_out:
case ioreq_type_t::ioctl_get:
std::cerr << "MMIO write resolved as read request types." << std::endl;
case ioreq_type_t::invalid:
write_result = false;
break;
}
return write_result;
}
}

View File

@ -0,0 +1,73 @@
#include <cstdint>
#include <ios>
#include <iostream>
#include <libanemo/width.hh>
#include <optional>
#include <libvio/frontend.hh>
#include <libvio/backend.hh>
#include <libvio/frontend/clint.hh>
namespace libvio {
std::optional<uint64_t> clint::read(uint64_t offset, libanemo::width_t width) {
if (!aligned(offset, width)) {
return std::nullopt;
}
if (offset < 0x0004) {
// msip
return libanemo::partial_read(offset, width, sip);
} else if (offset < 0x4000) {
// reserved
std::cerr << "libvio: warning: reading at reserved offset " << std::hex << offset << std::endl;
return 0;
} else if (offset < 0x4008) {
// mtimecmp
uint64_t data = backend->reg_read(regs::mtimecmp);
return libanemo::partial_read(offset-8, width, data);
} else if (offset < 0xbff8) {
// reserved
std::cerr << "libvio: warning: reading at reserved offset " << std::hex << offset << std::endl;
return 0;
} else if (offset < 0xc000) {
// mtime
uint64_t data = backend->reg_read(regs::mtime);
return libanemo::partial_read(offset, width, data);
} else {
return std::nullopt;
}
}
bool clint::write(uint64_t offset, libanemo::width_t width, uint64_t data) {
if (!aligned(offset, width)) {
return false;
}
if (offset < 0x0004) {
// msip
if (offset==0) {
sip = data&1;
}
return true;
} else if (offset < 0x4000) {
// reserved
std::cerr << "libvio: warning: writing at reserved offset " << std::hex << offset << std::endl;
return true;
} else if (offset < 0x4008) {
// mtimecmp
uint64_t old_data = backend->reg_read(regs::mtimecmp);
backend->reg_write(regs::mtimecmp, libanemo::partial_write(offset-0x4000, width, old_data, data));
return true;
} else if (offset < 0xbff8) {
// reserved
std::cerr << "libvio: warning: writing at reserved offset " << std::hex << offset << std::endl;
return true;
} else if (offset < 0xc000) {
// mtime
uint64_t old_data = backend->reg_read(regs::mtime);
backend->reg_write(regs::mtime, libanemo::partial_write(offset-0xbff8, width, old_data, data));
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,42 @@
#include <cstdint>
#include <libanemo/width.hh>
#include <optional>
#include <libvio/frontend.hh>
#include <libvio/backend.hh>
#include <libvio/frontend/mtime.hh>
namespace libvio {
std::optional<uint64_t> mtime::read(uint64_t offset, libanemo::width_t width) {
if (!aligned(offset, width)) {
return std::nullopt;
}
if (offset < 8) {
uint64_t data = backend->reg_read(regs::mtime);
return libanemo::partial_read(offset, width, data);
} else if (offset < 16) {
uint64_t data = backend->reg_read(regs::mtimecmp);
return libanemo::partial_read(offset-8, width, data);
} else {
return std::nullopt;
}
}
bool mtime::write(uint64_t offset, libanemo::width_t width, uint64_t data) {
if (!aligned(offset, width)) {
return false;
}
if (offset < 8) {
uint64_t old_data = backend->reg_read(regs::mtime);
backend->reg_write(regs::mtime, libanemo::partial_write(offset, width, old_data, data));
return true;
} else if (offset < 16) {
uint64_t old_data = backend->reg_read(regs::mtimecmp);
backend->reg_write(regs::mtimecmp, libanemo::partial_write(offset-8, width, old_data, data));
return true;
} else {
return false;
}
}
}

View File

@ -0,0 +1,91 @@
#include <cstdint>
#include <libanemo/width.hh>
#include <libvio/backend.hh>
#include <libvio/frontend/uart16550.hh>
#include <optional>
namespace libvio {
std::optional<uint64_t> uart16550::read(uint64_t offset, libanemo::width_t width) {
// UART 16650 uses 8 bit registers
// enforce 8 bit access here
if (width != libanemo::width_t::byte) {
return std::nullopt;
}
switch (offset) {
case 0:
if (dlab) { // divisor latch low
return divisor & 0xff;
} else { // receiver buffer
return backend->iflow_read(iflows::rx);
}
case 1:
if (dlab) { // divisor latch high
return divisor << 8;
} else { // interrupt enable register
return (tx_irq_enabled<<1) | rx_irq_enabled;
}
case 2:
// TODO: support interrupt reason
return 0;
case 3: // line control register
return (dlab<<7) | (lcr&0x7f);
case 4: // modem control register
return (mcr&0xe3) | (irq_enabled<<3);
case 5: // line status register
return (backend->oflow_ready(oflows::tx)?0x60:0x00) | backend->iflow_valid(iflows::rx);
case 6: // modem status register
return 0;
case 7: // scratcher register
return scratch;
default:
return std::nullopt;
}
}
bool uart16550::write(uint64_t offset, libanemo::width_t width, uint64_t data) {
// UART 16650 uses 8 bit registers
// enforce 8 bit access here
if (width != libanemo::width_t::byte) {
return false;
}
switch (offset) {
case 0:
if (dlab) { // divisor latch low
divisor = data & 0xff;
} else { // tranmit buffer
backend->oflow_write(oflows::tx, data);
}
return true;
case 1:
if (dlab) { // divisor latch high
divisor = (data<<8) & 0xff;
} else { // interrupt enable register
tx_irq_enabled = data & 0x02;
rx_irq_enabled = data & 0x01;
}
return true;
case 2: // FIFO control register
return true;
case 3: // line control register
dlab = data & 0x40;
lcr = data & 0x7f;
return true;
case 4: // modem control register
mcr = data & 0xe3;
irq_enabled = data & 0x08;
return true;
case 5: // line status register
return true;
case 6: // modem status register
return true;
case 7: // scratcher register
scratch = data & 0xff;
return true;
default:
return false;
}
}
}

View File

@ -0,0 +1,63 @@
#include <cstdint>
#include <libanemo/width.hh>
#include <optional>
#include <libvio/frontend.hh>
#include <libvio/backend.hh>
#include <libvio/frontend/uartlite.hh>
namespace libvio {
std::optional<uint64_t> uartlite::read(uint64_t offset, libanemo::width_t width) {
if (!aligned(offset, width) || width==libanemo::width_t::dword) {
return std::nullopt;
}
if (offset < 4) {
// RX FIFO
uint64_t data = backend->iflow_read(iflows::rx);
data = libanemo::zero_truncate(data, libanemo::width_t::byte);
return libanemo::partial_read(offset, width, data);
} else if (offset < 8) {
// TX FIFO is write only
return 0;
} else if (offset < 12) {
// status reguster
bool tx_ready = backend->oflow_ready(oflows::tx);
uint32_t tx_fifo_full = !tx_ready;
uint32_t tx_fifo_empty = tx_ready;
uint32_t rx_fifo_full = 0; // virtual hardware is never "fifo full"
uint32_t rx_fifo_valid = backend->iflow_valid(iflows::rx);;
return uint32_t(intr_enabled)<<4 | tx_fifo_full<<3 | tx_fifo_empty<<2 | rx_fifo_full<<1 | rx_fifo_valid;
} else if (offset < 16) {
// control register is write only
return 0;
} else {
return std::nullopt;
}
}
bool uartlite::write(uint64_t offset, libanemo::width_t width, uint64_t data) {
if (!aligned(offset, width) || width==libanemo::width_t::dword) {
return false;
}
if (offset < 4) {
// RX FIFO is read only
return true;
} else if (offset < 8) {
// TX FIFO
if (offset == 4) {
backend->oflow_write(oflows::tx, libanemo::zero_truncate(data, libanemo::width_t::byte));
}
return true;
} else if (offset < 12) {
// status reguster is read only
return true;
} else if (offset < 16) {
// control register
intr_enabled = (data>>4) & 1;
return true;
} else {
return false;
}
}
}

View File

@ -1,73 +0,0 @@
#include <cstdint>
#include <libvio/mtime.hh>
#include <libvio/backend.hh>
#include <chrono>
namespace libvio {
mtime_backend_chrono::mtime_backend_chrono(void) {
mtime_offset = std::chrono::high_resolution_clock::now();
}
uint64_t mtime_backend_chrono::request(uint64_t req) {
if (req == reqval::mtimecmp_l) {
return mtimecmp & 0x00000000ffffffff;
}
if (req == reqval::mtimecmp_h) {
return mtimecmp >> 32;
}
if (req == (reqval::mtimecmp_h|reqval::mtimecmp_l)) {
return mtimecmp;
}
// get current time
auto now = std::chrono::high_resolution_clock::now();
auto duration = now - mtime_offset;
uint64_t microseconds = std::chrono::duration_cast<std::chrono::microseconds>(duration).count();
if (req == reqval::mtime_l) {
return microseconds & 0x00000000ffffffff;
}
if (req == reqval::mtime_h) {
return microseconds >> 32;
}
if (req == (reqval::mtime_h|reqval::mtime_l)) {
return microseconds;
}
return 0;
}
void mtime_backend_chrono::put(uint64_t req, uint64_t data) {
if (req == reqval::mtimecmp_l) {
mtimecmp = (mtimecmp & 0xffffffff00000000) | (data & 0x00000000ffffffff);
return;
}
if (req == reqval::mtimecmp_h) {
mtimecmp = (data << 32) | (mtimecmp & 0x00000000ffffffff);
return;
}
if (req == (reqval::mtimecmp_h|reqval::mtimecmp_l)) {
mtimecmp = data;
return;
}
// update mtime offset when trying to write to mtime
// so that if mtime is read just afterwards
// it will return the value written
auto now = std::chrono::high_resolution_clock::now();
uint64_t new_mtime = 0;
if (req&reqval::mtime_l) {
new_mtime |= data & 0x00000000ffffffff;
}
if (req&reqval::mtime_h) {
new_mtime |= uint64_t(data) << 32;
}
mtime_offset = now - std::chrono::microseconds{new_mtime};
}
bool libvio::mtime_backend_chrono::poll(uint64_t req) {
return true;
}
bool libvio::mtime_backend_chrono::check(uint64_t req) {
return true;
}
}

View File

@ -1,71 +0,0 @@
#include <cstdint>
#include <libvio/mtime.hh>
#include <libvio/frontend.hh>
namespace libvio {
ioreq_t mtime_frontend::resolve_read(size_t offset, width_t width) const {
if (width == width_t::dword) {
if (offset == 0) {
// reading mtime
return {ioreq_type_t::read, reqval::mtime_h|reqval::mtime_l};
} else if (offset == 8) {
// reading mtimecmp
return {ioreq_type_t::read, reqval::mtimecmp_h|reqval::mtimecmp_l};;
} else {
return {ioreq_type_t::invalid, 0};
}
} else if (width == width_t::word) {
if (offset == 0) {
return {ioreq_type_t::read, reqval::mtime_l};
} else if (offset == 4) {
return {ioreq_type_t::read, reqval::mtime_h};
} else if (offset == 8) {
return {ioreq_type_t::read, reqval::mtimecmp_l};
} else if (offset == 12) {
return {ioreq_type_t::read, reqval::mtimecmp_l};
} else {
return {ioreq_type_t::invalid, 0};
}
} else {
return {ioreq_type_t::invalid, 0};
}
}
ioreq_t mtime_frontend::resolve_write(size_t offset, width_t width, uint64_t data) const {
if (width == width_t::dword) {
if (offset == 0) {
// mtime
return {ioreq_type_t::write, reqval::mtime_h|reqval::mtime_l};
} else if (offset == 8) {
// mtimecmp
return {ioreq_type_t::write, reqval::mtimecmp_h|reqval::mtimecmp_l};;
} else {
return {ioreq_type_t::invalid, 0};
}
} else if (width == width_t::word) {
if (offset == 0) {
return {ioreq_type_t::write, reqval::mtime_l};
} else if (offset == 4) {
return {ioreq_type_t::write, reqval::mtime_h};
} else if (offset == 8) {
return {ioreq_type_t::write, reqval::mtimecmp_l};
} else if (offset == 12) {
return {ioreq_type_t::write, reqval::mtimecmp_l};
} else {
return {ioreq_type_t::invalid, 0};
}
} else {
return {ioreq_type_t::invalid, 0};
}
}
uint64_t mtime_frontend::ioctl_get(uint64_t req) {
return 0;
}
void mtime_frontend::ioctl_set(uint64_t req, uint64_t data) {
return;
}
}

View File

@ -1,41 +0,0 @@
#include <cstdint>
#include <iostream>
#include <libcpu/abstract_cpu.hh>
#include <libcpu/rv32i_cpu_system.hh>
#include <libsdb/sdb.hh>
#include <libvio/bus.hh>
#include <libvio/console.hh>
#include <libvio/mtime.hh>
#include <string>
int main(int argc, char** argv) {
if (argc != 2) {
std::cerr << "Usage: nemu-minimal <binary_file>\n";
return 1;
}
libcpu::rv32i_cpu_system cpu;
libcpu::contiguous_memory<uint32_t> memory{0x80000000, 128*1024*1024};
memory.load_elf_from_file(argv[1]);
cpu.instr_bus = &memory;
cpu.data_bus = &memory;
libvio::io_dispatcher bus{{
{new libvio::console_frontend{}, new libvio::console_backend_iostream{std::cin, std::cout}, 0xa00003f8, 8},
{new libvio::mtime_frontend{}, new libvio::mtime_backend_chrono{}, 0xa0000048, 16}
}};
cpu.mmio_bus = bus.new_agent();
cpu.reset(0x80000000);
libsdb::sdb<uint32_t> sdb {};
sdb.cpu = &cpu;
while (!sdb.stopped()) {
std::cout << "sdb> ";
std::string cmd;
std::getline(std::cin, cmd);
sdb.execute_command(cmd);
}
sdb.execute_command("status");
return cpu.get_gpr(10);
}