Compare commits

..

16 Commits

Author SHA1 Message Date
efbf73f6b5 lrw/scw reservation 2023-12-20 10:43:17 +01:00
9dd57071ce Updated README 2023-11-07 13:16:00 +01:00
c8fbd9f4da Added LICENSE 2023-11-07 12:29:46 +01:00
3e110a82f4 Added MULH test 2023-11-03 17:19:00 +01:00
de202e25b9 Updated README 2023-11-03 16:36:21 +01:00
e9cc295470 Fixed tests using sbi shutdown 2023-11-03 16:35:08 +01:00
b5cf188c0a Makefile: Changed clean rule, fixed dep 2023-11-03 16:14:03 +01:00
923e9d39a0 Better exception/interrupt handle 2023-11-03 11:25:58 +01:00
0983be511c Comments on makefile 2023-11-03 11:22:28 +01:00
c423e6a2aa Makefile can now build linux/bbl 2023-11-02 18:54:57 +01:00
d6af840ed1 CSR: sie=mie, sip=mip 2023-10-24 00:36:39 +02:00
6330104873 CSR: SSTATUS=MSTATUS 2023-10-24 00:22:33 +02:00
2d33e50074 Define CSR STATUS bits, std functions on exception 2023-10-24 00:19:10 +02:00
cf8a1de199 gdbstub on bp, unreachable, previous privilege S 2023-10-23 17:54:49 +02:00
b57739fe38 Hardened MMU permission checks 2023-10-23 17:52:21 +02:00
07f683dc41 Hardened memory bounds check 2023-10-23 17:52:09 +02:00
26 changed files with 2488 additions and 95 deletions

9
LICENSE Normal file
View File

@@ -0,0 +1,9 @@
MIT License
Copyright 2023 Valentin HAUDIQUET
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@@ -4,11 +4,14 @@ CFLAGS=-O3 -Wall -I src
LDFLAGS=-lpthread
BUILD_DIR=build
# Risc-V toolchain
RV_LINUX_CCPREFIX=riscv32-unknown-linux-gnu-
C_FILES := $(shell find src/ -name '*.c')
all: $(BUILD_DIR)/$(NAME)
# Top-level targets
# Top-level target : vriscv
$(BUILD_DIR)/$(NAME): $(C_FILES) | $(BUILD_DIR)
$(CC) $(CFLAGS) -o $@ $^ $(LDFLAGS)
@@ -16,16 +19,51 @@ $(BUILD_DIR)/$(NAME): $(C_FILES) | $(BUILD_DIR)
$(BUILD_DIR):
mkdir -p $(BUILD_DIR)
# Phony targets
# Clean : clean built executable
.PHONY: clean
clean:
rm -rf $(BUILD_DIR)/$(NAME)
# Distclean : clean build directory
.PHONY: distclean
distclean:
rm -rf $(BUILD_DIR)
rungdb: all
echo $(shell objdump -h ../riscv-pk/build/bbl | grep .payload | awk '{print $4}')
# Linux and bootloader, for running linux
$(BUILD_DIR)/linux:
cd $(BUILD_DIR) && git clone https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git --depth 1 -b v6.6
$(BUILD_DIR)/linux/.config: hardware/linux.config | $(BUILD_DIR)/linux
cp hardware/linux.config $(BUILD_DIR)/linux/.config
$(BUILD_DIR)/linux/vmlinux: $(BUILD_DIR)/linux/.config | $(BUILD_DIR)/linux
cd $(BUILD_DIR)/linux/ && make ARCH=riscv CROSS_COMPILE=$(RV_LINUX_CCPREFIX) -j7 vmlinux
$(BUILD_DIR)/riscv-pk:
cd $(BUILD_DIR) && git clone https://github.com/riscv-software-src/riscv-pk --depth 1
$(BUILD_DIR)/riscv-pk/build: | $(BUILD_DIR)/riscv-pk
mkdir $(BUILD_DIR)/riscv-pk/build
$(BUILD_DIR)/riscv-pk/build/bbl: $(BUILD_DIR)/linux/vmlinux hardware/vriscv.dts | $(BUILD_DIR)/riscv-pk/build
cd $(BUILD_DIR)/riscv-pk/build && ../configure \
--prefix=$(CURDIR)/$(BUILD_DIR)/riscv-pk/build/prefix \
--host=riscv32-unknown-elf \
--with-arch=rv32ima_zicsr_zifencei --with-abi=ilp32 \
--with-dts=../../../hardware/vriscv.dts \
--with-payload=../../linux/vmlinux
cd $(BUILD_DIR)/riscv-pk/build && make && make install
# Run : run linux on the emulator
.PHONY: run
.SILENT: run
run: all
./$(BUILD_DIR)/$(NAME)
run: all $(BUILD_DIR)/riscv-pk/build/bbl
./$(BUILD_DIR)/$(NAME) -m4096 $(BUILD_DIR)/riscv-pk/build/bbl
# Test : all the tests
.PHONY: tests
.SILENT: tests
tests: all

View File

@@ -1,5 +1,10 @@
# vriscv - a risc-v simulator
Linux and the BBL bootloader can be downloaded, built, and ran on the simulator using:
```
make run
```
## Unit tests
Unit tests can be compiled and run using :
@@ -22,6 +27,3 @@ Juraj's Blog, mostly:
RISC-V SBI Specifications:
- https://github.com/riscv-non-isa/riscv-sbi-doc/releases
Buildroot fork for nommu linux:
- https://github.com/regymm/buildroot

2038
hardware/linux.config Normal file

File diff suppressed because it is too large Load Diff

74
hardware/vriscv.dts Normal file
View File

@@ -0,0 +1,74 @@
/dts-v1/;
/ {
#address-cells = <1>;
#size-cells = <1>;
compatible = "riscv-virtio";
model = "riscv-virtio,qemu";
chosen {
bootargs = "debug keep_bootcon earlycon=sbi console=sbi";
stdout-path = "/uart0@3000000";
};
cpus {
#address-cells = <1>;
#size-cells = <0>;
timebase-frequency = <10000000>;
cpu0: cpu@0 {
device_type = "cpu";
reg = <0>;
compatible = "riscv";
riscv,isa = "riscv,sv32";
clock-frequency = <10000000>;
cpu0_intc: interrupt-controller {
#interrupt-cells = <1>;
compatible = "riscv,cpu-intc";
interrupt-controller;
};
};
};
ram: memory@0 {
device_type = "memory";
reg = <0x0 0xFFFFFFFF>;
};
soc {
#address-cells = <1>;
#size-cells = <1>;
compatible = "simple-bus";
ranges;
clint0: clint@2000000 {
#interrupt-cells = <1>;
compatible = "riscv,clint0";
reg = <0x2000000 0xC000>;
interrupts-extended = <&cpu0_intc 3 &cpu0_intc 7>;
};
// /* FIXME: This is probably not correct for now */
plic0: interrupt-controller@c000000 {
#interrupt-cells = <1>;
interrupt-controller;
compatible = "riscv,plic0";
reg = <0xC000000 0x4000000>;
interrupts-extended = <&cpu0_intc 9>, <&cpu0_intc 11>;
riscv,ndev = <1>;
riscv,max-priority = <7>;
};
// uart0: serial@3000000 {
// interrupts = <0xa>;
// interrupt-parent = <&plic0>;
// clock-frequency = <0x384000>;
// reg = <0x3000000 0x1>;
// compatible = "simple-uart";
// };
};
uart0: serial@3000000 {
clock-frequency = <0x384000>;
reg = <0x3000000 0x1>;
compatible = "sifive,uart0";
};
};

View File

@@ -7,7 +7,12 @@ uint32_t csr_read(struct RV32_CPU* cpu, uint32_t csr)
{
case CSR_CYCLE:
return cpu->sim_ticks_done;
break;
case CSR_SSTATUS:
return csr_read(cpu, CSR_MSTATUS);
case CSR_SIE:
return csr_read(cpu, CSR_MIE);
case CSR_SIP:
return csr_read(cpu, CSR_MIP);
default:
break;
}
@@ -17,5 +22,19 @@ uint32_t csr_read(struct RV32_CPU* cpu, uint32_t csr)
void csr_write(struct RV32_CPU* cpu, uint32_t csr, uint32_t value)
{
switch(csr)
{
case CSR_SSTATUS:
csr_write(cpu, CSR_MSTATUS, value);
return;
case CSR_SIE:
csr_write(cpu, CSR_MIE, value);
return;
case CSR_SIP:
csr_write(cpu, CSR_MIP, value);
return;
default:
break;
}
cpu->csr[csr] = value;
}

View File

@@ -61,4 +61,40 @@
/* Machine Memory Protection */
#define CSR_PMPCFG0 0x3A0
// CSR STATUS
// SIE: Supervisor Interrupt Enable
#define STATUS_SIE 0x2
// MIE: Machine Interrupt Enable
#define STATUS_MIE 0x8
// SPIE: Supervisor Previous Interrupt Enable
#define STATUS_SPIE 0x20
// MPIE: Machine Previous Interrupt Enable
#define STATUS_MPIE 0x80
// UBE : User Big Endian (always 0 for us, we are little endian)
#define STATUS_UBE 0x40
// SPP : Supervisor Previous Privilege
#define STATUS_SPP 0x100
// VS (2bits) : for extensions
#define STATUS_VS 0x600
// MPP (2bits) : Machine Previous Privilege
#define STATUS_MPP 0x1800
// FS (2bits) : for extensions
#define STATUS_FS 0x6000
// XS (2bits) : for extensions
#define STATUS_XS 0x18000
// MPRV : Modify PRiVilege
#define STATUS_MPRV 0x20000
// SUM : permit Supervisor User Memory access
#define STATUS_SUM 0x40000
// MXR : Make eXecutable Readable
#define STATUS_MXR 0x80000
// TVM : Trap Virtual Memory (virtualization support)
#define STATUS_TVM 0x100000
// TW : Timeout Wait (virtualization support)
#define STATUS_TW 0x200000
// TSR : Trap SRET (virtualization support)
#define STATUS_TSR 0x400000
// SD : for extensions
#define STATUS_SD 0x80000000
#endif

View File

@@ -1,8 +1,9 @@
#include "exception.h"
#include "vriscv.h"
#include <stdio.h>
void exception_trigger(rv32_cpu_t* cpu, uint32_t scause, uint32_t tval)
__attribute__((noreturn)) void exception_trigger(rv32_cpu_t* cpu, uint32_t scause, uint32_t tval)
{
// An exception can only be triggered by the CPU itself,
// so we know we already own the mutex
@@ -14,36 +15,55 @@ void exception_trigger(rv32_cpu_t* cpu, uint32_t scause, uint32_t tval)
// Exceptions cannot be disabled
// Set xCAUSE : exception cause, with interrupt bit set to null
cpu->csr[CSR_SCAUSE] = scause;
// Save previous interrupt enable in xSTATUS.xPIE
if(cpu->csr[CSR_SSTATUS] & 0b10U)
cpu->csr[CSR_SSTATUS] |= 0x80;
// Set previous privilege mode in xSTATUS.xPP
// TODO
// TODO : Check medeleg to see if we should handle exception in S mode or M mode
// Unset SIE (interrupt enable) bit
cpu->csr[CSR_SSTATUS] &= ~0b10U;
csr_write(cpu, CSR_SSTATUS, csr_read(cpu, CSR_SSTATUS) & (~STATUS_SIE));
// Set xCAUSE : exception cause, with interrupt bit set to null
csr_write(cpu, CSR_SCAUSE, scause);
if(gdbstub && scause == SCAUSE_BREAKPOINT)
{
cpu->sim_ticks_left = 0;
// No simulation ticks left : wakeup people waiting on sim end
pthread_cond_signal(&cpu->sim_condition);
// Then, wait for simulation state to change until we get more ticks to simulate
while(!cpu->sim_ticks_left)
pthread_cond_wait(&cpu->sim_condition, &cpu->mutex);
}
// Save previous interrupt enable in xSTATUS.xPIE
if(csr_read(cpu, CSR_SSTATUS) & STATUS_SIE)
csr_write(cpu, CSR_SSTATUS, csr_read(cpu, CSR_SSTATUS) | STATUS_SPIE);
else
csr_write(cpu, CSR_SSTATUS, csr_read(cpu, CSR_SSTATUS) & (~STATUS_SPIE));
// Set previous privilege mode in xSTATUS.xPP
if(cpu->privilege_mode == SUPERVISOR)
csr_write(cpu, CSR_SSTATUS, csr_read(cpu, CSR_SSTATUS) | STATUS_SPP);
else if(cpu->privilege_mode == USER)
csr_write(cpu, CSR_SSTATUS, csr_read(cpu, CSR_SSTATUS) & (~STATUS_SPP));
// Set privilege mode for exception handling, checking for delegation
// TODO
// TODO
// Set xTVAL, exception-specific information related to xCAUSE
cpu->csr[CSR_STVAL] = tval;
csr_write(cpu, CSR_STVAL, tval);
// Set SEPC to instruction that caused exception
cpu->csr[CSR_SEPC] = cpu->pc;
csr_write(cpu, CSR_SEPC, cpu->pc);
// Set PC to xTVEC : exception handling code
// xTVEC: [Base(30bits) Mode(2 bits)], address 4-byte aligned in base
// Exceptions are not vectored (we can safely ignore mode)
cpu->pc = cpu->csr[CSR_STVEC] & 0xFFFFFFFC;
cpu->pc = csr_read(cpu, CSR_STVEC) & 0xFFFFFFFC;
// Unlock cpu mutex, cpu_loop will lock it just after
// Unlock cpu mutex, cpu_loop will lock it just after
pthread_mutex_unlock(&cpu->mutex);
// cpu loop (attribute noreturn should erase previous stack)
// TODO : Hard reset the stack pointer
// cpu loop
cpu_loop(cpu);
__builtin_unreachable();
}

View File

@@ -3,7 +3,7 @@
#include "rv32cpu.h"
void exception_trigger(rv32_cpu_t* cpu, uint32_t scause, uint32_t tval);
__attribute__((noreturn)) void exception_trigger(rv32_cpu_t* cpu, uint32_t scause, uint32_t tval);
#define SCAUSE_INSTRUCTION_MISSALIGNED 0x0
#define SCAUSE_INSTRUCTION_ACCESS_FAULT 0x1

View File

@@ -4,28 +4,38 @@
#include <stdio.h>
#include <unistd.h>
uint32_t interrupt_mi_from_scause(uint32_t scause)
{
switch(scause)
{
case SCAUSE_SUPERVISOR_TIMER_INTERRUPT:
return 0x20;
default:
fprintf(stderr, "interrupt_mie_bit_from_scause: wrong scause 0x%x\n", scause);
exit(EXIT_FAILURE);
}
return 0;
}
void interrupt_trigger(rv32_cpu_t* cpu, uint32_t scause)
{
// Make sure that interrupts are enabled
if(cpu->privilege_mode == MACHINE)
// Make sure that interrupts are enabled globally
if(!(csr_read(cpu, CSR_MSTATUS) & STATUS_SIE))
return;
// Make sure that current interrupt is enabled
if(!(csr_read(cpu, CSR_MIE) & interrupt_mi_from_scause(scause)))
{
// In machine mode, we check mstatus.sie (bit 1)
if(!(cpu->csr[CSR_MSTATUS] & 0b10))
return;
}
else if(cpu->privilege_mode == SUPERVISOR)
{
// In supervisor mode, we check sstatus.sie (bit 1)
if(!(cpu->csr[CSR_SSTATUS] & 0b10))
return;
}
else
{
// TODO
fprintf(stderr, "interrupt_trigger in non M/S-mode not implemented yet\n");
exit(EXIT_FAILURE);
// Mark interrupt as pending
csr_write(cpu, CSR_MIP, csr_read(cpu, CSR_MIP) | interrupt_mi_from_scause(scause));
return;
}
// TODO : CHECK mideleg to see wether we should handle interrupt is S mode or M mode
// An interrupt can only be triggered from outside
// of the cpu, so we are on a different thread
// and we don't already own the CPU mutex
@@ -39,12 +49,14 @@ void interrupt_trigger(rv32_cpu_t* cpu, uint32_t scause)
cpu->csr[CSR_SCAUSE] = 0x80000000 | scause;
// Set xSTATUS.xPIE (previous interrupt enable) bit
cpu->csr[CSR_SSTATUS] |= 0x80;
cpu->csr[CSR_MSTATUS] |= STATUS_SPIE;
// Set xSTATUS.xPP (Previous Privilege) bit
// TODO : Allow user mode interrupts (by not setting this)
cpu->csr[CSR_MSTATUS] |= 0x100;
// Unset xSTATUS.xIE (interrupt enable) bit
cpu->csr[CSR_SSTATUS] &= ~0b10U;
cpu->csr[CSR_MSTATUS] &= (~STATUS_SIE);
// Set xEPC : PC at interruption
cpu->csr[CSR_SEPC] = cpu->pc;

View File

@@ -529,18 +529,22 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
case IMM_SRET:
// SRET: Return from supervisor interrupt
// Restore Interrupt Enable from Previous Interrupt Enable
uint32_t sstatus = csr_read(cpu, CSR_SSTATUS);
if(sstatus & 0x80)
csr_write(cpu, CSR_SSTATUS, sstatus | 0b10);
if(csr_read(cpu, CSR_SSTATUS) & STATUS_SPIE)
csr_write(cpu, CSR_SSTATUS, csr_read(cpu, CSR_SSTATUS) | STATUS_SIE);
// Restore privilege mode from Previous Privilege
// TODO
if(!(csr_read(cpu, CSR_SSTATUS) & STATUS_SPP))
{
// Previous Privilege was 0, return to user mode
fprintf(stderr, "SRET to user mode : not implemented yet\n");
exit(EXIT_FAILURE);
}
// Set Previous Interrupt Enable to 1
csr_write(cpu, CSR_SSTATUS, csr_read(cpu, CSR_SSTATUS) | 0x80);
csr_write(cpu, CSR_SSTATUS, csr_read(cpu, CSR_SSTATUS) | STATUS_SPIE);
// Set Previous Privilege to 0
// TODO
csr_write(cpu, CSR_SSTATUS, csr_read(cpu, CSR_SSTATUS) & (~STATUS_SPP));
// Saved PC before interrupt is in CSR SEPC, jump back
cpu->pc = csr_read(cpu, CSR_SEPC) - 4;
@@ -632,13 +636,19 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
case FUNC75_LRW:
// Load-Reserved Word
cpu->regs.x[instruction->rd] = mem_read32(address);
// TODO register reservation set that subsumes the bytes in word
cpu->load_reservation = address;
break;
case FUNC75_SCW:
// Store-Conditional Word
// TODO succeed only if the reservation is still valid and the reservation set contains the bytes written
mem_write32(address, cpu->regs.x[instruction->rs2]);
cpu->regs.x[instruction->rd] = 0; // TODO write 1 in rd on failure
if(cpu->load_reservation == address)
{
mem_write32(address, cpu->regs.x[instruction->rs2]);
cpu->regs.x[instruction->rd] = 0; // Write 0 in rd on success
}
else
{
cpu->regs.x[instruction->rd] = 1; // Write 1 in rd on failure
}
break;
case FUNC75_AMOSWAPW:
// Atomic Memory Operation SWAP Word
@@ -759,6 +769,8 @@ __attribute__((noreturn)) void cpu_loop(rv32_cpu_t* cpu)
// Let go of cpu mutex
pthread_mutex_unlock(&cpu->mutex);
}
__builtin_unreachable();
}
static void cpu_print_instruction(instruction_t* instruction)

View File

@@ -200,6 +200,7 @@ typedef struct RV32_CPU
uint32_t csr[CSR_COUNT];
rvcpu_privilege_mode_t privilege_mode;
uint32_t load_reservation;
// Simulation data
ssize_t sim_ticks_left; // -1 : simulate forever

View File

@@ -75,7 +75,7 @@ void sbi_call(rv32_cpu_t* cpu, uint32_t extension_id, uint32_t func_id)
case SBI_EXTENSION_LEGACY_SHUTDOWN:
{
printf("sbi_call: shutdown, goodbye !\n");
cpu->sim_ticks_left = 1;
exit(cpu->regs.a0);
break;
}
default:

View File

@@ -50,7 +50,7 @@ void mem_write8(uint32_t address, uint8_t value)
{
if(io->reg_size == 1)
{
void (*fn_write)(uint32_t, uint8_t) = io->fn_write;
void (*fn_write)(uint32_t, uint8_t) = io->fn_write;
fn_write(address, value);
return;
}
@@ -63,6 +63,13 @@ void mem_write8(uint32_t address, uint8_t value)
io = io->next;
}
// Check if we are inside of physical memory
if(address + 1 > memory_size)
{
fprintf(stderr, "MEMORY: Invalid write of size 1 outside of physical memory at address 0x%x\n", address);
exit(EXIT_FAILURE);
}
// Proceed with memory write
pthread_mutex_lock(&memory_mutex);
memory[address] = value;
@@ -81,7 +88,7 @@ void mem_write16(uint32_t address, uint16_t value)
{
if(io->reg_size == 2)
{
void (*fn_write)(uint32_t, uint16_t) = io->fn_write;
void (*fn_write)(uint32_t, uint16_t) = io->fn_write;
fn_write(address, value);
return;
}
@@ -94,6 +101,13 @@ void mem_write16(uint32_t address, uint16_t value)
io = io->next;
}
// Check if we are inside of physical memory
if(address + 2 > memory_size)
{
fprintf(stderr, "MEMORY: Invalid write of size 2 outside of physical memory at address 0x%x\n", address);
exit(EXIT_FAILURE);
}
// Proceed with memory write
pthread_mutex_lock(&memory_mutex);
*((uint16_t*) &memory[address]) = value;
@@ -112,7 +126,7 @@ void mem_write32(uint32_t address, uint32_t value)
{
if(io->reg_size == 4)
{
void (*fn_write)(uint32_t, uint32_t) = io->fn_write;
void (*fn_write)(uint32_t, uint32_t) = io->fn_write;
fn_write(address, value);
return;
}
@@ -125,6 +139,13 @@ void mem_write32(uint32_t address, uint32_t value)
io = io->next;
}
// Check if we are inside of physical memory
if(address + 4 > memory_size)
{
fprintf(stderr, "MEMORY: Invalid write of size 1 outside of physical memory at address 0x%x\n", address);
exit(EXIT_FAILURE);
}
// Proceed with memory write
pthread_mutex_lock(&memory_mutex);
*((uint32_t*) &memory[address]) = value;
@@ -135,7 +156,7 @@ uint8_t mem_read8(uint32_t address)
{
address = mmu_resolve(cpu0, READ, address);
// Look wether we are on an MMIO region
// Look wether we are on an MMIO region
struct MMIO_ENTRY* io = mmio;
while(io)
{
@@ -143,7 +164,7 @@ uint8_t mem_read8(uint32_t address)
{
if(io->reg_size == 1)
{
uint8_t (*fn_read)(uint32_t) = io->fn_read;
uint8_t (*fn_read)(uint32_t) = io->fn_read;
return fn_read(address);
}
else
@@ -155,18 +176,25 @@ uint8_t mem_read8(uint32_t address)
io = io->next;
}
// Check if we are inside of physical memory
if(address + 1 > memory_size)
{
fprintf(stderr, "MEMORY: Invalid read of size 1 outside of physical memory at address 0x%x\n", address);
exit(EXIT_FAILURE);
}
// Proceed with memory read
pthread_mutex_lock(&memory_mutex);
uint8_t tr = memory[address];
pthread_mutex_unlock(&memory_mutex);
return tr;
return tr;
}
uint16_t mem_read16(uint32_t address)
{
address = mmu_resolve(cpu0, READ, address);
// Look wether we are on an MMIO region
// Look wether we are on an MMIO region
struct MMIO_ENTRY* io = mmio;
while(io)
{
@@ -174,7 +202,7 @@ uint16_t mem_read16(uint32_t address)
{
if(io->reg_size == 2)
{
uint16_t (*fn_read)(uint32_t) = io->fn_read;
uint16_t (*fn_read)(uint32_t) = io->fn_read;
return fn_read(address);
}
else
@@ -186,18 +214,25 @@ uint16_t mem_read16(uint32_t address)
io = io->next;
}
// Check if we are inside of physical memory
if(address + 2 > memory_size)
{
fprintf(stderr, "MEMORY: Invalid read of size 2 outside of physical memory at address 0x%x\n", address);
exit(EXIT_FAILURE);
}
// Proceed with memory read
pthread_mutex_lock(&memory_mutex);
uint16_t tr = *((uint16_t*) &memory[address]);
pthread_mutex_unlock(&memory_mutex);
return tr;
return tr;
}
uint32_t mem_read32(uint32_t address)
{
address = mmu_resolve(cpu0, READ, address);
// Look wether we are on an MMIO region
// Look wether we are on an MMIO region
struct MMIO_ENTRY* io = mmio;
while(io)
{
@@ -205,7 +240,7 @@ uint32_t mem_read32(uint32_t address)
{
if(io->reg_size == 4)
{
uint32_t (*fn_read)(uint32_t) = io->fn_read;
uint32_t (*fn_read)(uint32_t) = io->fn_read;
return fn_read(address);
}
else
@@ -217,18 +252,25 @@ uint32_t mem_read32(uint32_t address)
io = io->next;
}
// Check if we are inside of physical memory
if(address + 4 > memory_size)
{
fprintf(stderr, "MEMORY: Invalid read of size 4 outside of physical memory at address 0x%x\n", address);
exit(EXIT_FAILURE);
}
// Proceed with memory read
pthread_mutex_lock(&memory_mutex);
uint32_t tr = *((uint32_t*) &memory[address]);
pthread_mutex_unlock(&memory_mutex);
return tr;
return tr;
}
uint32_t mem_fetch(uint32_t address)
{
address = mmu_resolve(cpu0, INSTRUCTION_FETCH, address);
// Look wether we are on an MMIO region
// Look wether we are on an MMIO region
struct MMIO_ENTRY* io = mmio;
while(io)
{
@@ -240,9 +282,16 @@ uint32_t mem_fetch(uint32_t address)
io = io->next;
}
// Check if we are inside of physical memory
if(address + 4 > memory_size)
{
fprintf(stderr, "MEMORY: Invalid fetch outside of physical memory at address 0x%x\n", address);
exit(EXIT_FAILURE);
}
// Proceed with memory read
pthread_mutex_lock(&memory_mutex);
uint32_t tr = *((uint32_t*) &memory[address]);
pthread_mutex_unlock(&memory_mutex);
return tr;
return tr;
}

View File

@@ -89,6 +89,18 @@ uint32_t mmu_resolve(rv32_cpu_t* cpu, memory_access_type_t access_type, uint32_t
// Leaf PTE, we are ready to resolve the mapping
// This is a 4 MiB megapage
// For an execute, check if we are allowed to execute
if(access_type == INSTRUCTION_FETCH && !(pte & PTE_X))
exception_trigger(cpu, mmu_scause_from_access(access_type), vaddr);
// For a write, check if we are allowed to write
if(access_type == WRITE && !(pte & PTE_W))
exception_trigger(cpu, mmu_scause_from_access(access_type), vaddr);
// For a read, check if we are allowed to read
if(access_type == READ && !(pte & PTE_R))
exception_trigger(cpu, mmu_scause_from_access(access_type), vaddr);
// Physical Address: [PPN[1] = pte.PPN[1], PPN[0] = vaddr.VPN[0], offset]
uint32_t paddr = 0;
paddr |= (PTE_PPN_1(pte) << 22);
@@ -120,6 +132,18 @@ uint32_t mmu_resolve(rv32_cpu_t* cpu, memory_access_type_t access_type, uint32_t
exit(EXIT_FAILURE);
}
// For an execute, check if we are allowed to execute
if(access_type == INSTRUCTION_FETCH && !(pte & PTE_X))
exception_trigger(cpu, mmu_scause_from_access(access_type), vaddr);
// For a write, check if we are allowed to write
if(access_type == WRITE && !(pte & PTE_W))
exception_trigger(cpu, mmu_scause_from_access(access_type), vaddr);
// For a read, check if we are allowed to read
if(access_type == READ && !(pte & PTE_R))
exception_trigger(cpu, mmu_scause_from_access(access_type), vaddr);
// Physical Address: [PPN[1] = pte.PPN[1], PPN[0] = pte.PPN[0], offset]
uint32_t paddr = 0;
paddr |= (PTE_PPN_1(pte) << 22);

View File

@@ -2,7 +2,7 @@ AS=riscv32-elf-as
LD=riscv32-elf-ld
BUILD_DIR=../build/tests/
S_FILES := $(shell find ./ -name '*.s')
S_FILES := $(shell find ./ -name '*.s' -not -name 'exit_return.s')
NAMES = $(basename $(S_FILES))
OBJECTS=$(patsubst %, $(BUILD_DIR)/%, $(NAMES))

View File

@@ -1,4 +1,6 @@
.include "exit_return.s"
.global _start
_start:
addi a0, zero, 0xBA
ebreak
exret

View File

@@ -1,3 +1,5 @@
.include "exit_return.s"
.global _start
_start:
# Set base value of a0 to 'test failed'
@@ -14,18 +16,18 @@ _start:
beq t0, t1, eq1
# On failure, return
ebreak
exret
eqNeg:
# All passed
addi a0, zero, 0
ebreak
exret
eq0:
# Inequality failed
ebreak
exret
eq1:
# Equality passed ; now try to test a negative offset case
beq t0, t1, eqNeg
ebreak
exret

View File

@@ -1,3 +1,5 @@
.include "exit_return.s"
.global _start
_start:
# Set base value of a0 to 'test failed'
@@ -12,20 +14,20 @@ _start:
blt t0, t1, lt1
# On failure, return
ebreak
exret
ltNeg:
# All passed
addi a0, zero, 0
ebreak
exret
lt0:
# Inequality failed
ebreak
exret
lt1:
# Inequality passed ; now try with negative numbers
addi t0, zero, -1
addi t1, zero, -2
blt t1, t0, ltNeg
ebreak
exret

4
tests/exit_return.s Normal file
View File

@@ -0,0 +1,4 @@
.macro exret
addi a7, zero, 0x8
ecall
.endm

View File

@@ -1,3 +1,5 @@
.include "exit_return.s"
.global _start
_start:
# Set base value of a0 to 'test failed'
@@ -8,17 +10,17 @@ _start:
addi t0, t0, 12
# Jump and link
jal ra, fn0
ebreak
exret
fnNeg:
# All good
addi a0, zero, 0
ebreak
exret
fn0:
# Check ra value with our t0 construct
beq t0, ra, eq0
ebreak
exret
eq0:
# Try to jump back to a negative offset

View File

@@ -1,3 +1,5 @@
.include "exit_return.s"
.global _start
_start:
# Set base value of a0 to 'test failed'
@@ -19,13 +21,13 @@ _start:
# Jump far to test jalr with negative offset
jal fnfar
ebreak
exret
# just_after : address is 16 bytes after auipc
just_after:
# ra must still be the old address
ret
ebreak
exret
# fn0 : function that calls fn1 and returns
fn0:
@@ -42,18 +44,18 @@ fn0:
addi sp, sp, 4
ret
ebreak
exret
# fn1 : just return
fn1:
ret
ebreak
exret
fnneg:
addi a0, zero, 0
ebreak
exret
fnfar:
auipc ra, 0
jalr -8(ra)
ebreak
exret

40
tests/mulh.s Normal file
View File

@@ -0,0 +1,40 @@
.include "exit_return.s"
.global _start
_start:
# Set base value of a0 to 'test failed'
addi a0, zero, 1
# Multiply 2 * 3 in t0, high bits
addi t0, zero, 3
addi t1, zero, 2
mulh t0, t0, t1
addi t1, zero, 0
beq t0, t1, mulh_low_ok
exret
mulh_low_ok:
# Multiply 2<<29 * 8 in t0, high bits
# Result high should be 2
addi t1, zero, 2
slli t0, t1, 29
addi t1, zero, 8
mulh t0, t0, t1
addi t1, zero, 2
beq t0, t1, mulh_high_ok
exret
mulh_high_ok:
# Multiply 2 << 29 * -8 in t0, high bits
# Result high should be 0xFFFF_FFFE ie -2 signed ?
addi t1, zero, 2
slli t0, t1, 29
addi t1, zero, -8
mulh t0, t0, t1
addi t1, zero, -2
beq t0, t1, mulh_neg_ok
exret
mulh_neg_ok:
addi a0, zero, 0
exret

View File

@@ -1,5 +1,7 @@
.include "exit_return.s"
.global _start
_start:
addi a1, zero, 0xBA
mv a0, a1
ebreak
exret

View File

@@ -1,3 +1,5 @@
.include "exit_return.s"
.global _start
_start:
# Set base value of a0 to 'test failed'
@@ -16,12 +18,12 @@ _start:
# Compare
beq t0, t1, good
ebreak
exret
good:
beq t0, t2, xtragood
ebreak
exret
xtragood:
addi a0, zero, 0
ebreak
exret

View File

@@ -29,3 +29,4 @@ test "BLT : Branch Less Than " "../build/tests/blt " 0
test "JAL : Jump And Link " "../build/tests/jal " 0
test "SWLW : Store Word Load Word " "../build/tests/swlw " 0
test "JALR : Jump And Link Register " "../build/tests/jalr " 0
test "MULH : MULtply High " "../build/tests/mulh " 0