Compare commits
54 Commits
72dda9aaeb
...
master
Author | SHA1 | Date | |
---|---|---|---|
efbf73f6b5 | |||
9dd57071ce | |||
c8fbd9f4da | |||
3e110a82f4 | |||
de202e25b9 | |||
e9cc295470 | |||
b5cf188c0a | |||
923e9d39a0 | |||
0983be511c | |||
c423e6a2aa | |||
d6af840ed1 | |||
6330104873 | |||
2d33e50074 | |||
cf8a1de199 | |||
b57739fe38 | |||
07f683dc41 | |||
71f3fbc8b5 | |||
02114ea7d8 | |||
a0935f0aad | |||
326b52ef86 | |||
256a56f70e | |||
608dbba6a0 | |||
b3f915dcb5 | |||
082d2dcd4f | |||
bdc091aab2 | |||
dcdebcd8e4 | |||
5bb973e8da | |||
9da9b5045f | |||
3f6657fe00 | |||
ce89df1ed4 | |||
5727356559 | |||
b1a327cccf | |||
a76c9e5e5c | |||
6c5e3fcc32 | |||
c404defb42 | |||
43d22052e5 | |||
c286beb1e1 | |||
9742c89270 | |||
7fbfae1081 | |||
58b4bdb1e6 | |||
d7e684ad91 | |||
31941c0813 | |||
c421595a95 | |||
9e895e9032 | |||
13f7f21b49 | |||
948ff6493b | |||
12c05b4879 | |||
66c89d8630 | |||
a62148255e | |||
1c1b6a7ac0 | |||
7f3efb092c | |||
44a917b398 | |||
c3fa088a38 | |||
e7b521c589 |
9
LICENSE
Normal file
9
LICENSE
Normal 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.
|
46
Makefile
46
Makefile
@@ -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
|
||||
|
15
README.md
15
README.md
@@ -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 :
|
||||
@@ -9,12 +14,16 @@ make tests
|
||||
|
||||
## Resources used
|
||||
|
||||
RISC-V Specifications:
|
||||
- https://five-embeddev.com/riscv-isa-manual/latest/instr-table.html (instruction table)
|
||||
- https://five-embeddev.com/riscv-isa-manual/latest/priv-instr-table.html (privileged instructions)
|
||||
|
||||
Device Tree Source :
|
||||
- https://elinux.org/Device_Tree_Usage
|
||||
|
||||
Juraj's Blog, mostly:
|
||||
- https://jborza.com/post/2021-04-04-riscv-supervisor-mode/
|
||||
- https://jborza.com/emulation/2021/04/22/ecalls-and-syscalls.html
|
||||
|
||||
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
2038
hardware/linux.config
Normal file
File diff suppressed because it is too large
Load Diff
74
hardware/vriscv.dts
Normal file
74
hardware/vriscv.dts
Normal 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";
|
||||
};
|
||||
};
|
@@ -8,94 +8,98 @@
|
||||
|
||||
uint32_t elf_32_load(void* file)
|
||||
{
|
||||
// Parse/verify the header
|
||||
elf_header_32_t* header = file;
|
||||
// Parse/verify the header
|
||||
elf_header_32_t* header = file;
|
||||
|
||||
// Verify magic number
|
||||
uint8_t* m = header->ELF;
|
||||
if((m[0] != 0x7F) || (m[1] != 'E') || (m[2] != 'L') || (m[3] != 'F'))
|
||||
{
|
||||
fprintf(stderr, "Not a valid ELF file (wrong magic)\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
// Verify magic number
|
||||
uint8_t* m = header->ELF;
|
||||
if((m[0] != 0x7F) || (m[1] != 'E') || (m[2] != 'L') || (m[3] != 'F'))
|
||||
{
|
||||
fprintf(stderr, "Not a valid ELF file (wrong magic)\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Verify architecture
|
||||
if(header->instruction_set != INSTRUCTION_SET_RISCV)
|
||||
{
|
||||
switch(header->instruction_set)
|
||||
{
|
||||
case INSTRUCTION_SET_X86:
|
||||
fprintf(stderr, "Provided ELF file targets x86 ; perhaps you forgot to cross-compile ? Please provide a RISC-V ELF\n");
|
||||
break;
|
||||
case INSTRUCTION_SET_X86_64:
|
||||
fprintf(stderr, "Provided ELF file targets x86_64; perhaps you forgot to cross-compile ? Please provide a RISC-V ELF\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Provided ELF file is for an unknown instruction set architecture (0x%x), please provide a RISC-V ELF\n", header->instruction_set);
|
||||
break;
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
// Verify architecture
|
||||
if(header->instruction_set != INSTRUCTION_SET_RISCV)
|
||||
{
|
||||
switch(header->instruction_set)
|
||||
{
|
||||
case INSTRUCTION_SET_X86:
|
||||
fprintf(stderr, "Provided ELF file targets x86 ; perhaps you forgot to cross-compile ? Please provide a RISC-V ELF\n");
|
||||
break;
|
||||
case INSTRUCTION_SET_X86_64:
|
||||
fprintf(stderr, "Provided ELF file targets x86_64; perhaps you forgot to cross-compile ? Please provide a RISC-V ELF\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Provided ELF file is for an unknown instruction set architecture (0x%x), please provide a RISC-V ELF\n", header->instruction_set);
|
||||
break;
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Verify bit count
|
||||
if(header->bits != BITS_32)
|
||||
{
|
||||
switch(header->bits)
|
||||
{
|
||||
case BITS_64:
|
||||
fprintf(stderr, "Provided ELF file targets RISC-V 64 bits ; please provide a 32-bits ELF\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Provided ELF file targets an unknown bit count (not 32 bits) ; please provide a 32-bits ELF\n");
|
||||
break;
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
// Verify bit count
|
||||
if(header->bits != BITS_32)
|
||||
{
|
||||
switch(header->bits)
|
||||
{
|
||||
case BITS_64:
|
||||
fprintf(stderr, "Provided ELF file targets RISC-V 64 bits ; please provide a 32-bits ELF\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Provided ELF file targets an unknown bit count (not 32 bits) ; please provide a 32-bits ELF\n");
|
||||
break;
|
||||
}
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// Verify endianness
|
||||
if(header->endianness != ELF_LITTLE_ENDIAN)
|
||||
{
|
||||
fprintf(stderr, "Provided ELF file is encoded in big endian ; please provide a little endian ELF\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
// Verify endianness
|
||||
if(header->endianness != ELF_LITTLE_ENDIAN)
|
||||
{
|
||||
fprintf(stderr, "Provided ELF file is encoded in big endian ; please provide a little endian ELF\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
// File should be correct ; now load it, using program header table
|
||||
elf_program_header_32_t* program_header = (elf_program_header_32_t*) (((uint8_t*) file) + header->program_header_table_32);
|
||||
size_t program_header_count = header->program_entry_amount;
|
||||
for(size_t i = 0; i < program_header_count; i++)
|
||||
{
|
||||
elf_program_header_32_t current = program_header[i];
|
||||
|
||||
// Check segment type
|
||||
if(current.segment_type != SEGMENT_TYPE_LOAD)
|
||||
{
|
||||
fprintf(stderr, "WARNING: Unknown segment type %u in ELF file ; skipping\n", current.segment_type);
|
||||
continue;
|
||||
}
|
||||
// File should be correct ; now load it, using program header table
|
||||
elf_program_header_32_t* program_header = (elf_program_header_32_t*) (((uint8_t*) file) + header->program_header_table_32);
|
||||
size_t program_header_count = header->program_entry_amount;
|
||||
for(size_t i = 0; i < program_header_count; i++)
|
||||
{
|
||||
elf_program_header_32_t current = program_header[i];
|
||||
|
||||
// Check memory size
|
||||
size_t memsz = current.segment_memory_size;
|
||||
if(!memsz)
|
||||
{
|
||||
fprintf(stderr, "WARNING: LOAD segment with null size in ELF file ; skipping\n");
|
||||
continue;
|
||||
}
|
||||
// Check segment type
|
||||
if(current.segment_type != SEGMENT_TYPE_LOAD)
|
||||
{
|
||||
// Don't message for riscv-specific attributes segment
|
||||
if(current.segment_type != SEGMENT_TYPE_RISCV_SPECIFIC_SHT_RISCV_ATTRIBUTES)
|
||||
{
|
||||
fprintf(stderr, "WARNING: Unknown segment type %u in ELF file ; skipping\n", current.segment_type);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Map the segment : first the part in the file, then the zeros
|
||||
if(memory_size < (current.virtual_address + memsz))
|
||||
{
|
||||
fprintf(stderr, "FATAL: Not enough memory while loading ELF file\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if(current.segment_file_size != 0)
|
||||
{
|
||||
memcpy(&memory[current.virtual_address], ((uint8_t*) file) + current.segment_offset, current.segment_file_size);
|
||||
}
|
||||
if(memsz > current.segment_file_size)
|
||||
{
|
||||
memset(&memory[current.virtual_address] + current.segment_file_size, 0, memsz - current.segment_file_size);
|
||||
}
|
||||
}
|
||||
// Check memory size
|
||||
size_t memsz = current.segment_memory_size;
|
||||
if(!memsz)
|
||||
{
|
||||
fprintf(stderr, "WARNING: LOAD segment with null size in ELF file ; skipping\n");
|
||||
continue;
|
||||
}
|
||||
|
||||
return header->entry_32;
|
||||
// Map the segment : first the part in the file, then the zeros
|
||||
if(memory_size < (current.virtual_address + memsz))
|
||||
{
|
||||
fprintf(stderr, "FATAL: Not enough memory while loading ELF file\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if(current.segment_file_size != 0)
|
||||
{
|
||||
memcpy(&memory[current.virtual_address], ((uint8_t*) file) + current.segment_offset, current.segment_file_size);
|
||||
}
|
||||
if(memsz > current.segment_file_size)
|
||||
{
|
||||
memset(&memory[current.virtual_address] + current.segment_file_size, 0, memsz - current.segment_file_size);
|
||||
}
|
||||
}
|
||||
|
||||
return header->entry_32;
|
||||
}
|
||||
|
@@ -77,6 +77,7 @@ typedef struct ELF_PROGRAM_HEADER_32
|
||||
#define SEGMENT_TYPE_DYNAMIC 2
|
||||
#define SEGMENT_TYPE_INTERP 3
|
||||
#define SEGMENT_TYPE_NOTE 4
|
||||
#define SEGMENT_TYPE_RISCV_SPECIFIC_SHT_RISCV_ATTRIBUTES 0x70000003
|
||||
|
||||
uint32_t elf_32_load(void* file);
|
||||
|
||||
|
40
src/cpu/csr.c
Normal file
40
src/cpu/csr.c
Normal file
@@ -0,0 +1,40 @@
|
||||
#include "csr.h"
|
||||
#include "rv32cpu.h"
|
||||
|
||||
uint32_t csr_read(struct RV32_CPU* cpu, uint32_t csr)
|
||||
{
|
||||
switch(csr)
|
||||
{
|
||||
case CSR_CYCLE:
|
||||
return cpu->sim_ticks_done;
|
||||
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;
|
||||
}
|
||||
|
||||
return cpu->csr[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;
|
||||
}
|
100
src/cpu/csr.h
Normal file
100
src/cpu/csr.h
Normal file
@@ -0,0 +1,100 @@
|
||||
#ifndef CSR_H
|
||||
#define CSR_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
/* ZICSR : Control and Status Registers */
|
||||
#define CSR_COUNT 0x2000
|
||||
|
||||
/* Unprivileged CSR */
|
||||
#define CSR_CYCLE 0xC00
|
||||
#define CSR_TIME 0xC01
|
||||
#define CSR_CYCLEH 0xC80
|
||||
#define CSR_TIMEH 0xC81
|
||||
|
||||
/* Supervisor-level CSR */
|
||||
/* Supervisor Trap setup CSR */
|
||||
#define CSR_SSTATUS 0x100
|
||||
#define CSR_SIE 0x104
|
||||
#define CSR_STVEC 0x105
|
||||
#define CSR_SCOUNTEREN 0x106
|
||||
#define CSR_SENVCFG 0x10A
|
||||
/* Supervisor Trap handling CSR */
|
||||
#define CSR_SSCRATCH 0x140
|
||||
#define CSR_SEPC 0x141
|
||||
#define CSR_SCAUSE 0x142
|
||||
#define CSR_STVAL 0x143
|
||||
#define CSR_SIP 0x144
|
||||
/* Supervisor Protection/Translation CSR */
|
||||
#define CSR_SATP 0x180
|
||||
/* Debug/Trace CSR */
|
||||
#define CSR_SCONTEXT 0x5A8
|
||||
|
||||
/* Machine-level CSR */
|
||||
#define CSR_MVENDORID 0xF11
|
||||
#define CSR_MARCHID 0xF12
|
||||
#define CSR_MIMPID 0xF13
|
||||
#define CSR_MHARTID 0xF14
|
||||
#define CSR_MCONFIGPTR 0xF15
|
||||
/* Machine Trap setup CSR */
|
||||
#define CSR_MSTATUS 0x300
|
||||
#define CSR_MISA 0x301
|
||||
#define CSR_MEDELEG 0x302
|
||||
#define CSR_MIDELEG 0x303
|
||||
#define CSR_MIE 0x304
|
||||
#define CSR_MTVEC 0x305
|
||||
#define CSR_MCOUNTEREN 0x306
|
||||
#define CSR_MSTATUSH 0x310
|
||||
/* Machine Trap handling CSR */
|
||||
#define CSR_MSCRATCH 0x340
|
||||
#define CSR_MEPC 0x341
|
||||
#define CSR_MCAUSE 0x342
|
||||
#define CSR_MTVAL 0x343
|
||||
#define CSR_MIP 0x344
|
||||
#define CSR_MTINST 0x34A
|
||||
#define CSR_MTVAL2 0x34B
|
||||
/* Machine Configuration */
|
||||
#define CSR_MENVCFG 0x30A
|
||||
#define CSR_MENVCFGH 0x31A
|
||||
#define CSR_MSECCFG 0x747
|
||||
#define CSR_MSECCFGH 0x757
|
||||
/* 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
|
69
src/cpu/exception.c
Normal file
69
src/cpu/exception.c
Normal file
@@ -0,0 +1,69 @@
|
||||
#include "exception.h"
|
||||
#include "vriscv.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
__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
|
||||
// We are in the CPU thread itself, but we need
|
||||
// the return of this function to be the beginning of
|
||||
// the cpu loop
|
||||
// To achieve that, we can just call cpu_loop (noreturn)
|
||||
// at the end of this function
|
||||
|
||||
// Exceptions cannot be disabled
|
||||
|
||||
// TODO : Check medeleg to see if we should handle exception in S mode or M mode
|
||||
|
||||
// Unset SIE (interrupt enable) bit
|
||||
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
|
||||
|
||||
// Set xTVAL, exception-specific information related to xCAUSE
|
||||
csr_write(cpu, CSR_STVAL, tval);
|
||||
|
||||
// Set SEPC to instruction that caused exception
|
||||
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 = csr_read(cpu, CSR_STVEC) & 0xFFFFFFFC;
|
||||
|
||||
// Unlock cpu mutex, cpu_loop will lock it just after
|
||||
pthread_mutex_unlock(&cpu->mutex);
|
||||
|
||||
// TODO : Hard reset the stack pointer
|
||||
// cpu loop
|
||||
cpu_loop(cpu);
|
||||
|
||||
__builtin_unreachable();
|
||||
}
|
19
src/cpu/exception.h
Normal file
19
src/cpu/exception.h
Normal file
@@ -0,0 +1,19 @@
|
||||
#ifndef EXCEPTION_H
|
||||
#define EXCEPTION_H
|
||||
|
||||
#include "rv32cpu.h"
|
||||
|
||||
__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
|
||||
#define SCAUSE_ILLEGAL_INSTRUCTION 0x2
|
||||
#define SCAUSE_BREAKPOINT 0x3
|
||||
#define SCAUSE_LOAD_ACCESS_FAULT 0x5
|
||||
#define SCAUSE_AMO_ADDRESS_MISALIGNED 0x6
|
||||
#define SCAUSE_ENVIRONMENT_CALL 0x8
|
||||
#define SCAUSE_INSTRUCTION_PAGE_FAULT 0xC
|
||||
#define SCAUSE_LOAD_PAGE_FAULT 0xD
|
||||
#define SCAUSE_STORE_AMO_PAGE_FAULT 0xF
|
||||
|
||||
#endif
|
@@ -96,6 +96,15 @@
|
||||
#define FUNC3_ECALL_EBREAK 0x0
|
||||
#define IMM_ECALL 0x0
|
||||
#define IMM_EBREAK 0x1
|
||||
/* RISC-V Privileged Instructions */
|
||||
#define IMM_SRET 0x102
|
||||
#define IMM_MRET 0x302
|
||||
#define FUNC7_SFENCEVMA 0x9
|
||||
#define FUNC7_WFI 0x8
|
||||
#define FUNC7_SINVALVMA 0x11
|
||||
#define FUNC7_SFENCEWINVAL_SFENCEINVALIR 0xC
|
||||
#define IMM5_SFENCEWINVAL 0x0
|
||||
#define IMM5_SFENCEINVALIR 0x1
|
||||
/* RISC-V RV32 ZICSR Extension */
|
||||
#define FUNC3_CSRRW 0x1
|
||||
#define FUNC3_CSRRS 0x2
|
||||
|
99
src/cpu/interrupt.c
Normal file
99
src/cpu/interrupt.c
Normal file
@@ -0,0 +1,99 @@
|
||||
#include "interrupt.h"
|
||||
#include "rv32cpu.h"
|
||||
|
||||
#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 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)))
|
||||
{
|
||||
// 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
|
||||
// We obtain in this thread the control of the CPU,
|
||||
// but we know it is not in the middle of an instruction
|
||||
// (we got the mutex) ; this way we can just change
|
||||
// registers to set interrupt handler execution
|
||||
pthread_mutex_lock(&cpu->mutex);
|
||||
|
||||
// Set xCAUSE with interrupt bit set
|
||||
cpu->csr[CSR_SCAUSE] = 0x80000000 | scause;
|
||||
|
||||
// Set xSTATUS.xPIE (previous interrupt enable) bit
|
||||
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_MSTATUS] &= (~STATUS_SIE);
|
||||
|
||||
// Set xEPC : PC at interruption
|
||||
cpu->csr[CSR_SEPC] = cpu->pc;
|
||||
|
||||
// Set PC to xTVEC : exception handler code
|
||||
// xTVEC: [Base(30bits) Mode(2 bits)], address 4-byte aligned in base
|
||||
// Interrupts can be vectored, if mode == 1, then pc = xTVEC + scause * 4
|
||||
int mode = cpu->csr[CSR_STVEC] & 0b11;
|
||||
switch(mode)
|
||||
{
|
||||
case 0:
|
||||
cpu->pc = cpu->csr[CSR_STVEC] & 0xFFFFFFFC;
|
||||
break;
|
||||
case 1:
|
||||
cpu->pc = (cpu->csr[CSR_STVEC] & 0xFFFFFFFC) + scause * 4;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "interrupt_trigger: invalid mode encountered in sTVEC register\n");
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&cpu->mutex);
|
||||
}
|
||||
|
||||
void interrupt_timer_thread()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
cpu0->csr[CSR_TIME]++;
|
||||
interrupt_trigger(cpu0, SCAUSE_SUPERVISOR_TIMER_INTERRUPT);
|
||||
usleep(1);
|
||||
}
|
||||
}
|
||||
|
||||
void interrupt_timer_setup()
|
||||
{
|
||||
pthread_t timer_thread;
|
||||
pthread_create(&timer_thread, 0, (void*) interrupt_timer_thread, 0);
|
||||
}
|
15
src/cpu/interrupt.h
Normal file
15
src/cpu/interrupt.h
Normal file
@@ -0,0 +1,15 @@
|
||||
#ifndef INTERRUPT_H
|
||||
#define INTERRUPT_H
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "rv32cpu.h"
|
||||
|
||||
#define SCAUSE_SUPERVISOR_SOFTWARE_INTERRUPT 0x1
|
||||
#define SCAUSE_SUPERVISOR_TIMER_INTERRUPT 0x5
|
||||
#define SCAUSE_SUPERVISOR_EXTERNAL_INTERRUPT 0x9
|
||||
|
||||
void interrupt_trigger(rv32_cpu_t* cpu, uint32_t scause);
|
||||
void interrupt_timer_setup();
|
||||
|
||||
#endif
|
@@ -1,15 +1,16 @@
|
||||
#include "rv32cpu.h"
|
||||
#include "instruction.h"
|
||||
#include "csr.h"
|
||||
#include "devices/sbi/sbi.h"
|
||||
|
||||
#include "memory/memory.h"
|
||||
#include "memory/mmu/mmu.h"
|
||||
#include "vriscv.h"
|
||||
#include "exception.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
|
||||
rv32_cpu_t* cpu0;
|
||||
pthread_mutex_t cpu0_mutex;
|
||||
|
||||
typedef union RAW_INSTRUCTION
|
||||
{
|
||||
@@ -41,12 +42,13 @@ static void cpu_print_instruction(instruction_t* instruction);
|
||||
void cpu_init()
|
||||
{
|
||||
cpu0 = calloc(1, sizeof(rv32_cpu_t));
|
||||
pthread_mutex_init(&cpu0_mutex, 0);
|
||||
pthread_mutex_init(&cpu0->mutex, 0);
|
||||
pthread_cond_init(&cpu0->sim_condition, 0);
|
||||
cpu0->regs.zero = 0;
|
||||
cpu0->privilege_mode = MACHINE;
|
||||
}
|
||||
|
||||
static void cpu_decode(raw_instruction_t raw_instruction, instruction_t* output)
|
||||
static void cpu_decode(rv32_cpu_t* cpu, raw_instruction_t raw_instruction, instruction_t* output)
|
||||
{
|
||||
output->opcode = raw_instruction.opcode;
|
||||
output->immediate = 0;
|
||||
@@ -71,7 +73,7 @@ static void cpu_decode(raw_instruction_t raw_instruction, instruction_t* output)
|
||||
// Then following 10 bits (30-21) are immediate bits 10-1
|
||||
output->immediate |= (raw_instruction.data & 0x7FE00000) >> 20;
|
||||
// Following bit (20) is immediate bit 11
|
||||
output->immediate |= (raw_instruction.data & 0x200000) >> 9;
|
||||
output->immediate |= (raw_instruction.data & 0x100000) >> 9;
|
||||
// Last bits (19-12) are immediate bits 19-12
|
||||
output->immediate |= (raw_instruction.data & 0xFF000);
|
||||
break;
|
||||
@@ -109,8 +111,8 @@ static void cpu_decode(raw_instruction_t raw_instruction, instruction_t* output)
|
||||
// TODO : Decode NOP instructions
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Error: Unknown instruction opcode 0x%x, could not decode\n", raw_instruction.opcode);
|
||||
exit(EXIT_FAILURE);
|
||||
// Throw an 'Invalid OPCODE' exception
|
||||
exception_trigger(cpu, SCAUSE_ILLEGAL_INSTRUCTION, 0);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -208,27 +210,27 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
|
||||
{
|
||||
case FUNC3_LB:
|
||||
// Load Byte (8-bits)
|
||||
cpu->regs.x[instruction->rd] = memory[mmu_translate(address)];
|
||||
cpu->regs.x[instruction->rd] = mem_read8(address);
|
||||
// Sign extend from 8 bits to 32 bits
|
||||
cpu->regs.x[instruction->rd] |= (cpu->regs.x[instruction->rd] & 0x80 ? 0xFFFFFF00 : 0);
|
||||
break;
|
||||
case FUNC3_LH:
|
||||
// Load Halfword (16-bits)
|
||||
cpu->regs.x[instruction->rd] = *((uint16_t*) &memory[mmu_translate(address)]);
|
||||
cpu->regs.x[instruction->rd] = mem_read16(address);
|
||||
// Sign extend from 16 bits to 32 bits
|
||||
cpu->regs.x[instruction->rd] |= (cpu->regs.x[instruction->rd] & 0x8000 ? 0xFFFF0000 : 0);
|
||||
break;
|
||||
case FUNC3_LW:
|
||||
// Load Word (32-bits)
|
||||
cpu->regs.x[instruction->rd] = *((uint32_t*) &memory[mmu_translate(address)]);
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
break;
|
||||
case FUNC3_LBU:
|
||||
// Load Byte Unsigned (8-bits)
|
||||
cpu->regs.x[instruction->rd] = memory[mmu_translate(address)];
|
||||
cpu->regs.x[instruction->rd] = mem_read8(address);
|
||||
break;
|
||||
case FUNC3_LHU:
|
||||
// Load Halfword Unsigned (16-bits)
|
||||
cpu->regs.x[instruction->rd] = *((uint16_t*) &memory[mmu_translate(address)]);
|
||||
cpu->regs.x[instruction->rd] = mem_read16(address);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "FATAL: Unknown func3 0x%x for load instruction, could not execute\n", instruction->func3);
|
||||
@@ -248,15 +250,15 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
|
||||
{
|
||||
case FUNC3_SB:
|
||||
// Store Byte (8-bits)
|
||||
memory[mmu_translate(address)] = cpu->regs.x[instruction->rs2] & 0xFF;
|
||||
mem_write8(address, cpu->regs.x[instruction->rs2] & 0xFF);
|
||||
break;
|
||||
case FUNC3_SH:
|
||||
// Store Halfword (16-bits)
|
||||
*((uint16_t*) &memory[mmu_translate(address)]) = cpu->regs.x[instruction->rs2] & 0xFFFF;
|
||||
mem_write16(address, cpu->regs.x[instruction->rs2] & 0xFFFF);
|
||||
break;
|
||||
case FUNC3_SW:
|
||||
// Store Word (32-bits)
|
||||
*((uint32_t*) &memory[mmu_translate(address)]) = cpu->regs.x[instruction->rs2];
|
||||
mem_write32(address, cpu->regs.x[instruction->rs2]);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "FATAL: Unknown func3 0x%x for store instruction, could not execute\n", instruction->func3);
|
||||
@@ -370,6 +372,7 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
|
||||
break;
|
||||
case FUNC7_MULH:
|
||||
// MULtiplication High (32 high bits) (signed * signed)
|
||||
// FIXME: This is wrong, we should sign extend the int64_t i guess
|
||||
uint64_t mulh_result = (uint64_t) (((int64_t) cpu->regs.x[instruction->rs1]) * ((int64_t) cpu->regs.x[instruction->rs2]));
|
||||
cpu->regs.x[instruction->rd] = (uint32_t) (mulh_result >> 32);
|
||||
break;
|
||||
@@ -391,6 +394,7 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
|
||||
break;
|
||||
case FUNC7_MULHSU:
|
||||
// MULtiplication High (32 high bits) (signed * unsigned)
|
||||
// FIXME: This is wrong, we should sign extend the int64_t i guess
|
||||
uint64_t mulhsu_result = (uint64_t) (((int64_t) cpu->regs.x[instruction->rs1]) * ((uint64_t) cpu->regs.x[instruction->rs2]));
|
||||
cpu->regs.x[instruction->rd] = (uint32_t) (mulhsu_result >> 32);
|
||||
break;
|
||||
@@ -428,6 +432,13 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
|
||||
cpu->regs.x[instruction->rd] = cpu->regs.x[instruction->rs1] ^ cpu->regs.x[instruction->rs2];
|
||||
break;
|
||||
case FUNC7_DIV:
|
||||
if(!cpu->regs.x[instruction->rs2])
|
||||
{
|
||||
// ISA dictates that we return FFFFFFFF and set a flag bit to signal the exception
|
||||
cpu->regs.x[instruction->rd] = 0xFFFFFFFF;
|
||||
// TODO flag ?
|
||||
break;
|
||||
}
|
||||
cpu->regs.x[instruction->rd] = ((int32_t) cpu->regs.x[instruction->rs1]) / ((int32_t) cpu->regs.x[instruction->rs2]);
|
||||
break;
|
||||
default:
|
||||
@@ -498,48 +509,107 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
|
||||
case OPCODE_NOP:
|
||||
{
|
||||
// TODO : Implement PAUSE, FENCE, FENCE.TSO
|
||||
fprintf(stderr, "Warning: Unsupported NOP instruction\n");
|
||||
break;
|
||||
}
|
||||
case OPCODE_SYSTEM:
|
||||
{
|
||||
uint32_t csr = instruction->immediate;
|
||||
switch(instruction->func3)
|
||||
{
|
||||
case FUNC3_ECALL_EBREAK:
|
||||
switch(instruction->immediate)
|
||||
{
|
||||
case IMM_ECALL:
|
||||
fprintf(stderr, "ECALL: a7(sbi ext id) = %d, a6(sbi funct id) = %d\n", cpu->regs.a7, cpu->regs.a6);
|
||||
sbi_call(cpu, cpu->regs.a7, cpu->regs.a6);
|
||||
break;
|
||||
case IMM_EBREAK:
|
||||
// EBREAK : on debug, give back hand to debugger ; without debug, end simulation
|
||||
// In any way, we set back simulation ticks to 0
|
||||
cpu->sim_ticks_left = 1;
|
||||
cpu->pc -= 4;
|
||||
// EBREAK : generate a breakpoint exception
|
||||
exception_trigger(cpu, SCAUSE_BREAKPOINT, 0);
|
||||
break;
|
||||
case IMM_SRET:
|
||||
// SRET: Return from supervisor interrupt
|
||||
// Restore Interrupt Enable from Previous Interrupt Enable
|
||||
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
|
||||
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) | STATUS_SPIE);
|
||||
|
||||
// Set Previous Privilege to 0
|
||||
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;
|
||||
break;
|
||||
case IMM_MRET:
|
||||
// Ret to destination address : CSR_MEPC content
|
||||
// TODO fix it to act like sret
|
||||
cpu->privilege_mode = SUPERVISOR;
|
||||
cpu->pc = cpu->csr[CSR_MEPC] - 4;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "FATAL: Unknown IMM for ECALL/EBREAK instruction while executing (IMM=0x%x)\n", instruction->immediate);
|
||||
exit(EXIT_FAILURE);
|
||||
switch(instruction->func7)
|
||||
{
|
||||
case FUNC7_SFENCEVMA:
|
||||
// TODO : Check if we really need to do something on SFENCE.VMA ?
|
||||
break;
|
||||
case FUNC7_WFI:
|
||||
// Wait For Interrupt : halt the simulation
|
||||
// It will be woken up by an interruption
|
||||
fprintf(stderr, "WFI: Halting simulation.\n");
|
||||
cpu->sim_ticks_left = 1;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "FATAL: Unknown IMM for ECALL/EBREAK instruction while executing (IMM=0x%x)\n", instruction->immediate);
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
case FUNC3_CSRRW:
|
||||
fprintf(stderr, "CSRRW\n");
|
||||
// CSR atomic Read/Write
|
||||
uint32_t csrrw_old_value = 0;
|
||||
if(instruction->rd)
|
||||
csrrw_old_value = csr_read(cpu, csr);
|
||||
csr_write(cpu, csr, cpu->regs.x[instruction->rs1]);
|
||||
cpu->regs.x[instruction->rd] = csrrw_old_value;
|
||||
break;
|
||||
case FUNC3_CSRRS:
|
||||
fprintf(stderr, "CSRRS\n");
|
||||
// CSR atomic Read and Set bits
|
||||
cpu->regs.x[instruction->rd] = csr_read(cpu, csr);
|
||||
csr_write(cpu, csr, cpu->regs.x[instruction->rd] | cpu->regs.x[instruction->rs1]);
|
||||
break;
|
||||
case FUNC3_CSRRC:
|
||||
fprintf(stderr, "CSRRC\n");
|
||||
// CSR atomic Read and Clear bits
|
||||
cpu->regs.x[instruction->rd] = csr_read(cpu, csr);
|
||||
csr_write(cpu, csr, cpu->regs.x[instruction->rd] & (~cpu->regs.x[instruction->rs1]));
|
||||
break;
|
||||
case FUNC3_CSRRWI:
|
||||
fprintf(stderr, "CSRRWI\n");
|
||||
// CSR atomic Read/Write Immediate (immediate in rs1)
|
||||
uint32_t csrrwi_old_value = 0;
|
||||
if(instruction->rd)
|
||||
csrrwi_old_value = csr_read(cpu, csr);
|
||||
csr_write(cpu, csr, instruction->rs1);
|
||||
cpu->regs.x[instruction->rd] = csrrwi_old_value;
|
||||
break;
|
||||
case FUNC3_CSRRSI:
|
||||
fprintf(stderr, "CSRRSI\n");
|
||||
// CSR atomic Read and Set bits immediate
|
||||
cpu->regs.x[instruction->rd] = csr_read(cpu, csr);
|
||||
csr_write(cpu, csr, cpu->regs.x[instruction->rd] | instruction->rs1);
|
||||
break;
|
||||
case FUNC3_CSRRCI:
|
||||
fprintf(stderr, "CSRRCI\n");
|
||||
// CSR atomic Read and Clear bits Immediate (immediate in rs1)
|
||||
cpu->regs.x[instruction->rd] = csr_read(cpu, csr);
|
||||
csr_write(cpu, csr, cpu->regs.x[instruction->rd] & (~((uint32_t) instruction->rs1)));
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "FATAL: Unknown func3 0x%x for SYSTEM instruction while executing\n", instruction->func3);
|
||||
@@ -561,77 +631,80 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
|
||||
|
||||
// FUNC7 contains 2 flag bits in lower part ; ignore them, we look for func7_5
|
||||
uint32_t address = cpu->regs.x[instruction->rs1];
|
||||
uint32_t* ptr = ((uint32_t*) &memory[mmu_translate(address)]);
|
||||
switch(instruction->func7 >> 2)
|
||||
{
|
||||
case FUNC75_LRW:
|
||||
// Load-Reserved Word
|
||||
cpu->regs.x[instruction->rd] = *ptr;
|
||||
// TODO register reservation set that subsumes the bytes in word
|
||||
fprintf(stderr, "LR.W\n");
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
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
|
||||
*ptr = cpu->regs.x[instruction->rs2];
|
||||
cpu->regs.x[instruction->rd] = 0; // TODO write 1 in rd on failure
|
||||
fprintf(stderr, "SC.W\n");
|
||||
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
|
||||
cpu->regs.x[instruction->rd] = *ptr;
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
// Put in RS1 addr the value of RS2
|
||||
*ptr = cpu->regs.x[instruction->rs2];
|
||||
mem_write32(address, cpu->regs.x[instruction->rs2]);
|
||||
// Put in RS2 the value of RS1 addr (which is in RD)
|
||||
cpu->regs.x[instruction->rs2] = cpu->regs.x[instruction->rd];
|
||||
break;
|
||||
case FUNC75_AMOADDW:
|
||||
// Atomic Memory Operation ADD Word
|
||||
cpu->regs.x[instruction->rd] = *ptr;
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
// Add rs1 addr and value of rs2
|
||||
*ptr = cpu->regs.x[instruction->rd] + cpu->regs.x[instruction->rs2];
|
||||
mem_write32(address, cpu->regs.x[instruction->rd] + cpu->regs.x[instruction->rs2]);
|
||||
break;
|
||||
case FUNC75_AMOXORW:
|
||||
// Atomic Memory Operation XOR Word
|
||||
cpu->regs.x[instruction->rd] = *ptr;
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
// Xor rs1 addr and value of rs2
|
||||
*ptr = cpu->regs.x[instruction->rd] ^ cpu->regs.x[instruction->rs2];
|
||||
mem_write32(address, cpu->regs.x[instruction->rd] ^ cpu->regs.x[instruction->rs2]);
|
||||
break;
|
||||
case FUNC75_AMOANDW:
|
||||
// Atomic Memory Operation AND Word
|
||||
cpu->regs.x[instruction->rd] = *ptr;
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
// AND rs1 addr and value of rs2
|
||||
*ptr = cpu->regs.x[instruction->rd] & cpu->regs.x[instruction->rs2];
|
||||
mem_write32(address, cpu->regs.x[instruction->rd] & cpu->regs.x[instruction->rs2]);
|
||||
break;
|
||||
case FUNC75_AMOORW:
|
||||
// Atomic Memory Operation OR Word
|
||||
cpu->regs.x[instruction->rd] = *ptr;
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
// Or rs1 addr and value of rs2
|
||||
*ptr = cpu->regs.x[instruction->rd] | cpu->regs.x[instruction->rs2];
|
||||
mem_write32(address, cpu->regs.x[instruction->rd] | cpu->regs.x[instruction->rs2]);
|
||||
break;
|
||||
case FUNC75_AMOMINW:
|
||||
// Atomic Memory Operation MIN Word
|
||||
cpu->regs.x[instruction->rd] = *ptr;
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
// Min rs1 addr and value of rs2
|
||||
*ptr = ((int32_t) cpu->regs.x[instruction->rd]) < ((int32_t) cpu->regs.x[instruction->rs2]) ? cpu->regs.x[instruction->rd] : cpu->regs.x[instruction->rs2];
|
||||
mem_write32(address, ((int32_t) cpu->regs.x[instruction->rd]) < ((int32_t) cpu->regs.x[instruction->rs2]) ? cpu->regs.x[instruction->rd] : cpu->regs.x[instruction->rs2]);
|
||||
break;
|
||||
case FUNC75_AMOMAXW:
|
||||
// Atomic Memory Operation MAX Word
|
||||
cpu->regs.x[instruction->rd] = *ptr;
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
// Max rs1 addr and value of rs2
|
||||
*ptr = ((int32_t) cpu->regs.x[instruction->rd]) > ((int32_t) cpu->regs.x[instruction->rs2]) ? cpu->regs.x[instruction->rd] : cpu->regs.x[instruction->rs2];
|
||||
mem_write32(address, ((int32_t) cpu->regs.x[instruction->rd]) > ((int32_t) cpu->regs.x[instruction->rs2]) ? cpu->regs.x[instruction->rd] : cpu->regs.x[instruction->rs2]);
|
||||
break;
|
||||
case FUNC75_AMOMINUW:
|
||||
// Atomic Memory Operation MIN Unsigned Word
|
||||
cpu->regs.x[instruction->rd] = *ptr;
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
// Min rs1 addr and value of rs2
|
||||
*ptr = cpu->regs.x[instruction->rd] < cpu->regs.x[instruction->rs2] ? cpu->regs.x[instruction->rd] : cpu->regs.x[instruction->rs2];
|
||||
mem_write32(address, cpu->regs.x[instruction->rd] < cpu->regs.x[instruction->rs2] ? cpu->regs.x[instruction->rd] : cpu->regs.x[instruction->rs2]);
|
||||
break;
|
||||
case FUNC75_AMOMAXUW:
|
||||
// Atomic Memory Operation MAX Unsigned Word
|
||||
cpu->regs.x[instruction->rd] = *ptr;
|
||||
cpu->regs.x[instruction->rd] = mem_read32(address);
|
||||
// Max rs1 addr and value of rs2
|
||||
*ptr = cpu->regs.x[instruction->rd] > cpu->regs.x[instruction->rs2] ? cpu->regs.x[instruction->rd] : cpu->regs.x[instruction->rs2];
|
||||
mem_write32(address, cpu->regs.x[instruction->rd] > cpu->regs.x[instruction->rs2] ? cpu->regs.x[instruction->rd] : cpu->regs.x[instruction->rs2]);
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "FATAL: Unknown func7 0x%x for ATOMIC/0x2 instruction, could not execute\n", instruction->func7);
|
||||
@@ -647,21 +720,19 @@ static void cpu_execute(rv32_cpu_t* cpu, instruction_t* instruction)
|
||||
}
|
||||
}
|
||||
|
||||
void cpu_loop(rv32_cpu_t* cpu)
|
||||
__attribute__((noreturn)) void cpu_loop(rv32_cpu_t* cpu)
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
// Aquire CPU and memory mutex
|
||||
pthread_mutex_lock(&cpu0_mutex);
|
||||
// Aquire CPU mutex
|
||||
pthread_mutex_lock(&cpu->mutex);
|
||||
|
||||
// No simulation ticks left : wakeup people waiting on sim end
|
||||
if(!cpu->sim_ticks_left)
|
||||
pthread_cond_signal(&cpu0->sim_condition);
|
||||
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(&cpu0->sim_condition, &cpu0_mutex);
|
||||
|
||||
pthread_mutex_lock(&memory_mutex);
|
||||
pthread_cond_wait(&cpu->sim_condition, &cpu->mutex);
|
||||
|
||||
// Fetch
|
||||
raw_instruction_t raw_instruction;
|
||||
@@ -670,14 +741,17 @@ void cpu_loop(rv32_cpu_t* cpu)
|
||||
fprintf(stderr, "Error: instruction fetch: pc is out of addressable memory\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
raw_instruction.data = *((uint32_t*) (&memory[cpu->pc]));
|
||||
raw_instruction.data = mem_fetch(cpu->pc);
|
||||
|
||||
// Decode
|
||||
instruction_t instruction;
|
||||
cpu_decode(raw_instruction, &instruction);
|
||||
cpu_decode(cpu, raw_instruction, &instruction);
|
||||
|
||||
printf("0x%x: ", cpu->pc);
|
||||
cpu_print_instruction(&instruction);
|
||||
if(trace)
|
||||
{
|
||||
printf("0x%x: ", cpu->pc);
|
||||
cpu_print_instruction(&instruction);
|
||||
}
|
||||
|
||||
// Execute
|
||||
cpu_execute(cpu, &instruction);
|
||||
@@ -692,10 +766,11 @@ void cpu_loop(rv32_cpu_t* cpu)
|
||||
if(cpu->sim_ticks_left != (-1))
|
||||
cpu->sim_ticks_left--;
|
||||
|
||||
// Let go of cpu and memory mutex
|
||||
pthread_mutex_unlock(&memory_mutex);
|
||||
pthread_mutex_unlock(&cpu0_mutex);
|
||||
// Let go of cpu mutex
|
||||
pthread_mutex_unlock(&cpu->mutex);
|
||||
}
|
||||
|
||||
__builtin_unreachable();
|
||||
}
|
||||
|
||||
static void cpu_print_instruction(instruction_t* instruction)
|
||||
@@ -719,7 +794,7 @@ static void cpu_print_instruction(instruction_t* instruction)
|
||||
// Jump And Link
|
||||
// Sign extend immediate from 21 bits to 32 bits
|
||||
uint32_t immediate = (instruction->immediate & 0x1FFFFF) | (instruction->immediate & 0x100000 ? 0xFFE00000 : 0);
|
||||
printf("jal x%u, 0x%x\n", instruction->rd, immediate);
|
||||
printf("jal x%u, %d (=0x%x)\n", instruction->rd, immediate, immediate);
|
||||
break;
|
||||
}
|
||||
case OPCODE_JALR:
|
||||
@@ -730,7 +805,7 @@ static void cpu_print_instruction(instruction_t* instruction)
|
||||
if(immediate == 0x0 && instruction->rd == 0 && instruction->rs1 == 1)
|
||||
printf("ret\n");
|
||||
else
|
||||
printf("jalr x%u, x%u, 0x%x\n", instruction->rd, instruction->rs1, immediate);
|
||||
printf("jalr x%u, %d(x%u)\n", instruction->rd, immediate, instruction->rs1);
|
||||
break;
|
||||
}
|
||||
case OPCODE_BRANCH:
|
||||
@@ -1049,8 +1124,42 @@ static void cpu_print_instruction(instruction_t* instruction)
|
||||
case IMM_EBREAK:
|
||||
printf("ebreak\n");
|
||||
break;
|
||||
case IMM_SRET:
|
||||
printf("sret\n");
|
||||
break;
|
||||
case IMM_MRET:
|
||||
printf("mret\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Warning: Unknown IMM for ECALL/EBREAK instruction (IMM=0x%x)\n", instruction->immediate);
|
||||
switch(instruction->func7)
|
||||
{
|
||||
case FUNC7_WFI:
|
||||
printf("wfi\n");
|
||||
break;
|
||||
case FUNC7_SFENCEVMA:
|
||||
printf("sfence.vma\n");
|
||||
break;
|
||||
case FUNC7_SINVALVMA:
|
||||
printf("sinval.vma\n");
|
||||
break;
|
||||
case FUNC7_SFENCEWINVAL_SFENCEINVALIR:
|
||||
switch(instruction->immediate & 0x1F)
|
||||
{
|
||||
case IMM5_SFENCEWINVAL:
|
||||
printf("sfence.w.inval\n");
|
||||
break;
|
||||
case IMM5_SFENCEINVALIR:
|
||||
printf("sfence.inval.ir\n");
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Warning: Unknown IMM for SFENCEWINVAL/SFENCEINVALIR instruction (IMM=0x%x)\n", instruction->immediate);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "Warning: Unknown IMM for ECALL/EBREAK instruction (IMM=0x%x)\n", instruction->immediate);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
break;
|
||||
|
@@ -5,13 +5,22 @@
|
||||
#include <pthread.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "csr.h"
|
||||
|
||||
typedef enum RVCPU_PRIVILEGE_MODE
|
||||
{
|
||||
USER,
|
||||
SUPERVISOR,
|
||||
MACHINE
|
||||
} rvcpu_privilege_mode_t;
|
||||
|
||||
/*
|
||||
* This is a structure encoding for the registers of
|
||||
* the rv32 cpu.
|
||||
* It allows access of register x0 using :
|
||||
* structname.x0, structname.zero, structname.x[0]
|
||||
* This way, access can be really flexible
|
||||
*/
|
||||
* This is a structure encoding for the registers of
|
||||
* the rv32 cpu.
|
||||
* It allows access of register x0 using :
|
||||
* structname.x0, structname.zero, structname.x[0]
|
||||
* This way, access can be really flexible
|
||||
*/
|
||||
typedef struct RV32_CPU_REGS
|
||||
{
|
||||
union
|
||||
@@ -186,18 +195,26 @@ typedef struct RV32_CPU_REGS
|
||||
typedef struct RV32_CPU
|
||||
{
|
||||
// CPU values
|
||||
rv32_cpu_regs_t regs;
|
||||
uint32_t pc;
|
||||
rv32_cpu_regs_t regs;
|
||||
uint32_t pc;
|
||||
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
|
||||
size_t sim_ticks_done;
|
||||
pthread_cond_t sim_condition;
|
||||
|
||||
pthread_mutex_t mutex;
|
||||
} rv32_cpu_t;
|
||||
|
||||
uint32_t csr_read(struct RV32_CPU* cpu, uint32_t csr);
|
||||
void csr_write(struct RV32_CPU* cpu, uint32_t csr, uint32_t value);
|
||||
|
||||
extern rv32_cpu_t* cpu0;
|
||||
extern pthread_mutex_t cpu0_mutex;
|
||||
void cpu_init();
|
||||
void cpu_loop(rv32_cpu_t* cpu);
|
||||
__attribute__((noreturn)) void cpu_loop(rv32_cpu_t* cpu);
|
||||
|
||||
#endif
|
||||
|
87
src/devices/sbi/sbi.c
Normal file
87
src/devices/sbi/sbi.c
Normal file
@@ -0,0 +1,87 @@
|
||||
#include "sbi.h"
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
void sbi_call(rv32_cpu_t* cpu, uint32_t extension_id, uint32_t func_id)
|
||||
{
|
||||
switch(extension_id)
|
||||
{
|
||||
case SBI_EXTENSION_BASE:
|
||||
{
|
||||
switch(func_id)
|
||||
{
|
||||
case SBI_FUNCTION_GET_SPEC_VERSION:
|
||||
// Format: [31(reserved,0) 7 bits(major) 24 bits(minor)]
|
||||
// Version 2.0
|
||||
cpu->regs.a0 = 0;
|
||||
cpu->regs.a1 = 0x2000000;
|
||||
break;
|
||||
case SBI_FUNCTION_GET_IMPL_ID:
|
||||
cpu->regs.a0 = 0;
|
||||
cpu->regs.a1 = 0;
|
||||
break;
|
||||
case SBI_FUNCTION_GET_IMPL_VERSION:
|
||||
cpu->regs.a0 = 0;
|
||||
cpu->regs.a1 = 0;
|
||||
break;
|
||||
case SBI_FUNCTION_PROBE_EXTENSION:
|
||||
// For now, we say that everything is available
|
||||
cpu->regs.a0 = 0;
|
||||
cpu->regs.a1 = 1;
|
||||
break;
|
||||
case SBI_FUNCTION_GET_MVENDOR_ID:
|
||||
cpu->regs.a0 = 0;
|
||||
cpu->regs.a1 = 0;
|
||||
break;
|
||||
case SBI_FUNCTION_GET_MARCH_ID:
|
||||
cpu->regs.a0 = 0;
|
||||
cpu->regs.a1 = 0;
|
||||
break;
|
||||
case SBI_FUNCTION_GET_MIMPL_ID:
|
||||
cpu->regs.a0 = 0;
|
||||
cpu->regs.a1 = 0;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "sbi_call: unknown function id 0x%x for base extension\n", func_id);
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case SBI_EXTENSION_TIMER:
|
||||
{
|
||||
switch(func_id)
|
||||
{
|
||||
case SBI_FUNCTION_SET_TIMER:
|
||||
cpu->regs.a0 = 0;
|
||||
cpu->regs.a1 = 0;
|
||||
break;
|
||||
default:
|
||||
fprintf(stderr, "sbi_call: unknown function id 0x%x for timer extension\n", func_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
case SBI_EXTENSION_SET_TIMER:
|
||||
{
|
||||
// TODO : Correctly implement that
|
||||
cpu->regs.a0 = 0;
|
||||
break;
|
||||
}
|
||||
case SBI_EXTENSION_LEGACY_CONSOLE_PUTCHAR:
|
||||
{
|
||||
printf("%c", cpu->regs.a0);
|
||||
cpu->regs.a0 = 0;
|
||||
break;
|
||||
}
|
||||
case SBI_EXTENSION_LEGACY_SHUTDOWN:
|
||||
{
|
||||
printf("sbi_call: shutdown, goodbye !\n");
|
||||
exit(cpu->regs.a0);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
fprintf(stderr, "sbi_call: unknown extension id 0x%x\n", extension_id);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
27
src/devices/sbi/sbi.h
Normal file
27
src/devices/sbi/sbi.h
Normal file
@@ -0,0 +1,27 @@
|
||||
#ifndef SBI_H
|
||||
#define SBI_H
|
||||
|
||||
#include "cpu/rv32cpu.h"
|
||||
|
||||
/* SBI Base extension */
|
||||
#define SBI_EXTENSION_BASE 0x10
|
||||
#define SBI_FUNCTION_GET_SPEC_VERSION 0x0
|
||||
#define SBI_FUNCTION_GET_IMPL_ID 0x1
|
||||
#define SBI_FUNCTION_GET_IMPL_VERSION 0x2
|
||||
#define SBI_FUNCTION_PROBE_EXTENSION 0x3
|
||||
#define SBI_FUNCTION_GET_MVENDOR_ID 0x4
|
||||
#define SBI_FUNCTION_GET_MARCH_ID 0x5
|
||||
#define SBI_FUNCTION_GET_MIMPL_ID 0x6
|
||||
|
||||
/* SBI Timer extension */
|
||||
#define SBI_EXTENSION_TIMER 0x54494d45
|
||||
#define SBI_FUNCTION_SET_TIMER 0x0
|
||||
|
||||
/* SBI legacy */
|
||||
#define SBI_EXTENSION_SET_TIMER 0x0
|
||||
#define SBI_EXTENSION_LEGACY_CONSOLE_PUTCHAR 0x1
|
||||
#define SBI_EXTENSION_LEGACY_SHUTDOWN 0x8
|
||||
|
||||
void sbi_call(rv32_cpu_t* cpu, uint32_t extension_id, uint32_t func_id);
|
||||
|
||||
#endif
|
26
src/devices/uart/uart.c
Normal file
26
src/devices/uart/uart.c
Normal file
@@ -0,0 +1,26 @@
|
||||
#include "uart.h"
|
||||
#include "memory/memory.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#define UART_ADDRESS 0x3000000
|
||||
#define UART_REGISTER_SIZE 4
|
||||
#define UART_REGISTER_COUNT 1
|
||||
#define UART_REG_TXFIFO 0x0
|
||||
|
||||
void uart_handle_mmio_write(uint32_t address, uint8_t value);
|
||||
uint32_t uart_handle_mmio_read(uint32_t address);
|
||||
|
||||
void uart_init()
|
||||
{
|
||||
mem_register_mmio(UART_ADDRESS, UART_REGISTER_SIZE, UART_REGISTER_COUNT, uart_handle_mmio_write, uart_handle_mmio_read);
|
||||
}
|
||||
|
||||
void uart_handle_mmio_write(uint32_t address, uint8_t value)
|
||||
{
|
||||
printf("%c", value);
|
||||
}
|
||||
|
||||
uint32_t uart_handle_mmio_read(uint32_t address)
|
||||
{
|
||||
return 0;
|
||||
}
|
6
src/devices/uart/uart.h
Normal file
6
src/devices/uart/uart.h
Normal file
@@ -0,0 +1,6 @@
|
||||
#ifndef UART_H
|
||||
#define UART_H
|
||||
|
||||
void uart_init();
|
||||
|
||||
#endif
|
@@ -2,7 +2,6 @@
|
||||
|
||||
#include "cpu/rv32cpu.h"
|
||||
#include "memory/memory.h"
|
||||
#include "memory/mmu/mmu.h"
|
||||
|
||||
#include <errno.h>
|
||||
#include <stdlib.h>
|
||||
@@ -10,6 +9,8 @@
|
||||
#include <string.h>
|
||||
|
||||
/* Sockets */
|
||||
#include <unistd.h>
|
||||
#include <sys/socket.h>
|
||||
#include <sys/types.h>
|
||||
#include <netinet/in.h>
|
||||
#include <arpa/inet.h>
|
||||
@@ -31,7 +32,6 @@ void gdbstub_thread_gdb();
|
||||
void gdbstub_cpu_watcher_thread();
|
||||
static void gdbstub_handle_ctrlc();
|
||||
|
||||
|
||||
/*
|
||||
* Receive a packet from client gdb
|
||||
* Ignores the ACK on the way, and only returns the packet (not initial '$' symbol)
|
||||
@@ -120,6 +120,11 @@ static void gdbstub_send_unsupported()
|
||||
void gdbstub_start()
|
||||
{
|
||||
socket_t sock = socket(AF_INET, SOCK_STREAM, 0);
|
||||
|
||||
// Set REUSEADDRESS/PORT so that we dont get "port already in use" on close
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &(int){1}, sizeof(int));
|
||||
setsockopt(sock, SOL_SOCKET, SO_REUSEPORT, &(int){1}, sizeof(int));
|
||||
|
||||
if(sock == INVALID_SOCKET)
|
||||
{
|
||||
fprintf(stderr, "Could not create gdbstub server socket: %s\n", strerror(errno));
|
||||
@@ -145,6 +150,15 @@ void gdbstub_start()
|
||||
gdbstub_server_socket = sock;
|
||||
}
|
||||
|
||||
/*
|
||||
* Gracefully close gdb socket
|
||||
*/
|
||||
void gdbstub_stop()
|
||||
{
|
||||
close(gdb_socket);
|
||||
close(gdbstub_server_socket);
|
||||
}
|
||||
|
||||
/*
|
||||
* Wait for a client (gdb) to connect to this server
|
||||
*/
|
||||
@@ -189,7 +203,7 @@ void gdbstub_thread_gdb()
|
||||
char resp[32 * 8 + 8 + 1] = {0};
|
||||
|
||||
// Obtain CPU0 mutex
|
||||
pthread_mutex_lock(&cpu0_mutex);
|
||||
pthread_mutex_lock(&cpu0->mutex);
|
||||
|
||||
// All general purpose registers in host byte order as chars
|
||||
for(size_t i = 0; i < 32; i++)
|
||||
@@ -203,7 +217,7 @@ void gdbstub_thread_gdb()
|
||||
snprintf(resp + 32 * 8, 9, "%08x", pc);
|
||||
|
||||
// Let go of CPU0 mutex
|
||||
pthread_mutex_unlock(&cpu0_mutex);
|
||||
pthread_mutex_unlock(&cpu0->mutex);
|
||||
|
||||
// Final packet size, send packet
|
||||
size_t size = 32 * 8 + 8;
|
||||
@@ -214,7 +228,7 @@ void gdbstub_thread_gdb()
|
||||
// G : write all registers -> read and set all registers
|
||||
|
||||
// Obtain CPU0 mutex
|
||||
pthread_mutex_lock(&cpu0_mutex);
|
||||
pthread_mutex_lock(&cpu0->mutex);
|
||||
|
||||
// All general purpose registers in host byte order as chars
|
||||
for(size_t i = 1; i < 32; i++)
|
||||
@@ -233,7 +247,7 @@ void gdbstub_thread_gdb()
|
||||
cpu0->pc = pc;
|
||||
|
||||
// Let go of CPU0 Mutex
|
||||
pthread_mutex_unlock(&cpu0_mutex);
|
||||
pthread_mutex_unlock(&cpu0->mutex);
|
||||
|
||||
gdbstub_send_packet("OK", 2);
|
||||
}
|
||||
@@ -244,19 +258,13 @@ void gdbstub_thread_gdb()
|
||||
uint32_t length;
|
||||
sscanf(packet + 1, "%x,%x", &address, &length);
|
||||
|
||||
// Aquire memory mutex
|
||||
pthread_mutex_lock(&memory_mutex);
|
||||
|
||||
char data[length * 2 + 1];
|
||||
for(size_t i = 0; i < length; i++)
|
||||
{
|
||||
uint32_t value = memory[mmu_translate(address + i)];
|
||||
uint32_t value = mem_read8(address + i);
|
||||
snprintf(data + i * 2, 3, "%02x", value);
|
||||
}
|
||||
|
||||
// Let go of memory mutex
|
||||
pthread_mutex_unlock(&memory_mutex);
|
||||
|
||||
gdbstub_send_packet(data, length * 2);
|
||||
}
|
||||
else if(packet[0] == 'M')
|
||||
@@ -271,19 +279,13 @@ void gdbstub_thread_gdb()
|
||||
data_start++;
|
||||
data_start++;
|
||||
|
||||
// Aquire memory mutex
|
||||
pthread_mutex_lock(&memory_mutex);
|
||||
|
||||
for(size_t i = 0; i < length; i++)
|
||||
{
|
||||
uint32_t value;
|
||||
sscanf(packet + data_start + i * 2, "%02x", &value);
|
||||
memory[mmu_translate(address + i)] = value;
|
||||
mem_write8(address + i, value);
|
||||
}
|
||||
|
||||
// Let go of memory mutex
|
||||
pthread_mutex_unlock(&memory_mutex);
|
||||
|
||||
gdbstub_send_packet("OK", 2);
|
||||
}
|
||||
else if(packet[0] == 's')
|
||||
@@ -294,9 +296,9 @@ void gdbstub_thread_gdb()
|
||||
send(gdb_socket, "+", 1, 0);
|
||||
|
||||
// Continue simulation, for 1 tick
|
||||
pthread_mutex_lock(&cpu0_mutex);
|
||||
pthread_mutex_lock(&cpu0->mutex);
|
||||
cpu0->sim_ticks_left = 1;
|
||||
pthread_mutex_unlock(&cpu0_mutex);
|
||||
pthread_mutex_unlock(&cpu0->mutex);
|
||||
pthread_cond_signal(&cpu0->sim_condition);
|
||||
}
|
||||
else if(packet[0] == 'c')
|
||||
@@ -307,9 +309,9 @@ void gdbstub_thread_gdb()
|
||||
send(gdb_socket, "+", 1, 0);
|
||||
|
||||
// Continue simulation
|
||||
pthread_mutex_lock(&cpu0_mutex);
|
||||
pthread_mutex_lock(&cpu0->mutex);
|
||||
cpu0->sim_ticks_left = -1;
|
||||
pthread_mutex_unlock(&cpu0_mutex);
|
||||
pthread_mutex_unlock(&cpu0->mutex);
|
||||
pthread_cond_signal(&cpu0->sim_condition);
|
||||
}
|
||||
else gdbstub_send_unsupported();
|
||||
@@ -320,8 +322,8 @@ void gdbstub_cpu_watcher_thread()
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
pthread_mutex_lock(&cpu0_mutex);
|
||||
pthread_cond_wait(&cpu0->sim_condition, &cpu0_mutex);
|
||||
pthread_mutex_lock(&cpu0->mutex);
|
||||
pthread_cond_wait(&cpu0->sim_condition, &cpu0->mutex);
|
||||
if(!cpu0->sim_ticks_left && cpu0->sim_ticks_done > 0)
|
||||
{
|
||||
// Send back halt reason
|
||||
@@ -329,7 +331,7 @@ void gdbstub_cpu_watcher_thread()
|
||||
char* resp = "S05";
|
||||
gdbstub_send_packet(resp, 3);
|
||||
}
|
||||
pthread_mutex_unlock(&cpu0_mutex);
|
||||
pthread_mutex_unlock(&cpu0->mutex);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,7 +342,7 @@ void gdbstub_cpu_watcher_thread()
|
||||
static void gdbstub_handle_ctrlc()
|
||||
{
|
||||
// Halt the simulation
|
||||
pthread_mutex_lock(&cpu0_mutex);
|
||||
pthread_mutex_lock(&cpu0->mutex);
|
||||
cpu0->sim_ticks_left = 0;
|
||||
pthread_mutex_unlock(&cpu0_mutex);
|
||||
pthread_mutex_unlock(&cpu0->mutex);
|
||||
}
|
||||
|
@@ -2,6 +2,7 @@
|
||||
#define GDBSTUB_H
|
||||
|
||||
void gdbstub_start();
|
||||
void gdbstub_stop();
|
||||
void gdbstub_wait_for_connection();
|
||||
|
||||
#endif
|
||||
|
30
src/main.c
30
src/main.c
@@ -2,7 +2,9 @@
|
||||
#include "memory/memory.h"
|
||||
#include "bootloader/bootloader.h"
|
||||
#include "cpu/rv32cpu.h"
|
||||
#include "cpu/interrupt.h"
|
||||
#include "gdbstub/gdbstub.h"
|
||||
#include "devices/uart/uart.h"
|
||||
|
||||
char* CURRENT_NAME;
|
||||
|
||||
@@ -14,6 +16,9 @@ int main(int argc, char** argv)
|
||||
// Initialize the memory
|
||||
mem_init();
|
||||
|
||||
// Initialize UART (memory-mapped)
|
||||
uart_init();
|
||||
|
||||
// Bootload the file passed as argument
|
||||
uint32_t entry_point = bootload(file_path);
|
||||
|
||||
@@ -27,32 +32,19 @@ int main(int argc, char** argv)
|
||||
gdbstub_wait_for_connection();
|
||||
}
|
||||
|
||||
// Initialize timer for timer interrupt
|
||||
interrupt_timer_setup();
|
||||
|
||||
// CPU simulation : create cpu0 thread
|
||||
if(!gdbstub) cpu0->sim_ticks_left = -1; // Simulate forever
|
||||
pthread_t cpu0_thread;
|
||||
pthread_create(&cpu0_thread, 0, (void*) cpu_loop, cpu0);
|
||||
|
||||
// Wait for the simulation to end
|
||||
// Wait forever, until simulation end (which should be an ecall shutdown)
|
||||
pthread_join(cpu0_thread, 0);
|
||||
if(gdbstub)
|
||||
{
|
||||
pthread_join(cpu0_thread, 0);
|
||||
}
|
||||
else
|
||||
{
|
||||
while(1)
|
||||
{
|
||||
pthread_mutex_lock(&cpu0_mutex);
|
||||
pthread_cond_wait(&cpu0->sim_condition, &cpu0_mutex);
|
||||
if(!cpu0->sim_ticks_left && cpu0->sim_ticks_done > 0)
|
||||
{
|
||||
// Simulation ended
|
||||
break;
|
||||
}
|
||||
pthread_mutex_unlock(&cpu0_mutex);
|
||||
}
|
||||
|
||||
fprintf(stderr, "Simulation ended in a non-debug environment\n");
|
||||
return cpu0->regs.a0;
|
||||
gdbstub_stop();
|
||||
}
|
||||
|
||||
return 0;
|
||||
|
@@ -1,11 +1,297 @@
|
||||
#include "memory.h"
|
||||
#include "vriscv.h"
|
||||
#include "mmu.h"
|
||||
|
||||
uint8_t* memory;
|
||||
pthread_mutex_t memory_mutex;
|
||||
|
||||
#define MMIO_INSIDE(io, addr) (addr >= io->address && addr < io->address + (io->reg_size * io->reg_count))
|
||||
struct MMIO_ENTRY
|
||||
{
|
||||
uint32_t address;
|
||||
uint32_t reg_size;
|
||||
uint32_t reg_count;
|
||||
void* fn_write;
|
||||
void* fn_read;
|
||||
struct MMIO_ENTRY* next;
|
||||
};
|
||||
struct MMIO_ENTRY* mmio = 0;
|
||||
|
||||
void mem_init()
|
||||
{
|
||||
memory = malloc(memory_size);
|
||||
pthread_mutex_init(&memory_mutex, 0);
|
||||
memory = malloc(memory_size);
|
||||
pthread_mutex_init(&memory_mutex, 0);
|
||||
}
|
||||
|
||||
void mem_register_mmio(uint32_t address, uint32_t reg_size, uint32_t reg_count, void* fn_write, void* fn_read)
|
||||
{
|
||||
struct MMIO_ENTRY** current = &mmio;
|
||||
while(*current)
|
||||
current = &(*current)->next;
|
||||
|
||||
*current = malloc(sizeof(struct MMIO_ENTRY));
|
||||
(*current)->address = address;
|
||||
(*current)->reg_count = reg_count;
|
||||
(*current)->reg_size = reg_size;
|
||||
(*current)->fn_write = fn_write;
|
||||
(*current)->fn_read = fn_read;
|
||||
(*current)->next = 0;
|
||||
}
|
||||
|
||||
void mem_write8(uint32_t address, uint8_t value)
|
||||
{
|
||||
address = mmu_resolve(cpu0, WRITE, address);
|
||||
|
||||
// Look wether we are on an MMIO region
|
||||
struct MMIO_ENTRY* io = mmio;
|
||||
while(io)
|
||||
{
|
||||
if(MMIO_INSIDE(io, address))
|
||||
{
|
||||
if(io->reg_size == 1)
|
||||
{
|
||||
void (*fn_write)(uint32_t, uint8_t) = io->fn_write;
|
||||
fn_write(address, value);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "MEMORY: Invalid MMIO access of size 1 in a mapping of %u-sized registers\n", io->reg_size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
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;
|
||||
pthread_mutex_unlock(&memory_mutex);
|
||||
}
|
||||
|
||||
void mem_write16(uint32_t address, uint16_t value)
|
||||
{
|
||||
address = mmu_resolve(cpu0, WRITE, address);
|
||||
|
||||
// Look wether we are on an MMIO region
|
||||
struct MMIO_ENTRY* io = mmio;
|
||||
while(io)
|
||||
{
|
||||
if(MMIO_INSIDE(io, address))
|
||||
{
|
||||
if(io->reg_size == 2)
|
||||
{
|
||||
void (*fn_write)(uint32_t, uint16_t) = io->fn_write;
|
||||
fn_write(address, value);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "MEMORY: Invalid MMIO access of size 2 in a mapping of %u-sized registers\n", io->reg_size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
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;
|
||||
pthread_mutex_unlock(&memory_mutex);
|
||||
}
|
||||
|
||||
void mem_write32(uint32_t address, uint32_t value)
|
||||
{
|
||||
address = mmu_resolve(cpu0, WRITE, address);
|
||||
|
||||
// Look wether we are on an MMIO region
|
||||
struct MMIO_ENTRY* io = mmio;
|
||||
while(io)
|
||||
{
|
||||
if(MMIO_INSIDE(io, address))
|
||||
{
|
||||
if(io->reg_size == 4)
|
||||
{
|
||||
void (*fn_write)(uint32_t, uint32_t) = io->fn_write;
|
||||
fn_write(address, value);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "MEMORY: Invalid MMIO access of size 4 in a mapping of %u-sized registers\n", io->reg_size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
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;
|
||||
pthread_mutex_unlock(&memory_mutex);
|
||||
}
|
||||
|
||||
uint8_t mem_read8(uint32_t address)
|
||||
{
|
||||
address = mmu_resolve(cpu0, READ, address);
|
||||
|
||||
// Look wether we are on an MMIO region
|
||||
struct MMIO_ENTRY* io = mmio;
|
||||
while(io)
|
||||
{
|
||||
if(MMIO_INSIDE(io, address))
|
||||
{
|
||||
if(io->reg_size == 1)
|
||||
{
|
||||
uint8_t (*fn_read)(uint32_t) = io->fn_read;
|
||||
return fn_read(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "MEMORY: Invalid MMIO access of size 1 in a mapping of %u-sized registers\n", io->reg_size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
uint16_t mem_read16(uint32_t address)
|
||||
{
|
||||
address = mmu_resolve(cpu0, READ, address);
|
||||
|
||||
// Look wether we are on an MMIO region
|
||||
struct MMIO_ENTRY* io = mmio;
|
||||
while(io)
|
||||
{
|
||||
if(MMIO_INSIDE(io, address))
|
||||
{
|
||||
if(io->reg_size == 2)
|
||||
{
|
||||
uint16_t (*fn_read)(uint32_t) = io->fn_read;
|
||||
return fn_read(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "MEMORY: Invalid MMIO access of size 2 in a mapping of %u-sized registers\n", io->reg_size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
uint32_t mem_read32(uint32_t address)
|
||||
{
|
||||
address = mmu_resolve(cpu0, READ, address);
|
||||
|
||||
// Look wether we are on an MMIO region
|
||||
struct MMIO_ENTRY* io = mmio;
|
||||
while(io)
|
||||
{
|
||||
if(MMIO_INSIDE(io, address))
|
||||
{
|
||||
if(io->reg_size == 4)
|
||||
{
|
||||
uint32_t (*fn_read)(uint32_t) = io->fn_read;
|
||||
return fn_read(address);
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "MEMORY: Invalid MMIO access of size 4 in a mapping of %u-sized registers\n", io->reg_size);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
uint32_t mem_fetch(uint32_t address)
|
||||
{
|
||||
address = mmu_resolve(cpu0, INSTRUCTION_FETCH, address);
|
||||
|
||||
// Look wether we are on an MMIO region
|
||||
struct MMIO_ENTRY* io = mmio;
|
||||
while(io)
|
||||
{
|
||||
if(MMIO_INSIDE(io, address))
|
||||
{
|
||||
fprintf(stderr, "MEMORY: Trying to fetch an instruction inside an MMIO region !\n");
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@@ -5,8 +5,15 @@
|
||||
#include <pthread.h>
|
||||
|
||||
extern uint8_t* memory;
|
||||
extern pthread_mutex_t memory_mutex;
|
||||
|
||||
void mem_init();
|
||||
void mem_register_mmio(uint32_t address, uint32_t size, uint32_t reg_size, void* fn_write, void* fn_read);
|
||||
void mem_write8(uint32_t address, uint8_t value);
|
||||
void mem_write16(uint32_t address, uint16_t value);
|
||||
void mem_write32(uint32_t address, uint32_t value);
|
||||
uint8_t mem_read8(uint32_t address);
|
||||
uint16_t mem_read16(uint32_t address);
|
||||
uint32_t mem_read32(uint32_t address);
|
||||
uint32_t mem_fetch(uint32_t address);
|
||||
|
||||
#endif
|
||||
|
154
src/memory/mmu.c
Normal file
154
src/memory/mmu.c
Normal file
@@ -0,0 +1,154 @@
|
||||
#include "mmu.h"
|
||||
|
||||
#include "cpu/exception.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
extern uint8_t* memory;
|
||||
|
||||
// Memory Managment Unit implementation
|
||||
// We only support Sv32
|
||||
|
||||
#define PAGE_SIZE (4 * 1024) // 4KiB, 2^12B
|
||||
#define LEVELS 2
|
||||
#define PTE_SIZE 4 // sizeof(uint32_t)
|
||||
|
||||
// SATP CSR Register: [MODE(1bit) ASID(9bits) PPN(22bits)]
|
||||
#define SATP_MODE (1 << 31)
|
||||
#define SATP_MODE_BARE (0)
|
||||
#define SATP_MODE_SV32 (1 << 31)
|
||||
#define SATP_ASID (0x1FF << 22)
|
||||
#define SATP_PPN (0x3FFFFF)
|
||||
|
||||
// Page Table entry: [PPN[1](12bits) PPN[0](10bits) RSW(2bits) D A G U X W R V]
|
||||
#define PTE_PPN_1(pte) ((pte & 0xFFF00000) >> 20)
|
||||
#define PTE_PPN_0(pte) ((pte & 0x000FFC00) >> 10)
|
||||
#define PTE_PPN(pte) ((pte & 0xFFFFFC00) >> 10)
|
||||
#define PTE_RSW (0b11 << 8)
|
||||
#define PTE_D (1 << 7)
|
||||
#define PTE_A (1 << 6)
|
||||
#define PTE_G (1 << 5)
|
||||
#define PTE_U (1 << 4)
|
||||
#define PTE_X (1 << 3)
|
||||
#define PTE_W (1 << 2)
|
||||
#define PTE_R (1 << 1)
|
||||
#define PTE_V (1 << 0)
|
||||
|
||||
// Physical address: 34 bits [PPN[1](12bits, ) PPN[0](10bits) Offset(12bits)]
|
||||
// We only use 32-bits addresses, so the top 2 are always 0
|
||||
#define PADDR_PAGE_OFFSET (0x00000FFF)
|
||||
|
||||
// Virtual address: [VPN[1](10bits) VPN[0](10bits) Offset(12bits)]
|
||||
#define VADDR_VPN_1 (0xFFC00000)
|
||||
#define VADDR_VPN_0 (0x003FF000)
|
||||
#define VADDR_PAGE_OFFSET (0x00000FFF)
|
||||
|
||||
uint32_t mmu_scause_from_access(memory_access_type_t access_type)
|
||||
{
|
||||
switch(access_type)
|
||||
{
|
||||
case READ:
|
||||
return SCAUSE_LOAD_PAGE_FAULT;
|
||||
case WRITE:
|
||||
return SCAUSE_STORE_AMO_PAGE_FAULT;
|
||||
case INSTRUCTION_FETCH:
|
||||
return SCAUSE_INSTRUCTION_PAGE_FAULT;
|
||||
default:
|
||||
fprintf(stderr, "mmu_scause_from_access: invalid parameter\n");
|
||||
exit(EXIT_FAILURE);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t mmu_resolve(rv32_cpu_t* cpu, memory_access_type_t access_type, uint32_t vaddr)
|
||||
{
|
||||
// TODO: Make sure we are in S-mode or U-mode
|
||||
|
||||
// Check if MODE field is 'bare', meaning no mmu
|
||||
if((cpu->csr[CSR_SATP] & SATP_MODE) == SATP_MODE_BARE)
|
||||
return vaddr;
|
||||
|
||||
// fprintf(stderr, "MMU enabled on (virtual) address 0x%x resolution\n", vaddr);
|
||||
|
||||
uint32_t page_table = (cpu->csr[CSR_SATP] & SATP_PPN) * PAGE_SIZE;
|
||||
|
||||
// Resolve first-level page table entry
|
||||
uint32_t vpn_1 = (vaddr & VADDR_VPN_1) >> 22;
|
||||
uint32_t pte_address = page_table + vpn_1 * PTE_SIZE;
|
||||
uint32_t pte = *((uint32_t*) (&memory[pte_address]));
|
||||
|
||||
if(!(pte & PTE_V))
|
||||
{
|
||||
// Invalid PTE
|
||||
exception_trigger(cpu, mmu_scause_from_access(access_type), vaddr);
|
||||
}
|
||||
|
||||
if((pte & PTE_R) || (pte & PTE_W) || (pte & PTE_X))
|
||||
{
|
||||
// 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);
|
||||
paddr |= (vaddr & VADDR_VPN_0);
|
||||
paddr |= vaddr & VADDR_PAGE_OFFSET;
|
||||
|
||||
return paddr;
|
||||
}
|
||||
|
||||
// PTE is a pointer to next level of page table
|
||||
page_table = PTE_PPN(pte) * PAGE_SIZE;
|
||||
|
||||
// Resolve second-level page table entry
|
||||
uint32_t vpn_0 = (vaddr & VADDR_VPN_0) >> 12;
|
||||
pte_address = page_table + vpn_0 * PTE_SIZE;
|
||||
pte = *((uint32_t*) (&memory[pte_address]));
|
||||
|
||||
if(!(pte & PTE_V))
|
||||
{
|
||||
// Invalid PTE
|
||||
exception_trigger(cpu, mmu_scause_from_access(access_type), vaddr);
|
||||
}
|
||||
|
||||
// This must be a leaf PTE, as Sv32 only supports 2-level mappings
|
||||
// This is a 4 KiB page
|
||||
if(!((pte & PTE_R) || (pte & PTE_W) || (pte & PTE_X)))
|
||||
{
|
||||
fprintf(stderr, "Error: Pointer second-level Page Table Entry 0x%x at 0x%x while resolving virtual address 0x%x\n", pte, pte_address, vaddr);
|
||||
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);
|
||||
paddr |= (PTE_PPN_0(pte) << 12);
|
||||
paddr |= vaddr & VADDR_PAGE_OFFSET;
|
||||
|
||||
return paddr;
|
||||
}
|
16
src/memory/mmu.h
Normal file
16
src/memory/mmu.h
Normal file
@@ -0,0 +1,16 @@
|
||||
#ifndef MMU_H
|
||||
#define MMU_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include "cpu/rv32cpu.h"
|
||||
|
||||
typedef enum MEMORY_ACCESS_TYPE
|
||||
{
|
||||
READ,
|
||||
WRITE,
|
||||
INSTRUCTION_FETCH
|
||||
} memory_access_type_t;
|
||||
|
||||
uint32_t mmu_resolve(rv32_cpu_t* cpu, memory_access_type_t access_type, uint32_t vaddr);
|
||||
|
||||
#endif
|
@@ -1,6 +0,0 @@
|
||||
#ifndef MMU_H
|
||||
#define MMU_H
|
||||
|
||||
#define mmu_translate(vaddr) (vaddr)
|
||||
|
||||
#endif
|
12
src/option.c
12
src/option.c
@@ -8,6 +8,7 @@
|
||||
uint64_t memory_size = 512 * 1024 * 1024;
|
||||
char* file_path;
|
||||
bool gdbstub = false;
|
||||
bool trace = false;
|
||||
|
||||
static void print_usage();
|
||||
static void print_help();
|
||||
@@ -100,6 +101,11 @@ void parse_options(int argc, char** argv)
|
||||
gdbstub = true;
|
||||
break;
|
||||
}
|
||||
case 't':
|
||||
{
|
||||
trace = true;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
fprintf(stderr, "Error: Unknown short option -%c\n", argv[i][k]);
|
||||
@@ -161,6 +167,11 @@ static int parse_long_option(char* str, char* argq)
|
||||
gdbstub = true;
|
||||
return 0;
|
||||
}
|
||||
else if(strcmp(str, "trace") == 0)
|
||||
{
|
||||
trace = true;
|
||||
return 0;
|
||||
}
|
||||
else
|
||||
{
|
||||
fprintf(stderr, "Error: Unknown long option " OPTION_SEPARATOR OPTION_SEPARATOR "%s\n", str);
|
||||
@@ -181,6 +192,7 @@ static void print_help()
|
||||
printf(" " OPTION_SEPARATOR "h, " OPTION_SEPARATOR "?, --help\t\tPrint this help message\n");
|
||||
printf(" " OPTION_SEPARATOR "v, --version\t\t\tPrint version information\n");
|
||||
printf(" " OPTION_SEPARATOR "m, --memory\t\t\tSet the simulated memory size, in MiB (max 4096)\n");
|
||||
printf(" " OPTION_SEPARATOR "t, --trace\t\t\tTrace executed instructions in stdout\n");
|
||||
printf(" " OPTION_SEPARATOR "s, --gdb-stub\t\tLaunch a gdb stub server (remote gdb debugging)\n");
|
||||
}
|
||||
|
||||
|
@@ -15,6 +15,7 @@ extern char* CURRENT_NAME;
|
||||
extern size_t memory_size;
|
||||
extern char* file_path;
|
||||
extern bool gdbstub;
|
||||
extern bool trace;
|
||||
void parse_options(int argc, char** argv);
|
||||
|
||||
#endif
|
||||
|
@@ -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))
|
||||
|
||||
|
@@ -1,4 +1,6 @@
|
||||
.include "exit_return.s"
|
||||
|
||||
.global _start
|
||||
_start:
|
||||
addi a0, zero, 0xBA
|
||||
ebreak
|
||||
exret
|
||||
|
10
tests/beq.s
10
tests/beq.s
@@ -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
|
||||
|
33
tests/blt.s
Normal file
33
tests/blt.s
Normal file
@@ -0,0 +1,33 @@
|
||||
.include "exit_return.s"
|
||||
|
||||
.global _start
|
||||
_start:
|
||||
# Set base value of a0 to 'test failed'
|
||||
addi a0, zero, 1
|
||||
|
||||
# Set A, B values of t0/t1, to test less than
|
||||
addi t0, zero, 1
|
||||
addi t1, zero, 2
|
||||
blt t1, t0, lt0
|
||||
|
||||
# Inequality passed, now test with jump
|
||||
blt t0, t1, lt1
|
||||
|
||||
# On failure, return
|
||||
exret
|
||||
|
||||
ltNeg:
|
||||
# All passed
|
||||
addi a0, zero, 0
|
||||
exret
|
||||
|
||||
lt0:
|
||||
# Inequality failed
|
||||
exret
|
||||
|
||||
lt1:
|
||||
# Inequality passed ; now try with negative numbers
|
||||
addi t0, zero, -1
|
||||
addi t1, zero, -2
|
||||
blt t1, t0, ltNeg
|
||||
exret
|
4
tests/exit_return.s
Normal file
4
tests/exit_return.s
Normal file
@@ -0,0 +1,4 @@
|
||||
.macro exret
|
||||
addi a7, zero, 0x8
|
||||
ecall
|
||||
.endm
|
16
tests/jal.s
16
tests/jal.s
@@ -1,3 +1,5 @@
|
||||
.include "exit_return.s"
|
||||
|
||||
.global _start
|
||||
_start:
|
||||
# Set base value of a0 to 'test failed'
|
||||
@@ -8,14 +10,18 @@ _start:
|
||||
addi t0, t0, 12
|
||||
# Jump and link
|
||||
jal ra, fn0
|
||||
ebreak
|
||||
exret
|
||||
|
||||
fnNeg:
|
||||
# All good
|
||||
addi a0, zero, 0
|
||||
exret
|
||||
|
||||
fn0:
|
||||
# Check ra value with our t0 construct
|
||||
beq t0, ra, eq0
|
||||
ebreak
|
||||
exret
|
||||
|
||||
eq0:
|
||||
# All good
|
||||
addi a0, zero, 0
|
||||
ebreak
|
||||
# Try to jump back to a negative offset
|
||||
jal ra, fnNeg
|
||||
|
31
tests/jalr.s
31
tests/jalr.s
@@ -1,3 +1,5 @@
|
||||
.include "exit_return.s"
|
||||
|
||||
.global _start
|
||||
_start:
|
||||
# Set base value of a0 to 'test failed'
|
||||
@@ -12,16 +14,20 @@ _start:
|
||||
# Try to call two imbricated functions
|
||||
jal fn0
|
||||
|
||||
auipc t0, 0
|
||||
jalr ra, 12(t0)
|
||||
# Jump to 'just_after'
|
||||
auipc ra, 0
|
||||
jalr 16(ra)
|
||||
|
||||
ebreak
|
||||
# Jump far to test jalr with negative offset
|
||||
jal fnfar
|
||||
|
||||
# just_after : address is 12 bytes after auipc
|
||||
exret
|
||||
|
||||
# just_after : address is 16 bytes after auipc
|
||||
just_after:
|
||||
addi a0, zero, 0
|
||||
# ra must still be the old address
|
||||
ret
|
||||
ebreak
|
||||
exret
|
||||
|
||||
# fn0 : function that calls fn1 and returns
|
||||
fn0:
|
||||
@@ -38,9 +44,18 @@ fn0:
|
||||
addi sp, sp, 4
|
||||
|
||||
ret
|
||||
ebreak
|
||||
exret
|
||||
|
||||
# fn1 : just return
|
||||
fn1:
|
||||
ret
|
||||
ebreak
|
||||
exret
|
||||
|
||||
fnneg:
|
||||
addi a0, zero, 0
|
||||
exret
|
||||
|
||||
fnfar:
|
||||
auipc ra, 0
|
||||
jalr -8(ra)
|
||||
exret
|
||||
|
40
tests/mulh.s
Normal file
40
tests/mulh.s
Normal 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
|
@@ -1,5 +1,7 @@
|
||||
.include "exit_return.s"
|
||||
|
||||
.global _start
|
||||
_start:
|
||||
addi a1, zero, 0xBA
|
||||
mv a0, a1
|
||||
ebreak
|
||||
exret
|
||||
|
14
tests/swlw.s
14
tests/swlw.s
@@ -1,3 +1,5 @@
|
||||
.include "exit_return.s"
|
||||
|
||||
.global _start
|
||||
_start:
|
||||
# Set base value of a0 to 'test failed'
|
||||
@@ -8,14 +10,20 @@ _start:
|
||||
addi t1, zero, 0xBA
|
||||
sw t1, 4(t0)
|
||||
|
||||
# Load Word at 0x104 in t0
|
||||
# Load Word at 0x104 in t0 and t2, using 2 different addressing modes
|
||||
addi t2, t0, 8
|
||||
lw t2, -4(t2)
|
||||
lw t0, 4(t0)
|
||||
|
||||
# Compare
|
||||
beq t0, t1, good
|
||||
|
||||
ebreak
|
||||
exret
|
||||
|
||||
good:
|
||||
beq t0, t2, xtragood
|
||||
exret
|
||||
|
||||
xtragood:
|
||||
addi a0, zero, 0
|
||||
ebreak
|
||||
exret
|
||||
|
@@ -25,6 +25,8 @@ test() {
|
||||
test "ADDI : Add Immediate " "../build/tests/addi " 186
|
||||
test "MV : Move registers " "../build/tests/mv " 186
|
||||
test "BEQ : Branch EQual " "../build/tests/beq " 0
|
||||
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
|
||||
|
Reference in New Issue
Block a user