forked from foobat/libanemo
Compare commits
31 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
c29f51b6e2 | |
|
|
342549bd5c | |
|
|
dcca687395 | |
|
|
b851d99598 | |
|
|
9d4bc1e79f | |
|
|
2b1b429bd6 | |
|
|
8549c5b373 | |
|
|
6b423fab0c | |
|
|
bd51e32487 | |
|
|
0ff4753ab0 | |
|
|
a336b2d975 | |
|
|
734f3f16e9 | |
|
|
25280346db | |
|
|
04ef9a2276 | |
|
|
1894331c5b | |
|
|
594b4cb101 | |
|
|
4771a17e11 | |
|
|
4878dba4d5 | |
|
|
521794ce51 | |
|
|
723232645c | |
|
|
4921bef5dc | |
|
|
ba585249ab | |
|
|
69edff820a | |
|
|
69ae644920 | |
|
|
4a742b5420 | |
|
|
daf4a0588e | |
|
|
22805f671d | |
|
|
55ae688a79 | |
|
|
30ac32dd8c | |
|
|
36f73c4cbe | |
|
|
b596e5df5c |
|
|
@ -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)
|
||||
|
|
|
|||
14
README.md
14
README.md
|
|
@ -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;
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
```
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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(), ®_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(®_buffer[i * reg_width], &gpr[i], reg_width);
|
||||
}
|
||||
WORD_T pc = cpu->get_pc();
|
||||
std::memcpy(®_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
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,4 @@
|
|||
#ifndef LIBVIO_BACKEND_IOFLOW_FILES_HH
|
||||
#define LIBVIO_BACKEND_IOFLOW_FILES_HH
|
||||
|
||||
#endif
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
};
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
||||
41
src/main.cc
41
src/main.cc
|
|
@ -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);
|
||||
}
|
||||
Loading…
Reference in New Issue