Compare commits

..

2 Commits

Author SHA1 Message Date
Anton Lydike af545587df rv32i: respect conf.debug_instruction setting 2 years ago
Anton Lydike 090600ef58 misc: fixed a typo in a docstring 2 years ago

@ -1,2 +0,0 @@
# introduces black formatting
5515c7795cfd690d346aad10ce17b30acf914648

@ -1,49 +0,0 @@
# This workflow will install Python dependencies, run tests and lint with a single version of Python
# For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions
name: CI - Python-based Testing
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.8','3.10']
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Upgrade pip
run: |
pip install --upgrade pip
- name: Install the package locally
run: pip install -r requirements.txt -r requirements-dev.txt
- name: Test with pytest
run: |
pytest -W error
- name: Test with lit
run: |
lit -v test/filecheck
#- name: Execute lit tests
# run: |
# export PYTHONPATH=$(pwd)
# lit -v tests/filecheck/

@ -1,34 +0,0 @@
# This workflow check the format all files in the repository
# * It checks that all nonempty files have a newline at the end
# * It checks that there are no whitespaces at the end of lines
# * It checks that Python files are formatted with black
name: Code Formatting
on:
pull_request:
push:
branches: [master]
jobs:
code-formatting:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ['3.10']
steps:
- uses: actions/checkout@v3
- name: Set up Python
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
- name: Upgrade pip
run: |
pip install --upgrade pip
- name: Run code formatting checks with pre-commit
uses: pre-commit/action@v3.0.0

@ -1,98 +0,0 @@
name: Deploy JupiterLite Page
on:
# Trigger the workflow on push or pull request,
# but only for the master branch
push:
branches:
- master
pull_request:
branches:
- master
jobs:
build:
runs-on: ubuntu-latest
steps:
- name: Checkout RiscEmu
uses: actions/checkout@v3
with:
path: riscemu
- name: Setup Python
uses: actions/setup-python@v4
with:
python-version: '3.10'
- name: Install dependencies
run: |
python -m pip install jupyterlite[all] libarchive-c build pyodide-build
- name: Build RiscEmu source distribution
run: |
cd riscemu
python setup.py sdist
# Pyodide is cached, so cloned only if not present in the cache, otherwise
# just checked out to whatever desired version and partially rebuilt.
- name: Restore cached Pyodide tree
id: cache-pyodide
uses: actions/cache@v3
with:
path: pyodide
key: pyodide
- name: Clone pyodide if not cached
if: steps.cache-pyodide.outputs.cache-hit != 'true'
run: git clone https://github.com/pyodide/pyodide.git
# Clean the xDSL and FrozenList package folders, generate their skeletons
# and do the necessary updates before building.
- name: Build custom Pyodide distribution
run: |
cd pyodide
git fetch --all
git checkout 0.22.0a3
python -m pip install -r requirements.txt
sudo apt update && sudo apt install f2c
rm -rf packages/riscemu
pyodide skeleton pypi riscemu
PYODIDE_PACKAGES="riscemu" make
- name: Build the JupyterLite site
run: |
mkdir content
cp riscemu/docs/* content -r
cp riscemu/examples content -r
rm -rf pyodide/pyodide
mkdir pyodide/pyodide
mv pyodide/dist pyodide/pyodide/pyodide
python -m jupyter lite build --contents content --pyodide pyodide/pyodide
- name: Upload artifact
uses: actions/upload-pages-artifact@v1
with:
path: ./_output
deploy:
needs: build
if: github.ref == 'refs/heads/master' && github.event_name == 'push'
permissions:
pages: write
id-token: write
environment:
name: github-pages
url: ${{ steps.deployment.outputs.page_url }}
runs-on: ubuntu-latest
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@v1

@ -1,22 +0,0 @@
name: Upload to PyPI
on:
release:
types: [published]
jobs:
deploy:
runs-on: ubuntu-latest
environment: publishing
permissions:
# IMPORTANT: this permission is mandatory for trusted publishing
id-token: write
steps:
# install build package and build dist
- name: Build distribution
run: >-
python3 -m pip install build --user &&
python3 -m build --sdist --wheel --outdir dist/
# retrieve your distributions here
- name: Publish package distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1

10
.gitignore vendored

@ -1,8 +1,6 @@
venv
__pycache__ __pycache__
.mypy_cache .mypy_cache
.pytest_cache dist/
riscemu.egg-info
/venv* build/
/dist
/riscemu.egg-info
/build

@ -3,4 +3,4 @@
<option name="USE_PROJECT_PROFILE" value="false" /> <option name="USE_PROJECT_PROFILE" value="false" />
<version value="1.0" /> <version value="1.0" />
</settings> </settings>
</component> </component>

@ -1,4 +1,4 @@
<?xml version="1.0" encoding="UTF-8"?> <?xml version="1.0" encoding="UTF-8"?>
<project version="4"> <project version="4">
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (riscemu)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (riscemu)" project-jdk-type="Python SDK" />
</project> </project>

@ -5,4 +5,4 @@
<module fileurl="file://$PROJECT_DIR$/.idea/riscemu.iml" filepath="$PROJECT_DIR$/.idea/riscemu.iml" /> <module fileurl="file://$PROJECT_DIR$/.idea/riscemu.iml" filepath="$PROJECT_DIR$/.idea/riscemu.iml" />
</modules> </modules>
</component> </component>
</project> </project>

@ -2,7 +2,6 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$"> <content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/riscemu" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" /> <sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/venv" /> <excludeFolder url="file://$MODULE_DIR$/venv" />
<excludeFolder url="file://$MODULE_DIR$/dist" /> <excludeFolder url="file://$MODULE_DIR$/dist" />
@ -10,7 +9,7 @@
<excludeFolder url="file://$MODULE_DIR$/riscemu.egg-info" /> <excludeFolder url="file://$MODULE_DIR$/riscemu.egg-info" />
<excludeFolder url="file://$MODULE_DIR$/build" /> <excludeFolder url="file://$MODULE_DIR$/build" />
</content> </content>
<orderEntry type="jdk" jdkName="Python 3.10 (riscemu)" jdkType="Python SDK" /> <orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

@ -3,4 +3,4 @@
<component name="VcsDirectoryMappings"> <component name="VcsDirectoryMappings">
<mapping directory="$PROJECT_DIR$" vcs="Git" /> <mapping directory="$PROJECT_DIR$" vcs="Git" />
</component> </component>
</project> </project>

@ -1,12 +0,0 @@
# See https://pre-commit.com for more information
# See https://pre-commit.com/hooks.html for more hooks
repos:
- repo: https://github.com/pre-commit/pre-commit-hooks
rev: v3.2.0
hooks:
- id: trailing-whitespace
- id: end-of-file-fixer
- repo: https://github.com/psf/black
rev: 23.3.0
hooks:
- id: black

@ -1,29 +1,5 @@
# Changelog # Changelog
## 2.1.1
- Bugfix: Fix some errors in the RV32F implementation (thanks @adutilleul)
- Bugfix: Fix how floats are printed in the register view (thanks @KGrykiel)
- Bugfix: Fix missing support for infinite registers in load/store ins (thanks @superlopuh)
## 2.1.0
- Added a very basic libc containing a `crt0.s`, and a few functions
such as `malloc`, `rand`, and `memcpy`.
- Added a subset of the `mmap2` syscall (code 192) to allocate new memory
- Refactored the launching code to improve using riscemu from code
- Added an option to start with the provided libc: `-o libc`
- Added floating point support (enabled by default). The RV32F extension is now available
## 2.0.5
- Added unlimited register mode with `-o unlimited_regs`
## 2.0.4
- Bugfix: fix a sign issue in instruction parsing for `rd, rs, rs` format
- Bugfix: respect `conf.debug_instruction` setting
## 2.0.3 - 2022-04-18 ## 2.0.3 - 2022-04-18
- Syscalls: cleaned up formatting and added instructions for extensions - Syscalls: cleaned up formatting and added instructions for extensions
@ -33,7 +9,7 @@
- Fixed bug where wrong parts of section would be printed in mmu.dump() - Fixed bug where wrong parts of section would be printed in mmu.dump()
- Removed tests for bind_twos_complement as the function is now redundant with the introduction of Int32 - Removed tests for bind_twos_complement as the function is now redundant with the introduction of Int32
- Fixed address translation error for sections without symbols - Fixed address translation error for sections without symbols
- Changed verbosity level at which start and end of CPU are printed, added prints for start and stack loading - Changed verbosity level at which start and end of CPU are printed, added prints for start and stack loading
## 2.0.2 ## 2.0.2
@ -47,6 +23,6 @@
## 2.0.0 ## 2.0.0
- Correct handling of 32 bit overflows and underflows - Correct handling of 32 bit overflows and underflows
- Complete revamp of internal data structures - Complete revamp of internal data structures
- Completely reworked how assembly is parsed - Completely reworked how assembly is parsed

@ -18,4 +18,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

@ -10,7 +10,7 @@ This emulator contains:
* RISC-V Assembly loader * RISC-V Assembly loader
* Emulation for most parts of the basic RISC-V instruction set and the M and A extensions * Emulation for most parts of the basic RISC-V instruction set and the M and A extensions
* Naive memory emulator * Naive memory emulator
* Basic implementation of some syscalls * Basic implementation of some syscalls
* A debugging environment * A debugging environment
## Installation: ## Installation:
@ -31,9 +31,7 @@ Hello world
Program exited with code 0 Program exited with code 0
``` ```
If you want to run it from a python script, here is [an online demo](https://AntonLydike.github.io/riscemu/lab/index.html?path=PythonDemo.ipynb). The [`read` syscall](docs/syscalls.md) defaults to readline behaviour. Reading "true chunks" (ignoring newlines) is currently not supported.
The [`read` syscall](docs/syscalls.md) defaults to readline behaviour. Reading "true chunks" (ignoring newlines) is currently not supported.
See the docs on [asembly](docs/assembly.md) for more detail on how to write assembly code for this emulator. See the docs on [asembly](docs/assembly.md) for more detail on how to write assembly code for this emulator.
See the [list of implemented syscalls](docs/syscalls.md) for more details on how to syscall. See the [list of implemented syscalls](docs/syscalls.md) for more details on how to syscall.
@ -71,10 +69,10 @@ disable_io Disallow reading/writing from stdin/stdout/stderr
--instruction-sets INSTRUCTION_SETS: (-is) --instruction-sets INSTRUCTION_SETS: (-is)
A list of comma separated instruction sets you want to load: A list of comma separated instruction sets you want to load:
Currently implemented: RV32I, RV32M Currently implemented: RV32I, RV32M
``` ```
If multiple files are specified, all are loaded into memeory, but only the last one is executed. This might be improved If multiple files are specified, all are loaded into memeory, but only the last one is executed. This might be improved
later, maybe the `_init` section of each binary is executed before the main loop starts? later, maybe the `_init` section of each binary is executed before the main loop starts?
If `stack_size` is greater than zero, a stack is allocated and initialized, with the `sp` register pointing to the end of the stack. If `stack_size` is greater than zero, a stack is allocated and initialized, with the `sp` register pointing to the end of the stack.
@ -90,17 +88,17 @@ See [docs/debugging.md](docs/debugging.md) for more info.
Check out the [documentation](https://riscemu.readthedocs.io/en/latest/riscemu.html). Check out the [documentation](https://riscemu.readthedocs.io/en/latest/riscemu.html).
## Accessing local documentation: ## Accessing local documentation:
To generate your local documentation, first install everything in `sphinx-docs/requirements.txt`. Then run `./generate-docs.sh`, which will To generate your local documentation, first install everything in `sphinx-docs/requirements.txt`. Then run `./generate-docs.sh`, which will
generate and make all doc files for you. Finally, you can open the docs locall by runnint `open sphinx-docs/build/html/index.html`. generate and make all doc files for you. Finally, you can open the docs locall by runnint `open sphinx-docs/build/html/index.html`.
## Resources: ## Resources:
* RISC-V Programmers Handbook: https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md
* Pseudo ops: https://www.codetd.com/article/8981522 * Pseudo ops: https://www.codetd.com/article/8981522
* detailed instruction definition: https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html#add * detailed instruction definition: https://msyksphinz-self.github.io/riscv-isadoc/html/rvi.html#add
* RISC-V reference card: https://www.cl.cam.ac.uk/teaching/1617/ECAD+Arch/files/docs/RISCVGreenCardv8-20151013.pdf * RISC-V reference card: https://www.cl.cam.ac.uk/teaching/1617/ECAD+Arch/files/docs/RISCVGreenCardv8-20151013.pdf
## TODO: ## TODO:
* Correctly handle 12 and 20 bit immediate (currently not limited to bits at all) * Correctly handle 12 and 20 bit immediate (currently not limited to bits at all)
* Add a cycle limit to the options and CPU to catch infinite loops * Add a cycle limit to the options and CPU to catch infinite loops
* Move away from `print` and use `logging.logger` instead * Move away from `print` and use `logging.logger` instead
* Writer proper tests * Writer proper tests

@ -1,53 +0,0 @@
{
"cells": [
{
"cell_type": "markdown",
"metadata": {},
"source": [
"# Using RiscEmu from Python\n",
"\n",
"Here is how you can run some assembly through RiscEmu from Python.\n",
"\n",
"This example is using [this fibonacci assembly](examples/fibs.asm)."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"from riscemu import RunConfig, UserModeCPU, RV32I, RV32M, AssemblyFileLoader\n",
"\n",
"cfg = RunConfig(debug_instruction=False, verbosity=50)\n",
"cpu = UserModeCPU((RV32I, RV32M), cfg)\n",
"\n",
"loader = AssemblyFileLoader.instantiate('examples/fibs.asm', [])\n",
"cpu.load_program(loader.parse())\n",
"\n",
"cpu.launch(cpu.mmu.programs[-1], True)"
]
}
],
"metadata": {
"kernelspec": {
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
"codemirror_mode": {
"name": "ipython",
"version": 3
},
"file_extension": ".py",
"mimetype": "text/x-python",
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.10.8"
}
},
"nbformat": 4,
"nbformat_minor": 4
}

@ -30,8 +30,8 @@ The following pseudo-ops are implemented as of yet:
* `.global <name>` mark symbol `<name>` as a global symbol. It is available from all loaded programs * `.global <name>` mark symbol `<name>` as a global symbol. It is available from all loaded programs
* `.align <bytes>` currently a nop as cpu does not care about alignment as of now * `.align <bytes>` currently a nop as cpu does not care about alignment as of now
## Sections: ## Sections:
Currently only these three sections are supported: Currently only these three sections are supported:
* `data` read-write data (non-executable) * `data` read-write data (non-executable)
* `rodata` read-only data (non-executable) * `rodata` read-only data (non-executable)
* `text` executable data (read-only) * `text` executable data (read-only)

@ -37,7 +37,7 @@ uses the `ebreak` instruction to open the debugger. Let's run it and see what ha
[MMU] Successfully loaded: LoadedExecutable[examples/fibs.asm](base=0x00000100, size=72bytes, sections=data text, run_ptr=0x00000138) [MMU] Successfully loaded: LoadedExecutable[examples/fibs.asm](base=0x00000100, size=72bytes, sections=data text, run_ptr=0x00000138)
[CPU] Started running from 0x00000138 (examples/fibs.asm) [CPU] Started running from 0x00000138 (examples/fibs.asm)
Debug instruction encountered at 0x0000013C Debug instruction encountered at 0x0000013C
>>> >>>
``` ```
In this interactive session, you have access to the cpu, registers, memory and syscall interface. You can look into everything, In this interactive session, you have access to the cpu, registers, memory and syscall interface. You can look into everything,
@ -58,8 +58,8 @@ and most common tasks should have helper methods for them.
* `get(name)` get register content * `get(name)` get register content
* `set(name, val)` set register content * `set(name, val)` set register content
* `cpu`: * `cpu`:
* The CPU has the `pc` attribute and `cycle` attribute. Others won't be useful in this context. * The CPU has the `pc` attribute and `cycle` attribute. Others won't be useful in this context.
**Available helpers are:** **Available helpers are:**
* `dump(regs | addr)` dumps either registers or memory address * `dump(regs | addr)` dumps either registers or memory address
@ -71,4 +71,4 @@ and most common tasks should have helper methods for them.
Example: Example:
![debuggin the fibs program](debug-session.png) ![debuggin the fibs program](debug-session.png)

@ -1,19 +1,19 @@
# Internal Structure # Internal Structure
## Loading assembly files: ## Loading assembly files:
In order to load an assembly file, you need to instantiate a CPU with the capabilities you want. Loading an assembly In order to load an assembly file, you need to instantiate a CPU with the capabilities you want. Loading an assembly
file is the done in multiple steps: file is the done in multiple steps:
* An `RiscVInput` is created, this represents the file internally * An `RiscVInput` is created, this represents the file internally
* An `RiscVTokenizer` is created by calling `cpu.get_tokenizer()`. * An `RiscVTokenizer` is created by calling `cpu.get_tokenizer()`.
* The input is tokenized by calling `.tokenize()` on the tokenizer. * The input is tokenized by calling `.tokenize()` on the tokenizer.
* The tokens can then be converted to an Executable, this will then * The tokens can then be converted to an Executable, this will then
hold all the information such as name, sections, symbols, etc. hold all the information such as name, sections, symbols, etc.
This is done by creating an `ExecutableParser(tk: RiscVTokenizer)` This is done by creating an `ExecutableParser(tk: RiscVTokenizer)`
and the calling `parse()`. and the calling `parse()`.
* Now you have a representation of the assembly file that can be loaded * Now you have a representation of the assembly file that can be loaded
into memory by calling `cpu.load(executable)`, this will internally into memory by calling `cpu.load(executable)`, this will internally
construct a `LoadedExecutable`, which represents the actual memory construct a `LoadedExecutable`, which represents the actual memory
regions the executable contains (and some meta information such as regions the executable contains (and some meta information such as
symbols). symbols).
@ -30,3 +30,4 @@ Creating a cpu with certain instruction sets is done by passing the CPU construc
``` ```
cpu = CPU(config, [RV32I, RV32M]) cpu = CPU(config, [RV32I, RV32M])
``` ```

@ -13,3 +13,4 @@ You can include the libraries by adding them as arguments (before your main asse
``` ```
These libraries are no where near a stable state, so documentation will be scarce. Your best bet would be to `grep` for functionality. Sorry! These libraries are no where near a stable state, so documentation will be scarce. Your best bet would be to `grep` for functionality. Sorry!

@ -7,7 +7,7 @@ Performing a syscall is quite simple:
; set syscall args: ; set syscall args:
addi a0, zero, 1 ; exit with code 1 addi a0, zero, 1 ; exit with code 1
; invode syscall handler ; invode syscall handler
scall scall
``` ```
The global symbols (e.g. `SCALL_READ`) are loaded by default. If you specify the option `no_syscall_symbols`, they will be omitted. The global symbols (e.g. `SCALL_READ`) are loaded by default. If you specify the option `no_syscall_symbols`, they will be omitted.
@ -47,4 +47,4 @@ Requires flag `--scall-fs` to be set to True
# Extending these syscalls # Extending these syscalls
You can implement your own syscall by adding its code to the `SYSCALLS` dict in the [riscemu/syscalls.py](../riscemu/syscall.py) file, creating a mapping of a syscall code to a name, and then implementing that syscall name in the SyscallInterface class further down that same file. Each syscall method should have the same signature: `read(self, scall: Syscall)`. The `Syscall` object gives you access to the cpu, through which you can access registers and memory. You can look at the `read` or `write` syscalls for further examples. You can implement your own syscall by adding its code to the `SYSCALLS` dict in the [riscemu/syscalls.py](../riscemu/syscall.py) file, creating a mapping of a syscall code to a name, and then implementing that syscall name in the SyscallInterface class further down that same file. Each syscall method should have the same signature: `read(self, scall: Syscall)`. The `Syscall` object gives you access to the cpu, through which you can access registers and memory. You can look at the `read` or `write` syscalls for further examples.

@ -1,23 +1,25 @@
// Example program (c) by Anton Lydike ; Example program (c) by Anton Lydike
// this calculates the fibonacci sequence and stores it in ram ; this calculates the fibonacci sequence and stores it in ram
.data .data
fibs: .space 56 fibs: .space 56
.text .text
main: main:
addi s1, zero, 0 // storage index addi s1, zero, 0 ; storage index
addi s2, zero, 56 // last storage index addi s2, zero, 56 ; last storage index
addi t0, zero, 1 // t0 = F_{i} addi t0, zero, 1 ; t0 = F_{i}
addi t1, zero, 1 // t1 = F_{i+1} addi t1, zero, 1 ; t1 = F_{i+1}
ebreak ; launch debugger
loop: loop:
sw t0, fibs(s1) // save sw t0, fibs(s1) ; save
add t2, t1, t0 // t2 = F_{i+2} add t2, t1, t0 ; t2 = F_{i+2}
addi t0, t1, 0 // t0 = t1 addi t0, t1, 0 ; t0 = t1
addi t1, t2, 0 // t1 = t2 addi t1, t2, 0 ; t1 = t2
addi s1, s1, 4 // increment storage pointer addi s1, s1, 4 ; increment storage pointer
blt s1, s2, loop // loop as long as we did not reach array length blt s1, s2, loop ; loop as long as we did not reach array length
// exit gracefully ; exit gracefully
ebreak ; launch debugger
addi a0, zero, 0 addi a0, zero, 0
addi a7, zero, 93 addi a7, zero, 93
scall // exit with code 0 scall ; exit with code 0

@ -0,0 +1,154 @@
; string operations in RISC-V Assembly
; All function restore all registers after use (even caller-saved ones)
.global NULL
; A global constant, points to the empty string
.global strlen
; size_t libstr_strlen(char* str)
; return the length of str
.global strncpy
; char *strncpy(char *dest, const char *src, size_t n)
; copy n bytes from source into dest. If source ends before n bytes, the rest is filled with null-bytes
; returns pointer to dest
.global strcpy
; char *strncpy(char *dest, const char *src)
; copy string src into dest, including null terminator
; returns pointer to dest
.global memchr
; void *memchr(const void *str, char c, size_t n)
; search vor the first occurance of c in str
; returns a pointer to the first occurance, or NULL
.global memset
; void *memset(void *str, char c, size_t n)
; copies the character c to the first n characters of str.
;.global memcmp
;.global memcpy
;.global strcat
; Create NullPtr constant
.set NULL, 0x00
.text
strlen:
; size_t strlen(char* str)
; push s1, s2 to the stack
sw s1, sp, -4
sw s2, sp, -8
; since no subroutines are called, we don't need to increment or decrement the sp
addi s2, zero, -1 ; length (since null byte is counted by this method, we return len - 1)
__strlen_loop:
lb s1, a0, 0 ; read character
addi s2, s2, 1 ; increment number bytes read
addi a0, a0, 1
bne s1, zero, __strlen_loop
; we are done, set return value in a0
add a0, zero, s2
; pop s1, s2, from stack
lw s1, sp, -4
lw s2, sp, -8
ret
strncpy:
; char *strncpy(char *dest, const char *src, size_t n)
; copy size bytes from source to dest
sw s1, sp, -4 ; push s1 to the stack
sw s2, sp, -8 ; push s1 to the stack
add s1, a0, zero ; save dest pointer for return
__strncpy_loop:
beq a2, zero, __strncpy_end
; copy byte
lb s2, a1, 0 ; read first byte from src
sb s2, a0, 0 ; write first byte to dest
; increment pointers
addi a0, a0, 1
addi a1, a1, 1
; one less byte to copy
addi a2, a2, -1
; if we read the terminating byte, jump to fill code
beq s2, zero, __strncpy_fill
; otherwise continue copying
j __strncpy_loop
__strncpy_fill:
; fill remaining space with 0 bytes
; if no bytes left, stop filling
beq a2, zero, __strncpy_end
sb zero, a0, 0
addi a0, a0, 1
addi a2, a2, -1
j __strncpy_fill
__strncpy_end:
; set return value
add a0, zero, s1
; pop s1, s2 from stack
lw s1, sp, -4
lw s2, sp, -8
ret
strcpy:
; char *strncpy(char *dest, const char *src)
sw s1, sp, -4 ; push s1 to the stack
sw s2, sp, -8 ; push s1 to the stack
add s1, a0, zero ; save dest pointer for return
__strcpy_loop:
; copy byte
lb s2, a1, 0 ; read first byte from src
sb s2, a0, 0 ; write first byte to dest
; increment pointers
addi a0, a0, 1
addi a1, a1, 1
bne s2, zero, __strcpy_loop
; we are done copying, return
; set return value
add a0, zero, s1
; pop s1, s2 from stack
lw s1, sp, -4
lw s2, sp, -8
ret
memchr:
; void *memchr(const void *str, char c, size_t n)
sw s1, sp, -4 ; push s1 to the stack
__memchr_loop:
beq a2, zero, __memchr_ret_null
lb s1, a0, 0
addi a0, a0, 1 ; let a0 point to the next byte
addi a2, a2, -1 ; decrement bytes to copy by 1
bne s1, s2, __memchr_loop
; return pointer to prev byte (as the prev byte actually matched a1)
addi a0, a0, -1
; pop s1, s2 from stack
lw s1, sp, -4
ret
__memchr_ret_null:
; nothing found, return nullptr
addi a0, zero, NULL
lw s1, sp, -4
ret
memset:
; void *memset(void *str, char c, size_t n)
__memset_loop:
beq a2, zero, __memset_ret
sb a1, a0, 0
addi a0, a0, 1
addi a2, a2, -1
j __memset_loop
__memset_ret:
ret

@ -1,44 +0,0 @@
// example of a simple memory allocation
// we use the mmap2 syscall for this
.text
// call mmap2
li a0, 0 // addr = 0, let OS choose address
li a1, 4096 // size
li a2, 3 // PROT_READ | PROT_WRITE
li a3, 5 // MAP_PRIVATE | MAP_ANONYMOUS
li a7, SCALL_MMAP2
ecall // invoke syscall
li t0, -1 // exit if unsuccessful
beq a0, t0, _exit
// print address
print.uhex a0
# we can look at the state of the mmu here:
ebreak
# > mmu.sections
# InstructionMemorySection[.text] at 0x00000100
# BinaryDataMemorySection[.stack] at 0x00000170
# BinaryDataMemorySection[.data.runtime-allocated] at 0x00080170
sw t0, 144(a0)
sw t0, 0(a0)
sw t0, 8(a0)
sw t0, 16(a0)
sw t0, 32(a0)
sw t0, 64(a0)
sw t0, 128(a0)
sw t0, 256(a0)
sw t0, 512(a0)
sw t0, 1024(a0)
sw t0, 2048(a0)
sw t0, 4000(a0)
lw t1, 128(a0)
print.uhex t0
ebreak
_exit:
li a7, 93
ecall

@ -1,11 +0,0 @@
main:
li a1, 1084227584
li a2, 1082130432
fcvt.s.wu ft0, a1
fcvt.s.wu ft1, a2
fmul.s ft6, ft0, ft1
print.float ft6
// exit gracefully
addi a0, zero, 0
addi a7, zero, 93
scall // exit with code 0

@ -1,20 +0,0 @@
.data
my_data:
.word 0x11223344, 0x55667788, 0x9900aabb, 0xccddeeff
.text
main:
// load base address into t0
la t0, my_data
// begin loading words and printing them
lw a0, 0(t0)
print.uhex a0
lw a0, 4(t0)
print.uhex a0
lw a0, 8(t0)
print.uhex a0
lw a0, 12(t0)
print.uhex a0
// exit
li a7, 93
ecall

@ -35,4 +35,4 @@ cd sphinx-docs
make html make html
# xdg-open build/html/index.html # xdg-open build/html/index.html

@ -1,29 +0,0 @@
# RiscEmu LibC
This is a very basic implementation of libc in risc-v assembly, meant specifically for the riscemu emulator.
This is currently very incomplete, only a handful of methods are implemented, and most of them pretty basic.
## Contents:
### `stdlib.s`
Basic implementations of:
- `malloc`/`free` (that leaks memory)
- `rand`/`srand` (using xorshift)
- `exit`/`atexit` (supporting up to 8 exit handlers)
### `string.s`
Somewhat nice implementations of:
- `strlen`
- `strncpy`
- `strcpy`
- `memchr`
- `memset` (very basic byte-by-byte copy)
## Correctness:
This library is only lightly tested, so be careful and report bugs when you find them!

@ -1,14 +0,0 @@
// A minimal crt0.s that works along the stdlib.s file provided to give
// some resemblance of a functioning compilation target :)
//
// Copyright (c) 2023 Anton Lydike
// SPDX-License-Identifier: MIT
.text
.globl _start
_start:
// TODO: read argc, argv from a0, a1
// maybe even find envptr?
jal main
jal exit

@ -1,178 +0,0 @@
// A very basic implementation of a stdlib.h but in assembly.
// should(tm) work with riscemu.
//
// Copyright (c) 2023 Anton Lydike
// SPDX-License-Identifier: MIT
.data
_rand_seed:
.word 0x76767676
_atexit_calls:
// leave room for 8 atexit handlers here for now
.word 0x00, 0x00, 0x00, 0x00
.word 0x00, 0x00, 0x00, 0x00
_atexit_count:
.word 0x00
_malloc_base_ptr:
// first word is a pointer to some space
// second word is the offset inside that space
// space is always MALLOC_PAGE_SIZE bytes
.word 0x00, 0x00
.equ MALLOC_PAGE_SIZE 4069
.text
// malloc/free
.globl malloc
.globl free
// malloc(size_t size)
malloc:
// set aside size in s0
sw s0, -4(sp)
mv a0, s0
la t0, _malloc_base_ptr
lw t1, 0(t0)
beq t1, zero, _malloc_init
_malloc_post_init:
// if we are here, we always have
// t0 = (&_malloc_base_ptr)
// t1 = *(&_malloc_base_ptr)
// new we load
// t2 = base_ptr_offset
lw t2, 4(t0)
// add allocated size to offset
add t2, t2, s0
// check for overflow
li t4, MALLOC_PAGE_SIZE
bge t2, t4, _malloc_fail
// save the new offset
sw t2, 4(t0)
// calculate base_ptr + offset
add a0, t2, t1
// return that
lw s0, -4(sp)
ret
_malloc_init:
// call mmap2()
li a0, 0 // addr = 0, let OS choose address
li a1, 4096 // size
li a2, 3 // PROT_READ | PROT_WRITE
li a3, 5 // MAP_PRIVATE | MAP_ANONYMOUS
li a7, SCALL_MMAP2
ecall // invoke syscall
// check for error code
li t0, -1
beq a0, t0, _malloc_fail
// if succeeded, load &_malloc_base_ptr
la t0, _malloc_base_ptr
// put value of _malloc_base_ptr into t1
mv a0, t1
// save base ptr to _malloc_base_ptr
sw t1, 0(t0)
// jump to post_init
j _malloc_post_init
_malloc_fail:
li a0, 0
ret
// free is a nop, that's valid, but not very good^^
free:
ret
// exit, atexit
.globl exit
.globl atexit
// we can happily use saved registers here because we don't care at all if we
// destroy the calling registers. This is __noreturn__ anyways!
// register layout:
// s0 = &_atexit_count
// s2 = &_atexit_calls
// s1 = updated value of atexit
// s3 = exit code
exit:
// save exit code to s3
mv s3, a0
_exit_start:
la s0, _atexit_count // s0 = &_atexit_count
lw s1, 0(s0) // s1 = *(&_atexit_count)
// exit if no atexit() calls remain
beq s1, zero, _exit
// decrement
addi s1, s1, -4 // s1--
// save decremented value
sw s1, 0(s0) // _atexit_count = s1
li s2, _atexit_calls
add s1, s1, s2 // s1 = &_atexit_calls + (s1)
lw s1, 0(s1) // s1 = *s1
la ra, _exit_start // set ra up to point to exit
jalr zero, s1, 0 // jump to address in s1
// jalr will call the other function, which will then return back
// to the beginning of exit.
_exit:
mv a0, s3
li a7, 93
ecall
// atexit a0 = funcptr
atexit:
sw t0, -4(sp)
sw t2, -8(sp)
// load _atexit_count
la t0, _atexit_count
lw t2, 0(t0)
// if >= 8, fail
li t1, 8
bge t2, t1, _atexit_fail
// increment atexit_count by 4 (one word)
addi t2, t2, 4
sw t2, 0(t0)
// load address of _atexit_calls
la t0, _atexit_calls
// add new _atexit_count to _atexit_calls
add t0, t0, t2
sw a0, -4(t0)
li a0, 0
lw t0, -4(sp)
lw t2, -8(sp)
ret
_atexit_fail:
li a0, -1
lw s0, -4(sp)
lw s1, -8(sp)
ret
// rand, srand
.globl rand
.globl srand
// simple xorshift rand implementation
rand:
// load seed
la t1, _rand_seed
lw a0, 0(t1)
// three rounds of shifts:
sll a0, t0, 13 // x ^= x << 13;
srl a0, t0, 17 // x ^= x >> 17;
sll a0, t0, 5 // x ^= x << 5;
sw a0, 0(t1)
ret
srand:
la t1, _rand_seed
sw a0, 0(t1)
ret

@ -1,154 +0,0 @@
// string operations in RISC-V Assembly
//
// Copyright (c) 2023 Anton Lydike
// SPDX-License-Identifier: MIT
// Create NullPtr constant
.equ NULL, 0x00
.global NULL
.global strlen
// size_t libstr_strlen(char* str)
// return the length of str
.global strncpy
// char *strncpy(char *dest, const char *src, size_t n)
// copy n bytes from source into dest. If source ends before n bytes, the rest is filled with null-bytes
// returns pointer to dest
.global strcpy
// char *strncpy(char *dest, const char *src)
// copy string src into dest, including null terminator
// returns pointer to dest
.global memchr
// void *memchr(const void *str, char c, size_t n)
// search vor the first occurance of c in str
// returns a pointer to the first occurance, or NULL
.global memset
// void *memset(void *str, char c, size_t n)
// copies the character c to the first n characters of str.
// missing implementations
//.global memcmp
//.global memcpy
//.global strcat
.text
strlen:
// size_t strlen(char* str)
// push s1, s2 to the stack
sw s1, sp, -4
sw s2, sp, -8
// since no subroutines are called, we don't need to increment or decrement the sp
addi s2, zero, -1 // length (since null byte is counted by this method, we return len - 1)
__strlen_loop:
lb s1, a0, 0 // read character
addi s2, s2, 1 // increment number bytes read
addi a0, a0, 1
bne s1, zero, __strlen_loop
// we are done, set return value in a0
add a0, zero, s2
// pop s1, s2, from stack
lw s1, sp, -4
lw s2, sp, -8
ret
strncpy:
// char *strncpy(char *dest, const char *src, size_t n)
// copy size bytes from source to dest
sw s1, sp, -4 // push s1 to the stack
sw s2, sp, -8 // push s1 to the stack
add s1, a0, zero // save dest pointer for return
__strncpy_loop:
beq a2, zero, __strncpy_end
// copy byte
lb s2, a1, 0 // read first byte from src
sb s2, a0, 0 // write first byte to dest
// increment pointers
addi a0, a0, 1
addi a1, a1, 1
// one less byte to copy
addi a2, a2, -1
// if we read the terminating byte, jump to fill code
beq s2, zero, __strncpy_fill
// otherwise continue copying
j __strncpy_loop
__strncpy_fill:
// fill remaining space with 0 bytes
// if no bytes left, stop filling
beq a2, zero, __strncpy_end
sb zero, a0, 0
addi a0, a0, 1
addi a2, a2, -1
j __strncpy_fill
__strncpy_end:
// set return value
add a0, zero, s1
// pop s1, s2 from stack
lw s1, sp, -4
lw s2, sp, -8
ret
strcpy:
// char *strcpy(char *dest, const char *src)
sw s1, sp, -4 // push s1 to the stack
sw s2, sp, -8 // push s1 to the stack
add s1, a0, zero // save dest pointer for return
__strcpy_loop:
// copy byte
lb s2, a1, 0 // read first byte from src
sb s2, a0, 0 // write first byte to dest
// increment pointers
addi a0, a0, 1
addi a1, a1, 1
bne s2, zero, __strcpy_loop
// we are done copying, return
// set return value
add a0, zero, s1
// pop s1, s2 from stack
lw s1, sp, -4
lw s2, sp, -8
ret
memchr:
// void *memchr(const void *str, char c, size_t n)
sw s1, sp, -4 // push s1 to the stack
andi a1, a1, 0xff // trim a1 to be byte-sized
__memchr_loop:
beq a2, zero, __memchr_ret_null
lb s1, a0, 0
addi a0, a0, 1 // let a0 point to the next byte
addi a2, a2, -1 // decrement bytes to copy by 1
bne s1, a1, __memchr_loop
// return pointer to prev byte (as the prev byte actually matched a1)
addi a0, a0, -1
// pop s1, from stack
lw s1, sp, -4
ret
__memchr_ret_null:
// nothing found, return nullptr
addi a0, zero, NULL
lw s1, sp, -4
ret
memset:
// void *memset(void *str, char c, size_t n)
__memset_loop:
beq a2, zero, __memset_ret
sb a1, a0, 0
addi a0, a0, 1
addi a2, a2, -1
j __memset_loop
__memset_ret:
ret

@ -1,3 +1,4 @@
[build-system] [build-system]
requires = ["setuptools", "wheel"] requires = ["setuptools", "wheel"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"

@ -1,5 +0,0 @@
black==23.3.0
pre-commit==3.2.2
pytest==7.3.1
filecheck==0.0.23
lit==16.0.2

@ -30,9 +30,7 @@ class UserModeCPU(CPU):
It is initialized with a configuration and a list of instruction sets. It is initialized with a configuration and a list of instruction sets.
""" """
def __init__( def __init__(self, instruction_sets: List[Type['riscemu.InstructionSet']], conf: RunConfig):
self, instruction_sets: List[Type["riscemu.InstructionSet"]], conf: RunConfig
):
""" """
Creates a CPU instance. Creates a CPU instance.
@ -51,16 +49,12 @@ class UserModeCPU(CPU):
syscall_symbols.update(self.mmu.global_symbols) syscall_symbols.update(self.mmu.global_symbols)
self.mmu.global_symbols.update(syscall_symbols) self.mmu.global_symbols.update(syscall_symbols)
def step(self, verbose: bool = False): def step(self, verbose=False):
""" """
Execute a single instruction, then return. Execute a single instruction, then return.
""" """
if self.halted: if self.halted:
print( print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
FMT_CPU
+ "[CPU] Program exited with code {}".format(self.exit_code)
+ FMT_NONE
)
return return
launch_debugger = False launch_debugger = False
@ -69,9 +63,7 @@ class UserModeCPU(CPU):
self.cycle += 1 self.cycle += 1
ins = self.mmu.read_ins(self.pc) ins = self.mmu.read_ins(self.pc)
if verbose: if verbose:
print( print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)
)
self.pc += self.INS_XLEN self.pc += self.INS_XLEN
self.run_instruction(ins) self.run_instruction(ins)
except RiscemuBaseException as ex: except RiscemuBaseException as ex:
@ -80,29 +72,25 @@ class UserModeCPU(CPU):
if self.debugger_active: if self.debugger_active:
raise ex raise ex
print(FMT_CPU + "[CPU] Debugger launch requested!" + FMT_NONE) print(FMT_CPU + '[CPU] Debugger launch requested!' + FMT_NONE)
launch_debugger = True launch_debugger = True
else: else:
print(ex.message()) print(ex.message())
ex.print_stacktrace() ex.print_stacktrace()
print(FMT_CPU + "[CPU] Halting due to exception!" + FMT_NONE) print(FMT_CPU + '[CPU] Halting due to exception!' + FMT_NONE)
self.halted = True self.halted = True
if launch_debugger: if launch_debugger:
launch_debug_session(self) launch_debug_session(self)
def run(self, verbose: bool = False): def run(self, verbose=False):
while not self.halted: while not self.halted:
self.step(verbose) self.step(verbose)
if self.conf.verbosity > 0: if self.conf.verbosity > 0:
print( print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
FMT_CPU
+ "[CPU] Program exited with code {}".format(self.exit_code)
+ FMT_NONE
)
def setup_stack(self, stack_size: int = 1024 * 4) -> bool: def setup_stack(self, stack_size=1024 * 4) -> bool:
""" """
Create program stack and populate stack pointer Create program stack and populate stack pointer
:param stack_size: the size of the required stack, defaults to 4Kib :param stack_size: the size of the required stack, defaults to 4Kib
@ -110,26 +98,22 @@ class UserModeCPU(CPU):
""" """
stack_sec = BinaryDataMemorySection( stack_sec = BinaryDataMemorySection(
bytearray(stack_size), bytearray(stack_size),
".stack", '.stack',
None, # FIXME: why does a binary data memory section require a context? None, # FIXME: why does a binary data memory section require a context?
"", '',
0, 0
) )
if not self.mmu.load_section(stack_sec, fixed_position=False): if not self.mmu.load_section(stack_sec, fixed_position=False):
print(FMT_ERROR + "[CPU] Could not insert stack section!" + FMT_NONE) print(FMT_ERROR + "[CPU] Could not insert stack section!" + FMT_NONE)
return False return False
self.regs.set("sp", Int32(stack_sec.base + stack_sec.size)) self.regs.set('sp', Int32(stack_sec.base + stack_sec.size))
if self.conf.verbosity > 1: if self.conf.verbosity > 1:
print( print(FMT_CPU + "[CPU] Created stack of size {} at 0x{:x}".format(
FMT_CPU stack_size, stack_sec.base
+ "[CPU] Created stack of size {} at 0x{:x}".format( ) + FMT_NONE)
stack_size, stack_sec.base
)
+ FMT_NONE
)
return True return True

@ -1,30 +1,22 @@
from abc import ABC from abc import ABC, abstractmethod
from typing import Optional from typing import Optional
from riscemu.types import MemorySection, MemoryFlags, T_RelativeAddress from riscemu.types import MemorySection, MemoryFlags, T_RelativeAddress
class IOModule(MemorySection, ABC): class IOModule(MemorySection, ABC):
def __init__( def __init__(self, name: str, flags: MemoryFlags, size: int, owner: str = 'system', base: int = 0):
self,
name: str,
flags: MemoryFlags,
size: int,
owner: str = "system",
base: int = 0,
):
super(IOModule, self).__init__(name, flags, size, base, owner, None) super(IOModule, self).__init__(name, flags, size, base, owner, None)
def contains(self, addr, size: int = 0): def contains(self, addr, size: int = 0):
return ( return self.base <= addr < self.base + self.size and \
self.base <= addr < self.base + self.size self.base <= addr + size <= self.base + self.size
and self.base <= addr + size <= self.base + self.size
)
def dump(self, *args, **kwargs): def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress] = None, fmt: str = 'hex',
bytes_per_row: int = 16, rows: int = 10, group: int = 4):
print(self) print(self)
def __repr__(self): def __repr__(self):
return "{}[{}] at 0x{:0X} (size={}bytes, flags={})".format( return "{}[{}] at 0x{:0X} (size={}bytes, flags={})".format(
self.__class__.__name__, self.name, self.base, self.size, self.flags self.__class__.__name__, self.name, self.base, self.size, self.flags
) )

@ -8,9 +8,7 @@ class TextIO(IOModule):
raise InstructionAccessFault(self.base + offset) raise InstructionAccessFault(self.base + offset)
def __init__(self, base: int, buflen: int = 128): def __init__(self, base: int, buflen: int = 128):
super(TextIO, self).__init__( super(TextIO, self).__init__('TextIO', MemoryFlags(False, False), buflen + 4, base=base)
"TextIO", MemoryFlags(False, False), buflen + 4, base=base
)
self.buff = bytearray(buflen) self.buff = bytearray(buflen)
self.current_line = "" self.current_line = ""
@ -25,15 +23,15 @@ class TextIO(IOModule):
self._print() self._print()
return return
buff_start = addr - 4 buff_start = addr - 4
self.buff[buff_start : buff_start + size] = data[0:size] self.buff[buff_start:buff_start + size] = data[0:size]
def _print(self): def _print(self):
buff = self.buff buff = self.buff
self.buff = bytearray(self.size) self.buff = bytearray(self.size)
if b"\x00" in buff: if b'\x00' in buff:
buff = buff.split(b"\x00")[0] buff = buff.split(b'\x00')[0]
text = buff.decode("ascii") text = buff.decode('ascii')
if "\n" in text: if '\n' in text:
lines = text.split("\n") lines = text.split("\n")
lines[0] = self.current_line + lines[0] lines[0] = self.current_line + lines[0]
for line in lines[:-1]: for line in lines[:-1]:

@ -8,16 +8,8 @@ from typing import Dict, List, Optional, Union
from .colors import * from .colors import *
from .helpers import align_addr from .helpers import align_addr
from .types import ( from .types import Instruction, MemorySection, MemoryFlags, T_AbsoluteAddress, \
Instruction, Program, InstructionContext, Int32
MemorySection,
MemoryFlags,
T_AbsoluteAddress,
Program,
InstructionContext,
Int32,
Float32,
)
from .types.exceptions import InvalidAllocationException, MemoryAccessException from .types.exceptions import InvalidAllocationException, MemoryAccessException
@ -73,7 +65,7 @@ class MMU:
return sec return sec
return None return None
def get_program_at_addr(self, addr: T_AbsoluteAddress) -> Optional[Program]: def get_bin_containing(self, addr: T_AbsoluteAddress) -> Optional[Program]:
for program in self.programs: for program in self.programs:
if program.base <= addr < program.base + program.size: if program.base <= addr < program.base + program.size:
return program return program
@ -88,14 +80,8 @@ class MMU:
""" """
sec = self.get_sec_containing(addr) sec = self.get_sec_containing(addr)
if sec is None: if sec is None:
print( print(FMT_MEM + "[MMU] Trying to read instruction form invalid region! (read at {}) ".format(addr)
FMT_MEM + "Have you forgotten an exit syscall or ret statement?" + FMT_NONE)
+ "[MMU] Trying to read instruction form invalid region! (read at {}) ".format(
addr
)
+ "Have you forgotten an exit syscall or ret statement?"
+ FMT_NONE
)
raise RuntimeError("No next instruction available!") raise RuntimeError("No next instruction available!")
return sec.read_ins(addr - sec.base) return sec.read_ins(addr - sec.base)
@ -108,19 +94,12 @@ class MMU:
:return: The bytearray at addr :return: The bytearray at addr
""" """
if isinstance(addr, Int32): if isinstance(addr, Int32):
breakpoint()
addr = addr.unsigned_value addr = addr.unsigned_value
sec = self.get_sec_containing(addr) sec = self.get_sec_containing(addr)
if sec is None: if sec is None:
print( print(FMT_MEM + "[MMU] Trying to read data form invalid region at 0x{:x}! ".format(addr) + FMT_NONE)
FMT_MEM raise MemoryAccessException("region is non-initialized!", addr, size, 'read')
+ "[MMU] Trying to read data form invalid region at 0x{:x}! ".format(
addr
)
+ FMT_NONE
)
raise MemoryAccessException(
"region is non-initialized!", addr, size, "read"
)
return sec.read(addr - sec.base, size) return sec.read(addr - sec.base, size)
def write(self, addr: int, size: int, data: bytearray): def write(self, addr: int, size: int, data: bytearray):
@ -133,16 +112,8 @@ class MMU:
""" """
sec = self.get_sec_containing(addr) sec = self.get_sec_containing(addr)
if sec is None: if sec is None:
print( print(FMT_MEM + '[MMU] Invalid write into non-initialized region at 0x{:08X}'.format(addr) + FMT_NONE)
FMT_MEM raise MemoryAccessException("region is non-initialized!", addr, size, 'write')
+ "[MMU] Invalid write into non-initialized region at 0x{:08X}".format(
addr
)
+ FMT_NONE
)
raise MemoryAccessException(
"region is non-initialized!", addr, size, "write"
)
return sec.write(addr - sec.base, size, data) return sec.write(addr - sec.base, size, data)
@ -156,11 +127,7 @@ class MMU:
""" """
sec = self.get_sec_containing(addr) sec = self.get_sec_containing(addr)
if sec is None: if sec is None:
print( print(FMT_MEM + "[MMU] No section containing addr 0x{:08X}".format(addr) + FMT_NONE)
FMT_MEM
+ "[MMU] No section containing addr 0x{:08X}".format(addr)
+ FMT_NONE
)
return return
sec.dump(addr - sec.base, *args, **kwargs) sec.dump(addr - sec.base, *args, **kwargs)
@ -172,54 +139,33 @@ class MMU:
""" """
print(FMT_MEM + "[MMU] Lookup for symbol {}:".format(symb) + FMT_NONE) print(FMT_MEM + "[MMU] Lookup for symbol {}:".format(symb) + FMT_NONE)
if symb in self.global_symbols: if symb in self.global_symbols:
print( print(" Found global symbol {}: 0x{:X}".format(symb, self.global_symbols[symb]))
" Found global symbol {}: 0x{:X}".format(
symb, self.global_symbols[symb]
)
)
for bin in self.programs: for bin in self.programs:
if symb in bin.context.labels: if symb in bin.context.labels:
print( print(" Found local labels {}: 0x{:X} in {}".format(symb, bin.context.labels[symb], bin.name))
" Found local labels {}: 0x{:X} in {}".format(
symb, bin.context.labels[symb], bin.name
)
)
def read_int(self, addr: int) -> Int32: def read_int(self, addr: int) -> Int32:
return Int32(self.read(addr, 4)) return Int32(self.read(addr, 4))
def read_float(self, addr: int) -> Float32:
return Float32(self.read(addr, 4))
def translate_address(self, address: T_AbsoluteAddress) -> str: def translate_address(self, address: T_AbsoluteAddress) -> str:
sec = self.get_sec_containing(address) sec = self.get_sec_containing(address)
if not sec: if not sec:
return "unknown at 0x{:0x}".format(address) return "unknown at 0x{:0x}".format(address)
bin = self.get_program_at_addr(address) bin = self.get_bin_containing(address)
secs = set(sec.name for sec in bin.sections) if bin else [] secs = set(sec.name for sec in bin.sections) if bin else []
elf_markers = { elf_markers = {
"__global_pointer$", '__global_pointer$', '_fdata', '_etext', '_gp',
"_fdata", '_bss_start', '_bss_end', '_ftext', '_edata', '_end', '_fbss'
"_etext",
"_gp",
"_bss_start",
"_bss_end",
"_ftext",
"_edata",
"_end",
"_fbss",
} }
def key(x): def key(x):
name, val = x name, val = x
return address - val return address - val
best_fit = sorted( best_fit = sorted(filter(lambda x: x[1] <= address, sec.context.labels.items()), key=key)
filter(lambda x: x[1] <= address, sec.context.labels.items()), key=key
)
best = ("", float("inf")) best = ('', float('inf'))
for name, val in best_fit: for name, val in best_fit:
if address - val < best[1]: if address - val < best[1]:
best = (name, val) best = (name, val)
@ -233,14 +179,13 @@ class MMU:
if not name: if not name:
return "{}:{} + 0x{:x} (0x{:x})".format( return "{}:{} + 0x{:x} (0x{:x})".format(
sec.owner, sec.name, address - sec.base, address sec.owner, sec.name,
address - sec.base, address
) )
return str( return str('{}:{} at {} (0x{:0x}) + 0x{:0x}'.format(
"{}:{} at {} (0x{:0x}) + 0x{:0x}".format( sec.owner, sec.name, name, val, address - val
sec.owner, sec.name, name, val, address - val ))
)
)
def has_continous_free_region(self, start: int, end: int) -> bool: def has_continous_free_region(self, start: int, end: int) -> bool:
# if we have no sections we are all good # if we have no sections we are all good
@ -266,24 +211,13 @@ class MMU:
def load_program(self, program: Program, align_to: int = 4): def load_program(self, program: Program, align_to: int = 4):
if program.base is not None: if program.base is not None:
if not self.has_continous_free_region( if not self.has_continous_free_region(program.base, program.base + program.size):
program.base, program.base + program.size print(FMT_MEM + "Cannot load program {} into desired space (0x{:0x}-0x{:0x}), area occupied.".format(
): program.name, program.base, program.base + program.size
print( ) + FMT_NONE)
FMT_MEM raise InvalidAllocationException("Area occupied".format(
+ "Cannot load program {} into desired space (0x{:0x}-0x{:0x}), area occupied.".format( program.name, program.base, program.base + program.size
program.name, program.base, program.base + program.size ), program.name, program.size, MemoryFlags(False, True))
)
+ FMT_NONE
)
raise InvalidAllocationException(
"Area occupied".format(
program.name, program.base, program.base + program.size
),
program.name,
program.size,
MemoryFlags(False, True),
)
at_addr = program.base at_addr = program.base
else: else:
@ -311,19 +245,14 @@ class MMU:
self.sections.append(sec) self.sections.append(sec)
self._update_state() self._update_state()
else: else:
print( print(FMT_MEM + '[MMU] Cannot place section {} at {}, space is occupied!'.format(sec, sec.base))
FMT_MEM
+ "[MMU] Cannot place section {} at {}, space is occupied!".format(
sec, sec.base
)
)
return False return False
else: else:
at_addr = align_addr(self.get_guaranteed_free_address(), 8) at_addr = align_addr(self.get_guaranteed_free_address(), 8)
sec.base = at_addr sec.base = at_addr
self.sections.append(sec) self.sections.append(sec)
self._update_state() self._update_state()
return True return True
def _update_state(self): def _update_state(self):
""" """
@ -341,7 +270,8 @@ class MMU:
def __repr__(self): def __repr__(self):
return "{}(\n\t{}\n)".format( return "{}(\n\t{}\n)".format(
self.__class__.__name__, "\n\t".join(repr(x) for x in self.programs) self.__class__.__name__,
"\n\t".join(repr(x) for x in self.programs)
) )
def context_for(self, addr: T_AbsoluteAddress) -> InstructionContext: def context_for(self, addr: T_AbsoluteAddress) -> InstructionContext:
@ -352,12 +282,13 @@ class MMU:
return InstructionContext() return InstructionContext()
def find_entrypoint(self) -> Optional[int]: def report_addr(self, addr: T_AbsoluteAddress):
# try to find the global entrypoint sec = self.get_sec_containing(addr)
if "_start" in self.global_symbols: if not sec:
return self.global_symbols["_start"] print("addr is in no section!")
# otherwise find a main (that's not necessarily global) return
for p in self.programs: owner = [b for b in self.programs if b.name == sec.owner]
if "main" in p.context.labels: if owner:
return p.context.resolve_label("main") print("owned by: {}".format(owner[0]))
return None
print("{}: 0x{:0x} + 0x{:0x}".format(name, val, addr - val))

@ -8,17 +8,8 @@ This package aims at providing an all-round usable RISC-V emulator and debugger
It contains everything needed to run assembly files, so you don't need any custom compilers or toolchains It contains everything needed to run assembly files, so you don't need any custom compilers or toolchains
""" """
from .types.exceptions import ( from .types.exceptions import RiscemuBaseException, LaunchDebuggerException, InvalidSyscallException, LinkerException, \
RiscemuBaseException, ParseException, NumberFormatException, InvalidRegisterException, MemoryAccessException, OutOfMemoryException
LaunchDebuggerException,
InvalidSyscallException,
LinkerException,
ParseException,
NumberFormatException,
InvalidRegisterException,
MemoryAccessException,
OutOfMemoryException,
)
from .instructions import * from .instructions import *
@ -33,5 +24,5 @@ from .config import RunConfig
from .parser import tokenize, parse_tokens, AssemblyFileLoader from .parser import tokenize, parse_tokens, AssemblyFileLoader
__author__ = "Anton Lydike <Anton@Lydike.com>" __author__ = "Anton Lydike <Anton@Lydike.com>"
__copyright__ = "Copyright 2023 Anton Lydike" __copyright__ = "Copyright 2022 Anton Lydike"
__version__ = "2.1.1" __version__ = '2.0.3'

@ -5,18 +5,126 @@ SPDX-License-Identifier: MIT
This file holds the logic for starting the emulator from the CLI This file holds the logic for starting the emulator from the CLI
""" """
import sys from riscemu import RiscemuBaseException, __copyright__, __version__
from riscemu.CPU import UserModeCPU
from riscemu import RiscemuBaseException if __name__ == '__main__':
from riscemu.riscemu_main import RiscemuMain from .config import RunConfig
from .instructions import InstructionSetDict
from .colors import FMT_BOLD, FMT_MAGENTA
from .parser import AssemblyFileLoader
import argparse
import sys
try: all_ins_names = list(InstructionSetDict.keys())
main = RiscemuMain()
main.run_from_cli(sys.argv[1:])
sys.exit(main.cpu.exit_code if not main.cfg.ignore_exit_code else 0)
except RiscemuBaseException as e: if '--version' in sys.argv:
print("Error: {}".format(e.message())) print("riscemu version {}\n{}\n\nAvailable ISA: {}".format(
e.print_stacktrace() __version__, __copyright__,
", ".join(InstructionSetDict.keys())
))
sys.exit()
sys.exit(-1) class OptionStringAction(argparse.Action):
def __init__(self, option_strings, dest, keys=None, omit_empty=False, **kwargs):
if keys is None:
raise ValueError('must define "keys" argument')
if isinstance(keys, dict):
keys_d = keys
elif isinstance(keys, (list, tuple)):
keys_d = {}
for k in keys:
if isinstance(k, tuple):
k, v = k
else:
v = False
keys_d[k] = v
else:
keys_d = dict()
super().__init__(option_strings, dest, default=keys_d, **kwargs)
self.keys = keys_d
self.omit_empty = omit_empty
def __call__(self, parser, namespace, values, option_string=None):
d = {}
if not self.omit_empty:
d.update(self.keys)
for x in values.split(','):
if x in self.keys:
d[x] = True
else:
raise ValueError('Invalid parameter supplied: ' + x)
setattr(namespace, self.dest, d)
parser = argparse.ArgumentParser(description='RISC-V Userspace parser and emulator', prog='riscemu')
parser.add_argument('files', metavar='file.asm', type=str, nargs='+',
help='The assembly files to load, the last one will be run')
parser.add_argument('--options', '-o', action=OptionStringAction,
keys=('disable_debug', 'no_syscall_symbols', 'fail_on_ex', 'add_accept_imm'))
parser.add_argument('--syscall-opts', '-so', action=OptionStringAction,
keys=('fs_access', 'disable_input'))
parser.add_argument('--instruction-sets', '-is', action=OptionStringAction,
help="Instruction sets to load, available are: {}. All are enabled by default"
.format(", ".join(all_ins_names)), keys={k: True for k in all_ins_names}, omit_empty=True)
parser.add_argument('--stack_size', type=int, help='Stack size of loaded programs, defaults to 8MB', nargs='?')
parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count',
default=0)
parser.add_argument('--interactive', help="Launch the interactive debugger instantly instead of loading any "
"programs", action='store_true')
args = parser.parse_args()
# create a RunConfig from the cli args
cfg_dict = dict(
stack_size=args.stack_size,
debug_instruction=not args.options['disable_debug'],
include_scall_symbols=not args.options['no_syscall_symbols'],
debug_on_exception=not args.options['fail_on_ex'],
add_accept_imm=args.options['add_accept_imm'],
scall_fs=args.syscall_opts['fs_access'],
scall_input=not args.syscall_opts['disable_input'],
verbosity=args.verbose
)
for k, v in dict(cfg_dict).items():
if v is None:
del cfg_dict[k]
cfg = RunConfig(**cfg_dict)
if not hasattr(args, 'ins'):
setattr(args, 'ins', {k: True for k in all_ins_names})
FMT_PRINT = FMT_BOLD + FMT_MAGENTA
# parse required instruction sets
ins_to_load = [
InstructionSetDict[name] for name, b in args.ins.items() if b
]
try:
cpu = UserModeCPU(ins_to_load, cfg)
opts = AssemblyFileLoader.get_options(sys.argv)
for file in args.files:
loader = AssemblyFileLoader.instantiate(file, opts)
cpu.load_program(loader.parse())
# set up a stack
cpu.setup_stack(cfg.stack_size)
# launch the last loaded program
cpu.launch(cpu.mmu.programs[-1], verbose=cfg.verbosity > 1)
sys.exit(cpu.exit_code)
except RiscemuBaseException as e:
print("Error: {}".format(e.message()))
e.print_stacktrace()
sys.exit(-1)

@ -6,17 +6,10 @@ from .colors import FMT_PARSE, FMT_NONE
from riscemu.types.exceptions import ParseException, ASSERT_LEN from riscemu.types.exceptions import ParseException, ASSERT_LEN
from .helpers import parse_numeric_argument, align_addr, get_section_base_name from .helpers import parse_numeric_argument, align_addr, get_section_base_name
from .tokenizer import Token from .tokenizer import Token
from .types import ( from .types import Program, T_RelativeAddress, InstructionContext, Instruction, BinaryDataMemorySection, \
Program, InstructionMemorySection, Int32
T_RelativeAddress,
InstructionContext, INSTRUCTION_SECTION_NAMES = ('.text', '.init', '.fini')
Instruction,
BinaryDataMemorySection,
InstructionMemorySection,
Int32,
)
INSTRUCTION_SECTION_NAMES = (".text", ".init", ".fini")
""" """
A tuple containing all section names which contain executable code (instead of data) A tuple containing all section names which contain executable code (instead of data)
@ -54,7 +47,8 @@ class CurrentSection:
def __repr__(self): def __repr__(self):
return "{}(name={},data={},type={})".format( return "{}(name={},data={},type={})".format(
self.__class__.__name__, self.name, self.data, self.type.name self.__class__.__name__, self.name,
self.data, self.type.name
) )
@ -78,20 +72,12 @@ class ParseContext:
if self.section.type == MemorySectionType.Data: if self.section.type == MemorySectionType.Data:
section = BinaryDataMemorySection( section = BinaryDataMemorySection(
self.section.data, self.section.data, self.section.name, self.context, self.program.name, self.section.base
self.section.name,
self.context,
self.program.name,
self.section.base,
) )
self.program.add_section(section) self.program.add_section(section)
elif self.section.type == MemorySectionType.Instructions: elif self.section.type == MemorySectionType.Instructions:
section = InstructionMemorySection( section = InstructionMemorySection(
self.section.data, self.section.data, self.section.name, self.context, self.program.name, self.section.base
self.section.name,
self.context,
self.program.name,
self.section.base,
) )
self.program.add_section(section) self.program.add_section(section)
@ -103,9 +89,7 @@ class ParseContext:
self._finalize_section() self._finalize_section()
self.section = CurrentSection(name, type, base) self.section = CurrentSection(name, type, base)
def add_label( def add_label(self, name: str, value: int, is_global: bool = False, is_relative: bool = False):
self, name: str, value: int, is_global: bool = False, is_relative: bool = False
):
self.context.labels[name] = value self.context.labels[name] = value
if is_global: if is_global:
self.program.global_labels.add(name) self.program.global_labels.add(name)
@ -125,16 +109,10 @@ class ParseContext:
def ASSERT_IN_SECTION_TYPE(context: ParseContext, type: MemorySectionType): def ASSERT_IN_SECTION_TYPE(context: ParseContext, type: MemorySectionType):
if context.section is None: if context.section is None:
raise ParseException( raise ParseException('Error, expected to be in {} section, but no section is present...'.format(type.name))
"Error, expected to be in {} section, but no section is present...".format(
type.name
)
)
if context.section.type != type: if context.section.type != type:
raise ParseException( raise ParseException(
"Error, expected to be in {} section, but currently in {}...".format( 'Error, expected to be in {} section, but currently in {}...'.format(type.name, context.section)
type.name, context.section
)
) )
@ -196,9 +174,7 @@ class AssemblerDirectives:
cls.add_bytes(size, bytearray(size), context) cls.add_bytes(size, bytearray(size), context)
@classmethod @classmethod
def add_bytes( def add_bytes(cls, size: int, content: Union[None, int, bytearray], context: ParseContext):
cls, size: int, content: Union[None, int, bytearray], context: ParseContext
):
ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data) ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data)
if content is None: if content is None:
@ -211,9 +187,9 @@ class AssemblerDirectives:
@classmethod @classmethod
def add_text(cls, text: str, context: ParseContext, zero_terminate: bool = True): def add_text(cls, text: str, context: ParseContext, zero_terminate: bool = True):
# replace '\t' and '\n' escape sequences # replace '\t' and '\n' escape sequences
text = text.replace("\\n", "\n").replace("\\t", "\t") text = text.replace('\\n', '\n').replace('\\t', '\t')
encoded_bytes = bytearray(text.encode("ascii")) encoded_bytes = bytearray(text.encode('ascii'))
if zero_terminate: if zero_terminate:
encoded_bytes += bytearray(1) encoded_bytes += bytearray(1)
cls.add_bytes(len(encoded_bytes), encoded_bytes, context) cls.add_bytes(len(encoded_bytes), encoded_bytes, context)
@ -221,36 +197,24 @@ class AssemblerDirectives:
@classmethod @classmethod
def handle_instruction(cls, token: Token, args: Tuple[str], context: ParseContext): def handle_instruction(cls, token: Token, args: Tuple[str], context: ParseContext):
op = token.value[1:] op = token.value[1:]
if hasattr(cls, "op_" + op): if hasattr(cls, 'op_' + op):
getattr(cls, "op_" + op)(token, args, context) getattr(cls, 'op_' + op)(token, args, context)
elif op in ("text", "data", "rodata", "bss", "sbss"): elif op in ('text', 'data', 'rodata', 'bss', 'sbss'):
cls.op_section(token, (token.value,), context) cls.op_section(token, (token.value,), context)
elif op in ("string", "asciiz", "asciz", "ascii"): elif op in ('string', 'asciiz', 'asciz', 'ascii'):
ASSERT_LEN(args, 1) ASSERT_LEN(args, 1)
cls.add_text(args[0], context, zero_terminate=(op != "ascii")) cls.add_text(args[0], context, op == 'ascii')
elif op in DATA_OP_SIZES: elif op in DATA_OP_SIZES:
size = DATA_OP_SIZES[op] size = DATA_OP_SIZES[op]
for arg in args: for arg in args:
cls.add_bytes(size, parse_numeric_argument(arg), context) cls.add_bytes(size, parse_numeric_argument(arg), context)
else: else:
print( print(FMT_PARSE + "Unknown assembler directive: {} {} in {}".format(token, args, context) + FMT_NONE)
FMT_PARSE
+ "Unknown assembler directive: {} {} in {}".format(
token, args, context
)
+ FMT_NONE
)
DATA_OP_SIZES = { DATA_OP_SIZES = {
"byte": 1, 'byte': 1,
"2byte": 2, '2byte': 2, 'half': 2, 'short': 2,
"half": 2, '4byte': 4, 'word': 4, 'long': 4,
"short": 2, '8byte': 8, 'dword': 8, 'quad': 8,
"4byte": 4,
"word": 4,
"long": 4,
"8byte": 8,
"dword": 8,
"quad": 8,
} }

@ -6,18 +6,18 @@ SPDX-License-Identifier: MIT
# Colors # Colors
FMT_RED = "\033[31m" FMT_RED = '\033[31m'
FMT_ORANGE = "\033[33m" FMT_ORANGE = '\033[33m'
FMT_GRAY = "\033[37m" FMT_GRAY = '\033[37m'
FMT_CYAN = "\033[36m" FMT_CYAN = '\033[36m'
FMT_GREEN = "\033[32m" FMT_GREEN = '\033[32m'
FMT_MAGENTA = "\033[35m" FMT_MAGENTA = '\033[35m'
FMT_BLUE = "\033[34m" FMT_BLUE = '\033[34m'
FMT_YELLOW = "\033[93m" FMT_YELLOW = '\033[93m'
FMT_BOLD = "\033[1m" FMT_BOLD = '\033[1m'
FMT_NONE = "\033[0m" FMT_NONE = '\033[0m'
FMT_UNDERLINE = "\033[4m" FMT_UNDERLINE = '\033[4m'
FMT_ERROR = FMT_RED + FMT_BOLD FMT_ERROR = FMT_RED + FMT_BOLD
@ -26,4 +26,4 @@ FMT_PARSE = FMT_CYAN + FMT_BOLD
FMT_CPU = FMT_BLUE + FMT_BOLD FMT_CPU = FMT_BLUE + FMT_BOLD
FMT_SYSCALL = FMT_YELLOW + FMT_BOLD FMT_SYSCALL = FMT_YELLOW + FMT_BOLD
FMT_DEBUG = FMT_MAGENTA + FMT_BOLD FMT_DEBUG = FMT_MAGENTA + FMT_BOLD
FMT_CSR = FMT_ORANGE + FMT_BOLD FMT_CSR = FMT_ORANGE + FMT_BOLD

@ -20,7 +20,6 @@ class RunConfig:
scall_fs: bool = False scall_fs: bool = False
verbosity: int = 0 verbosity: int = 0
slowdown: float = 1 slowdown: float = 1
unlimited_registers: bool = False
# runtime config
use_libc: bool = False CONFIG = RunConfig()
ignore_exit_code: bool = False

@ -11,10 +11,10 @@ from .helpers import *
if typing.TYPE_CHECKING: if typing.TYPE_CHECKING:
from riscemu import CPU, Registers from riscemu import CPU, Registers
HIST_FILE = os.path.join(os.path.expanduser("~"), ".riscemu_history") HIST_FILE = os.path.join(os.path.expanduser('~'), '.riscemu_history')
def launch_debug_session(cpu: "CPU", prompt=""): def launch_debug_session(cpu: 'CPU', prompt=""):
if cpu.debugger_active: if cpu.debugger_active:
return return
import code import code
@ -39,7 +39,7 @@ def launch_debug_session(cpu: "CPU", prompt=""):
mmu.dump(what, *args, **kwargs) mmu.dump(what, *args, **kwargs)
def dump_stack(*args, **kwargs): def dump_stack(*args, **kwargs):
mmu.dump(regs.get("sp"), *args, **kwargs) mmu.dump(regs.get('sp'), *args, **kwargs)
def ins(): def ins():
print("Current instruction at 0x{:08X}:".format(cpu.pc)) print("Current instruction at 0x{:08X}:".format(cpu.pc))
@ -51,7 +51,11 @@ def launch_debug_session(cpu: "CPU", prompt=""):
return return
context = mmu.context_for(cpu.pc) context = mmu.context_for(cpu.pc)
ins = SimpleInstruction(name, tuple(args), context, cpu.pc) ins = SimpleInstruction(
name,
tuple(args),
context,
cpu.pc)
print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE) print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE)
cpu.run_instruction(ins) cpu.run_instruction(ins)
@ -59,7 +63,7 @@ def launch_debug_session(cpu: "CPU", prompt=""):
try: try:
cpu.run(verbose) cpu.run(verbose)
except LaunchDebuggerException: except LaunchDebuggerException:
print(FMT_DEBUG + "Returning to debugger...") print(FMT_DEBUG + 'Returning to debugger...')
return return
def step(): def step():
@ -88,3 +92,4 @@ def launch_debug_session(cpu: "CPU", prompt=""):
finally: finally:
cpu.debugger_active = False cpu.debugger_active = False
readline.write_history_file(HIST_FILE) readline.write_history_file(HIST_FILE)

@ -1,2 +1,2 @@
from .decoder import decode, RISCV_REGS from .decoder import decode, RISCV_REGS
from .formatter import format_ins from .formatter import format_ins

@ -1,4 +1,4 @@
if __name__ == "__main__": if __name__ == '__main__':
import code import code
import readline import readline
import rlcompleter import rlcompleter
@ -14,6 +14,4 @@ if __name__ == "__main__":
readline.set_completer(rlcompleter.Completer(sess_vars).complete) readline.set_completer(rlcompleter.Completer(sess_vars).complete)
readline.set_completer(rlcompleter.Completer(sess_vars).complete) readline.set_completer(rlcompleter.Completer(sess_vars).complete)
readline.parse_and_bind("tab: complete") readline.parse_and_bind("tab: complete")
code.InteractiveConsole(sess_vars).interact( code.InteractiveConsole(sess_vars).interact(banner="Interaktive decoding session started...", exitmsg="Closing...")
banner="Interaktive decoding session started...", exitmsg="Closing..."
)

@ -5,14 +5,13 @@ from typing import Tuple, List
def print_ins(ins: int): def print_ins(ins: int):
print(" f7 rs2 rs1 f3 rd op") print(" f7 rs2 rs1 f3 rd op")
print( print(
f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:05b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}" f"0b{ins >> 25 :07b}_{(ins >> 20) & 0b11111:05b}_{(ins >> 15) & 0b11111:05b}_{(ins >> 12) & 0b111:03b}_{(ins >> 7) & 0b11111:05b}_{ins & 0b1111111:07b}");
)
STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = { STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = {
0x00000013: ("nop", [], 0x00000013), 0x00000013: ("nop", [], 0x00000013),
0x00008067: ("ret", [], 0x00008067), 0x00008067: ("ret", [], 0x00008067),
0xFE010113: ("addi", [2, 2, -32], 0xFE010113), 0xfe010113: ("addi", [2, 2, -32], 0xfe010113),
0x02010113: ("addi", [2, 2, 32], 0x02010113), 0x02010113: ("addi", [2, 2, 32], 0x02010113),
0x00100073: ("ebreak", [], 0x00100073), 0x00100073: ("ebreak", [], 0x00100073),
0x00000073: ("ecall", [], 0x00000073), 0x00000073: ("ecall", [], 0x00000073),
@ -24,7 +23,7 @@ STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = {
def int_from_ins(insn: bytearray): def int_from_ins(insn: bytearray):
return int.from_bytes(insn, "little") return int.from_bytes(insn, 'little')
def name_from_insn(ins: int): def name_from_insn(ins: int):
@ -46,7 +45,7 @@ def name_from_insn(ins: int):
if isinstance(dec, str): if isinstance(dec, str):
return dec return dec
if opcode == 0x1C and fun3 == 0: if opcode == 0x1c and fun3 == 0:
# we have ecall/ebreak # we have ecall/ebreak
token = imm110(ins) token = imm110(ins)
if token in dec: if token in dec:

@ -1,7 +1,6 @@
from typing import Dict, Callable, List, Union from typing import Dict, Callable, List, Union
from .regs import RISCV_REGS from .regs import RISCV_REGS
def op(ins: int): def op(ins: int):
return (ins >> 2) & 31 return (ins >> 2) & 31
@ -47,12 +46,7 @@ def imm_b(ins: int):
lower = rd(ins) lower = rd(ins)
higher = funct7(ins) higher = funct7(ins)
num = ( num = (lower & 0b11110) + ((higher & 0b0111111) << 5) + ((lower & 1) << 11) + ((higher >> 6) << 12)
(lower & 0b11110)
+ ((higher & 0b0111111) << 5)
+ ((lower & 1) << 11)
+ ((higher >> 6) << 12)
)
return sign_extend(num, 13) return sign_extend(num, 13)
@ -62,11 +56,10 @@ def imm_u(ins: int):
def imm_j(ins: int): def imm_j(ins: int):
return sign_extend( return sign_extend(
(((ins >> 21) & 0b1111111111) << 1) (((ins >> 21) & 0b1111111111) << 1) +
+ (((ins >> 20) & 1) << 11) (((ins >> 20) & 1) << 11) +
+ (((ins >> 12) & 0b11111111) << 12) (((ins >> 12) & 0b11111111) << 12) +
+ (((ins >> 31) & 1) << 20), (((ins >> 31) & 1) << 20), 21
21,
) )
@ -118,7 +111,7 @@ INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[int]]] = {
0x0D: decode_u, 0x0D: decode_u,
0x18: decode_b, 0x18: decode_b,
0x19: decode_i, 0x19: decode_i,
0x1B: decode_j, 0x1b: decode_j,
0x1C: decode_i_unsigned, 0x1c: decode_i_unsigned,
0b1011: decode_r, 0b1011: decode_r
} }

@ -1,15 +1,5 @@
from .formats import ( from .formats import INSTRUCTION_ARGS_DECODER, op, decode_i, decode_r, decode_u, decode_b, decode_j, decode_s, \
INSTRUCTION_ARGS_DECODER, decode_i_shamt, decode_i_unsigned
op,
decode_i,
decode_r,
decode_u,
decode_b,
decode_j,
decode_s,
decode_i_shamt,
decode_i_unsigned,
)
from .regs import RISCV_REGS from .regs import RISCV_REGS
@ -19,9 +9,9 @@ def int_to_hex(num: int):
return f"0x{num:x}" return f"0x{num:x}"
def format_ins(ins: int, name: str, fmt: str = "int"): def format_ins(ins: int, name: str, fmt: str = 'int'):
opcode = op(ins) opcode = op(ins)
if fmt == "hex": if fmt == 'hex':
fmt = int_to_hex fmt = int_to_hex
else: else:
fmt = str fmt = str
@ -30,7 +20,7 @@ def format_ins(ins: int, name: str, fmt: str = "int"):
return f"{name} <unknown op>" return f"{name} <unknown op>"
decoder = INSTRUCTION_ARGS_DECODER[opcode] decoder = INSTRUCTION_ARGS_DECODER[opcode]
if name in ("ecall", "ebreak", "mret", "sret", "uret"): if name in ('ecall', 'ebreak', 'mret', 'sret', 'uret'):
return name return name
if opcode in (0x8, 0x0): if opcode in (0x8, 0x0):
r1, r2, imm = decoder(ins) r1, r2, imm = decoder(ins)

@ -4,7 +4,7 @@ from .formats import *
tbl = lambda: defaultdict(tbl) tbl = lambda: defaultdict(tbl)
RV32 = tbl() RV32 = tbl()
RV32[0x1B] = "jal" RV32[0x1b] = "jal"
RV32[0x0D] = "lui" RV32[0x0D] = "lui"
RV32[0x05] = "auipc" RV32[0x05] = "auipc"
RV32[0x19][0] = "jalr" RV32[0x19][0] = "jalr"
@ -36,26 +36,26 @@ RV32[0x08][0] = "sb"
RV32[0x08][1] = "sh" RV32[0x08][1] = "sh"
RV32[0x08][2] = "sw" RV32[0x08][2] = "sw"
RV32[0x1C][1] = "csrrw" RV32[0x1c][1] = "csrrw"
RV32[0x1C][2] = "csrrs" RV32[0x1c][2] = "csrrs"
RV32[0x1C][3] = "csrrc" RV32[0x1c][3] = "csrrc"
RV32[0x1C][5] = "csrrwi" RV32[0x1c][5] = "csrrwi"
RV32[0x1C][6] = "csrrsi" RV32[0x1c][6] = "csrrsi"
RV32[0x1C][7] = "csrrci" RV32[0x1c][7] = "csrrci"
RV32[0x1C][0][0] = "ecall" RV32[0x1c][0][0] = "ecall"
RV32[0x1C][0][1] = "ebreak" RV32[0x1c][0][1] = "ebreak"
RV32[0x0C][0][0] = "add" RV32[0x0C][0][0] = "add"
RV32[0x0C][0][32] = "sub" RV32[0x0C][0][32] = "sub"
RV32[0x0C][1][0] = "sll" RV32[0x0C][1][0] = "sll"
RV32[0x0C][2][0] = "slt" RV32[0x0C][2][0] = "slt"
RV32[0x0C][3][0] = "sltu" RV32[0x0C][3][0] = "sltu"
RV32[0x0C][4][0] = "xor" RV32[0x0C][4][0] = "xor"
RV32[0x0C][5][0] = "srl" RV32[0x0C][5][0] = "srl"
RV32[0x0C][5][32] = "sra" RV32[0x0C][5][32] = "sra"
RV32[0x0C][6][0] = "or" RV32[0x0C][6][0] = "or"
RV32[0x0C][7][0] = "and" RV32[0x0C][7][0] = "and"
# rv32m # rv32m
RV32[0x0C][0][1] = "mul" RV32[0x0C][0][1] = "mul"

@ -1,34 +1,6 @@
RISCV_REGS = [ RISCV_REGS = [
"zero", 'zero', 'ra', 'sp', 'gp', 'tp', 't0', 't1', 't2',
"ra", 's0', 's1', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7',
"sp", 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11',
"gp", 't3', 't4', 't5', 't6'
"tp",
"t0",
"t1",
"t2",
"s0",
"s1",
"a0",
"a1",
"a2",
"a3",
"a4",
"a5",
"a6",
"a7",
"s2",
"s3",
"s4",
"s5",
"s6",
"s7",
"s8",
"s9",
"s10",
"s11",
"t3",
"t4",
"t5",
"t6",
] ]

@ -14,8 +14,6 @@ from .types.exceptions import *
def align_addr(addr: int, to_bytes: int = 8) -> int: def align_addr(addr: int, to_bytes: int = 8) -> int:
""" """
align an address to `to_bytes` (meaning addr & to_bytes = 0) align an address to `to_bytes` (meaning addr & to_bytes = 0)
This will increase the address
""" """
return addr + (-addr % to_bytes) return addr + (-addr % to_bytes)
@ -25,19 +23,16 @@ def parse_numeric_argument(arg: str) -> int:
parse hex or int strings parse hex or int strings
""" """
try: try:
if arg.lower().startswith("0x"): if arg.lower().startswith('0x'):
return int(arg, 16) return int(arg, 16)
return int(arg) return int(arg)
except ValueError as ex: except ValueError as ex:
raise ParseException( raise ParseException('Invalid immediate argument \"{}\", maybe missing symbol?'.format(arg), (arg, ex))
'Invalid immediate argument "{}", maybe missing symbol?'.format(arg),
(arg, ex),
)
def create_chunks(my_list, chunk_size): def create_chunks(my_list, chunk_size):
"""Split a list like [a,b,c,d,e,f,g,h,i,j,k,l,m] into e.g. [[a,b,c,d],[e,f,g,h],[i,j,k,l],[m]]""" """Split a list like [a,b,c,d,e,f,g,h,i,j,k,l,m] into e.g. [[a,b,c,d],[e,f,g,h],[i,j,k,l],[m]]"""
return [my_list[i : i + chunk_size] for i in range(0, len(my_list), chunk_size)] return [my_list[i:i + chunk_size] for i in range(0, len(my_list), chunk_size)]
def apply_highlight(item, ind, hi_ind): def apply_highlight(item, ind, hi_ind):
@ -50,31 +45,26 @@ def apply_highlight(item, ind, hi_ind):
def highlight_in_list(items, hi_ind, joiner=" "): def highlight_in_list(items, hi_ind, joiner=" "):
return joiner.join( return joiner.join([apply_highlight(item, i, hi_ind) for i, item in enumerate(items)])
[apply_highlight(item, i, hi_ind) for i, item in enumerate(items)]
)
def format_bytes(byte_arr: bytearray, fmt: str, group: int = 1, highlight: int = -1): def format_bytes(byte_arr: bytearray, fmt: str, group: int = 1, highlight: int = -1):
"""Format byte array as per fmt. Group into groups of size `group`, and highlight index `highlight`.""" """Format byte array as per fmt. Group into groups of size `group`, and highlight index `highlight`."""
chunks = create_chunks(byte_arr, group) chunks = create_chunks(byte_arr, group)
if fmt == "hex": if fmt == 'hex':
return highlight_in_list(["0x{}".format(ch.hex()) for ch in chunks], highlight) return highlight_in_list(['0x{}'.format(ch.hex()) for ch in chunks], highlight)
if fmt == "int": if fmt == 'int':
spc = str(ceil(log10(2 ** (group * 8 - 1))) + 1) spc = str(ceil(log10(2 ** (group * 8 - 1))) + 1)
return highlight_in_list( return highlight_in_list([('{:0' + spc + 'd}').format(Int32(ch)) for ch in chunks], highlight)
[("{:0" + spc + "d}").format(Int32(ch)) for ch in chunks], highlight if fmt == 'uint':
)
if fmt == "uint":
spc = str(ceil(log10(2 ** (group * 8)))) spc = str(ceil(log10(2 ** (group * 8))))
return highlight_in_list( return highlight_in_list([('{:0' + spc + 'd}').format(UInt32(ch)) for ch in chunks],
[("{:0" + spc + "d}").format(UInt32(ch)) for ch in chunks], highlight highlight)
) if fmt == 'char':
if fmt == "char":
return highlight_in_list((repr(chr(b))[1:-1] for b in byte_arr), highlight, "") return highlight_in_list((repr(chr(b))[1:-1] for b in byte_arr), highlight, "")
T = TypeVar("T") T = TypeVar('T')
class Peekable(Generic[T], Iterator[T]): class Peekable(Generic[T], Iterator[T]):
@ -108,10 +98,6 @@ class Peekable(Generic[T], Iterator[T]):
def get_section_base_name(section_name: str) -> str: def get_section_base_name(section_name: str) -> str:
if "." not in section_name: if '.' not in section_name:
print( print(FMT_PARSE + f"Invalid section {section_name}, not starting with a dot!" + FMT_NONE)
FMT_PARSE return '.' + section_name.split('.')[1]
+ f"Invalid section {section_name}, not starting with a dot!"
+ FMT_NONE
)
return "." + section_name.split(".")[1]

@ -10,52 +10,52 @@ class RV32A(InstructionSet):
for this? for this?
""" """
def instruction_lr_w(self, ins: "Instruction"): def instruction_lr_w(self, ins: 'Instruction'):
INS_NOT_IMPLEMENTED(ins) INS_NOT_IMPLEMENTED(ins)
def instruction_sc_w(self, ins: "Instruction"): def instruction_sc_w(self, ins: 'Instruction'):
INS_NOT_IMPLEMENTED(ins) INS_NOT_IMPLEMENTED(ins)
def instruction_amoswap_w(self, ins: "Instruction"): def instruction_amoswap_w(self, ins: 'Instruction'):
dest, addr, val = self.parse_rd_rs_rs(ins) dest, addr, val = self.parse_rd_rs_rs(ins)
if dest == "zero": if dest == 'zero':
self.mmu.write(addr, val.to_bytes()) self.mmu.write(addr, val.to_bytes())
else: else:
old = Int32(self.mmu.read(addr, 4)) old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, val.to_bytes()) self.mmu.write(addr, val.to_bytes())
self.regs.set(dest, old) self.regs.set(dest, old)
def instruction_amoadd_w(self, ins: "Instruction"): def instruction_amoadd_w(self, ins: 'Instruction'):
dest, addr, val = self.parse_rd_rs_rs(ins) dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4)) old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, (old + val).to_bytes(4)) self.mmu.write(addr, (old + val).to_bytes(4))
self.regs.set(dest, old) self.regs.set(dest, old)
def instruction_amoand_w(self, ins: "Instruction"): def instruction_amoand_w(self, ins: 'Instruction'):
dest, addr, val = self.parse_rd_rs_rs(ins) dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4)) old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, (old & val).to_bytes(4)) self.mmu.write(addr, (old & val).to_bytes(4))
self.regs.set(dest, old) self.regs.set(dest, old)
def instruction_amoor_w(self, ins: "Instruction"): def instruction_amoor_w(self, ins: 'Instruction'):
dest, addr, val = self.parse_rd_rs_rs(ins) dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4)) old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, (old | val).to_bytes(4)) self.mmu.write(addr, (old | val).to_bytes(4))
self.regs.set(dest, old) self.regs.set(dest, old)
def instruction_amoxor_w(self, ins: "Instruction"): def instruction_amoxor_w(self, ins: 'Instruction'):
dest, addr, val = self.parse_rd_rs_rs(ins) dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4)) old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, (old ^ val).to_bytes(4)) self.mmu.write(addr, (old ^ val).to_bytes(4))
self.regs.set(dest, old) self.regs.set(dest, old)
def instruction_amomax_w(self, ins: "Instruction"): def instruction_amomax_w(self, ins: 'Instruction'):
dest, addr, val = self.parse_rd_rs_rs(ins) dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4)) old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, max(old, val).to_bytes(4)) self.mmu.write(addr, max(old, val).to_bytes(4))
self.regs.set(dest, old) self.regs.set(dest, old)
def instruction_amomaxu_w(self, ins: "Instruction"): def instruction_amomaxu_w(self, ins: 'Instruction'):
val: UInt32 val: UInt32
dest, addr, val = self.parse_rd_rs_rs(ins, signed=False) dest, addr, val = self.parse_rd_rs_rs(ins, signed=False)
old = UInt32(self.mmu.read(addr, 4)) old = UInt32(self.mmu.read(addr, 4))
@ -63,13 +63,13 @@ class RV32A(InstructionSet):
self.mmu.write(addr, max(old, val).to_bytes()) self.mmu.write(addr, max(old, val).to_bytes())
self.regs.set(dest, old) self.regs.set(dest, old)
def instruction_amomin_w(self, ins: "Instruction"): def instruction_amomin_w(self, ins: 'Instruction'):
dest, addr, val = self.parse_rd_rs_rs(ins) dest, addr, val = self.parse_rd_rs_rs(ins)
old = Int32(self.mmu.read(addr, 4)) old = Int32(self.mmu.read(addr, 4))
self.mmu.write(addr, min(old, val).to_bytes(4)) self.mmu.write(addr, min(old, val).to_bytes(4))
self.regs.set(dest, old) self.regs.set(dest, old)
def instruction_amominu_w(self, ins: "Instruction"): def instruction_amominu_w(self, ins: 'Instruction'):
val: UInt32 val: UInt32
dest, addr, val = self.parse_rd_rs_rs(ins, signed=False) dest, addr, val = self.parse_rd_rs_rs(ins, signed=False)
old = UInt32(self.mmu.read(addr, 4)) old = UInt32(self.mmu.read(addr, 4))

@ -1,609 +0,0 @@
"""
RiscEmu (c) 2023 Anton Lydike
SPDX-License-Identifier: MIT
This file contains copious amounts of docstrings that were all taken
from https://msyksphinz-self.github.io/riscv-isadoc/html/rvfd.html
(all the docstrings on the instruction methods documenting the opcodes
and their function)
"""
from typing import Tuple
from .instruction_set import InstructionSet, Instruction
from riscemu.types import INS_NOT_IMPLEMENTED, Float32, Int32, UInt32
class RV32F(InstructionSet):
def instruction_fmadd_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|rs3 |00 |rs2 |rs1 |rm |rd |10000|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fmadd.s rd,rs1,rs2,rs3
:Description:
| Perform single-precision fused multiply addition.
:Implementation:
| f[rd] = f[rs1]×f[rs2]+f[rs3]
"""
rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins)
self.regs.set_f(rd, rs1 * rs2 + rs3)
def instruction_fmsub_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|rs3 |00 |rs2 |rs1 |rm |rd |10001|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fmsub.s rd,rs1,rs2,rs3
:Description:
| Perform single-precision fused multiply addition.
:Implementation:
| f[rd] = f[rs1]×f[rs2]-f[rs3]
"""
rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins)
self.regs.set_f(rd, rs1 * rs2 - rs3)
def instruction_fnmsub_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|rs3 |00 |rs2 |rs1 |rm |rd |10010|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fnmsub.s rd,rs1,rs2,rs3
:Description:
| Perform single-precision fused multiply addition.
:Implementation:
| f[rd] = -f[rs1]×f[rs2]+f[rs3]
"""
rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins)
self.regs.set_f(rd, -rs1 * rs2 + rs3)
def instruction_fnmadd_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|rs3 |00 |rs2 |rs1 |rm |rd |10011|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fnmadd.s rd,rs1,rs2,rs3
:Description:
| Perform single-precision fused multiply addition.
:Implementation:
| f[rd] = -f[rs1]×f[rs2]-f[rs3]
"""
rd, rs1, rs2, rs3 = self.parse_rd_rs_rs_rs(ins)
self.regs.set_f(rd, -rs1 * rs2 - rs3)
def instruction_fadd_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|00000|00 |rs2 |rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fadd.s rd,rs1,rs2
:Description:
| Perform single-precision floating-point addition.
:Implementation:
| f[rd] = f[rs1] + f[rs2]
"""
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set_f(
rd,
rs1 + rs2,
)
def instruction_fsub_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|00001|00 |rs2 |rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fsub.s rd,rs1,rs2
:Description:
| Perform single-precision floating-point substraction.
:Implementation:
| f[rd] = f[rs1] - f[rs2]
"""
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set_f(
rd,
rs1 - rs2,
)
def instruction_fmul_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|00010|00 |rs2 |rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fmul.s rd,rs1,rs2
:Description:
| Perform single-precision floating-point multiplication.
:Implementation:
| f[rd] = f[rs1] × f[rs2]
"""
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set_f(
rd,
rs1 * rs2,
)
def instruction_fdiv_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|00011|00 |rs2 |rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fdiv.s rd,rs1,rs2
:Description:
| Perform single-precision floating-point division.
:Implementation:
| f[rd] = f[rs1] / f[rs2]
"""
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set_f(
rd,
rs1 / rs2,
)
def instruction_fsqrt_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|01011|00 |00000|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fsqrt.s rd,rs1
:Description:
| Perform single-precision square root.
:Implementation:
| f[rd] = sqrt(f[rs1])
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set_f(rd, self.regs.get_f(rs) ** 0.5)
def instruction_fsgnj_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|00100|00 |rs2 |rs1 |000 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fsgnj.s rd,rs1,rs2
:Description:
| Produce a result that takes all bits except the sign bit from rs1.
| The results sign bit is rs2s sign bit.
:Implementation:
| f[rd] = {f[rs2][31], f[rs1][30:0]}
"""
INS_NOT_IMPLEMENTED(ins)
def instruction_fsgnjn_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|00100|00 |rs2 |rs1 |001 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fsgnjn.s rd,rs1,rs2
:Description:
| Produce a result that takes all bits except the sign bit from rs1.
| The results sign bit is opposite of rs2s sign bit.
:Implementation:
| f[rd] = {~f[rs2][31], f[rs1][30:0]}
"""
INS_NOT_IMPLEMENTED(ins)
def instruction_fsgnjx_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|00100|00 |rs2 |rs1 |010 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fsgnjx.s rd,rs1,rs2
:Description:
| Produce a result that takes all bits except the sign bit from rs1.
| The results sign bit is XOR of sign bit of rs1 and rs2.
:Implementation:
| f[rd] = {f[rs1][31] ^ f[rs2][31], f[rs1][30:0]}
"""
INS_NOT_IMPLEMENTED(ins)
def instruction_fmin_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|00101|00 |rs2 |rs1 |000 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fmin.s rd,rs1,rs2
:Description:
| Write the smaller of single precision data in rs1 and rs2 to rd.
:Implementation:
| f[rd] = min(f[rs1], f[rs2])
"""
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set_f(
rd,
Float32(
min(
rs1.value,
rs2.value,
)
),
)
def instruction_fmax_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|00101|00 |rs2 |rs1 |001 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fmax.s rd,rs1,rs2
:Description:
| Write the larger of single precision data in rs1 and rs2 to rd.
:Implementation:
| f[rd] = max(f[rs1], f[rs2])
"""
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set_f(
rd,
Float32(
max(
rs1.value,
rs2.value,
)
),
)
def instruction_fcvt_w_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11000|00 |00000|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.w.s rd,rs1
:Description:
| Convert a floating-point number in floating-point register rs1 to a signed 32-bit in integer register rd.
:Implementation:
| x[rd] = sext(s32_{f32}(f[rs1]))
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set(rd, Int32(self.regs.get_f(rs).bytes))
def instruction_fcvt_wu_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11000|00 |00001|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.wu.s rd,rs1
:Description:
| Convert a floating-point number in floating-point register rs1 to a signed 32-bit in unsigned integer register rd.
:Implementation:
| x[rd] = sext(u32_{f32}(f[rs1]))
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set(rd, UInt32(self.regs.get_f(rs).bytes))
def instruction_fmv_x_w(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11100|00 |00000|rs1 |000 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fmv.x.w rd,rs1
:Description:
| Move the single-precision value in floating-point register rs1 represented in IEEE 754-2008 encoding to the lower 32 bits of integer register rd.
:Implementation:
| x[rd] = sext(f[rs1][31:0])
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set(rd, UInt32(self.regs.get_f(rs).bits))
def instruction_feq_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|10100|00 |rs2 |rs1 |010 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| feq.s rd,rs1,rs2
:Description:
| Performs a quiet equal comparison between floating-point registers rs1 and rs2 and record the Boolean result in integer register rd.
| Only signaling NaN inputs cause an Invalid Operation exception.
| The result is 0 if either operand is NaN.
:Implementation:
| x[rd] = f[rs1] == f[rs2]
"""
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, Int32(rs1 == rs2))
def instruction_flt_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|10100|00 |rs2 |rs1 |001 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| flt.s rd,rs1,rs2
:Description:
| Performs a quiet less comparison between floating-point registers rs1 and rs2 and record the Boolean result in integer register rd.
| Only signaling NaN inputs cause an Invalid Operation exception.
| The result is 0 if either operand is NaN.
:Implementation:
| x[rd] = f[rs1] < f[rs2]
"""
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, Int32(rs1 < rs2))
def instruction_fle_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|10100|00 |rs2 |rs1 |000 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fle.s rd,rs1,rs2
:Description:
| Performs a quiet less or equal comparison between floating-point registers rs1 and rs2 and record the Boolean result in integer register rd.
| Only signaling NaN inputs cause an Invalid Operation exception.
| The result is 0 if either operand is NaN.
:Implementation:
| x[rd] = f[rs1] <= f[rs2]
"""
rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, Int32(rs1 <= rs2))
def instruction_fclass_s(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11100|00 |00000|rs1 |001 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fclass.s rd,rs1
:Description:
| Examines the value in floating-point register rs1 and writes to integer register rd a 10-bit mask that indicates the class of the floating-point number.
| The format of the mask is described in [classify table]_.
| The corresponding bit in rd will be set if the property is true and clear otherwise.
| All other bits in rd are cleared. Note that exactly one bit in rd will be set.
:Implementation:
| x[rd] = classifys(f[rs1])
"""
INS_NOT_IMPLEMENTED(ins)
def instruction_fcvt_s_w(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11010|00 |00000|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.s.w rd,rs1
:Description:
| Converts a 32-bit signed integer, in integer register rs1 into a floating-point number in floating-point register rd.
:Implementation:
| f[rd] = f32_{s32}(x[rs1])
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set_f(rd, Float32.from_bytes(self.regs.get(rs).signed().value))
def instruction_fcvt_s_wu(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11010|00 |00001|rs1 |rm |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fcvt.s.wu rd,rs1
:Description:
| Converts a 32-bit unsigned integer, in integer register rs1 into a floating-point number in floating-point register rd.
:Implementation:
| f[rd] = f32_{u32}(x[rs1])
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set_f(rd, Float32.from_bytes(self.regs.get(rs).unsigned_value))
def instruction_fmv_w_x(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|11110|00 |00000|rs1 |000 |rd |10100|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| fmv.w.x rd,rs1
:Description:
| Move the single-precision value encoded in IEEE 754-2008 standard encoding from the lower 32 bits of integer register rs1 to the floating-point register rd.
:Implementation:
| f[rd] = x[rs1][31:0]
"""
rd, rs = self.parse_rd_rs(ins)
self.regs.set_f(rd, Float32.from_bytes(self.regs.get(rs).unsigned_value))
def instruction_flw(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+-----+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+-----+-----+---+
|imm[11:0] |rs1 |010 |rd |00001|11 |
+-----+-----+-----+-----+-----+-----+-----+---+
:Format:
| flw rd,offset(rs1)
:Description:
| Load a single-precision floating-point value from memory into floating-point register rd.
:Implementation:
| f[rd] = M[x[rs1] + sext(offset)][31:0]
"""
rd, addr = self.parse_mem_ins(ins)
self.regs.set_f(rd, self.mmu.read_float(addr.value))
def instruction_fsw(self, ins: Instruction):
"""
+-----+-----+-----+-----+-----+--------+-----+---+
|31-27|26-25|24-20|19-15|14-12|11-7 |6-2 |1-0|
+-----+-----+-----+-----+-----+--------+-----+---+
|imm[11:5] |rs2 |rs1 |010 |imm[4:0]|01001|11 |
+-----+-----+-----+-----+-----+--------+-----+---+
:Format:
| fsw rs2,offset(rs1)
:Description:
| Store a single-precision value from floating-point register rs2 to memory.
:Implementation:
| M[x[rs1] + sext(offset)] = f[rs2][31:0]
"""
rs, addr = self.parse_mem_ins(ins)
val = self.regs.get_f(rs)
self.mmu.write(addr.value, 4, bytearray(val.bytes))
def parse_rd_rs(self, ins: Instruction) -> Tuple[str, str]:
assert len(ins.args) == 2
return ins.get_reg(0), ins.get_reg(1)
def parse_rd_rs_rs(self, ins: Instruction) -> Tuple[str, Float32, Float32]:
assert len(ins.args) == 3
return (
ins.get_reg(0),
self.regs.get_f(ins.get_reg(1)),
self.regs.get_f(ins.get_reg(2)),
)
def parse_rd_rs_rs_rs(
self, ins: Instruction
) -> Tuple[str, Float32, Float32, Float32]:
assert len(ins.args) == 4
return (
ins.get_reg(0),
self.regs.get_f(ins.get_reg(1)),
self.regs.get_f(ins.get_reg(2)),
self.regs.get_f(ins.get_reg(3)),
)

@ -22,186 +22,241 @@ class RV32I(InstructionSet):
See https://maxvytech.com/images/RV32I-11-2018.pdf for a more detailed overview See https://maxvytech.com/images/RV32I-11-2018.pdf for a more detailed overview
""" """
def instruction_lb(self, ins: "Instruction"): def instruction_lb(self, ins: 'Instruction'):
rd, addr = self.parse_mem_ins(ins) rd, addr = self.parse_mem_ins(ins)
self.regs.set(rd, Int32.sign_extend(self.mmu.read(addr.unsigned_value, 1), 8)) self.regs.set(rd, Int32.sign_extend(self.mmu.read(addr.unsigned_value, 1), 8))
def instruction_lh(self, ins: "Instruction"): def instruction_lh(self, ins: 'Instruction'):
rd, addr = self.parse_mem_ins(ins) rd, addr = self.parse_mem_ins(ins)
self.regs.set(rd, Int32.sign_extend(self.mmu.read(addr.unsigned_value, 2), 16)) self.regs.set(rd, Int32.sign_extend(self.mmu.read(addr.unsigned_value, 2), 16))
def instruction_lw(self, ins: "Instruction"): def instruction_lw(self, ins: 'Instruction'):
rd, addr = self.parse_mem_ins(ins) rd, addr = self.parse_mem_ins(ins)
self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 4))) self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 4)))
def instruction_lbu(self, ins: "Instruction"): def instruction_lbu(self, ins: 'Instruction'):
rd, addr = self.parse_mem_ins(ins) rd, addr = self.parse_mem_ins(ins)
self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 1))) self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 1)))
def instruction_lhu(self, ins: "Instruction"): def instruction_lhu(self, ins: 'Instruction'):
rd, addr = self.parse_mem_ins(ins) rd, addr = self.parse_mem_ins(ins)
self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 2))) self.regs.set(rd, Int32(self.mmu.read(addr.unsigned_value, 2)))
def instruction_sb(self, ins: "Instruction"): def instruction_sb(self, ins: 'Instruction'):
rd, addr = self.parse_mem_ins(ins) rd, addr = self.parse_mem_ins(ins)
self.mmu.write(addr.unsigned_value, 1, self.regs.get(rd).to_bytes(1)) self.mmu.write(addr.unsigned_value, 1, self.regs.get(rd).to_bytes(1))
def instruction_sh(self, ins: "Instruction"): def instruction_sh(self, ins: 'Instruction'):
rd, addr = self.parse_mem_ins(ins) rd, addr = self.parse_mem_ins(ins)
self.mmu.write(addr.unsigned_value, 2, self.regs.get(rd).to_bytes(2)) self.mmu.write(addr.unsigned_value, 2, self.regs.get(rd).to_bytes(2))
def instruction_sw(self, ins: "Instruction"): def instruction_sw(self, ins: 'Instruction'):
rd, addr = self.parse_mem_ins(ins) rd, addr = self.parse_mem_ins(ins)
self.mmu.write(addr.unsigned_value, 4, self.regs.get(rd).to_bytes(4)) self.mmu.write(addr.unsigned_value, 4, self.regs.get(rd).to_bytes(4))
def instruction_sll(self, ins: "Instruction"): def instruction_sll(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0) dst = ins.get_reg(0)
src1 = ins.get_reg(1) src1 = ins.get_reg(1)
src2 = ins.get_reg(2) src2 = ins.get_reg(2)
self.regs.set(dst, self.regs.get(src1) << (self.regs.get(src2) & 0b11111)) self.regs.set(
dst,
self.regs.get(src1) << (self.regs.get(src2) & 0b11111)
)
def instruction_slli(self, ins: "Instruction"): def instruction_slli(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0) dst = ins.get_reg(0)
src1 = ins.get_reg(1) src1 = ins.get_reg(1)
imm = ins.get_imm(2) imm = ins.get_imm(2)
self.regs.set(dst, self.regs.get(src1) << (imm & 0b11111)) self.regs.set(
dst,
self.regs.get(src1) << (imm & 0b11111)
)
def instruction_srl(self, ins: "Instruction"): def instruction_srl(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0) dst = ins.get_reg(0)
src1 = ins.get_reg(1) src1 = ins.get_reg(1)
src2 = ins.get_reg(2) src2 = ins.get_reg(2)
self.regs.set( self.regs.set(
dst, self.regs.get(src1).shift_right_logical(self.regs.get(src2) & 0b11111) dst,
self.regs.get(src1).shift_right_logical(self.regs.get(src2) & 0b11111)
) )
def instruction_srli(self, ins: "Instruction"): def instruction_srli(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0) dst = ins.get_reg(0)
src1 = ins.get_reg(1) src1 = ins.get_reg(1)
imm = ins.get_imm(2) imm = ins.get_imm(2)
self.regs.set(dst, self.regs.get(src1).shift_right_logical(imm & 0b11111)) self.regs.set(
dst,
self.regs.get(src1).shift_right_logical(imm & 0b11111)
)
def instruction_sra(self, ins: "Instruction"): def instruction_sra(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0) dst = ins.get_reg(0)
src1 = ins.get_reg(1) src1 = ins.get_reg(1)
src2 = ins.get_reg(2) src2 = ins.get_reg(2)
self.regs.set(dst, self.regs.get(src1) >> (self.regs.get(src2) & 0b11111)) self.regs.set(
dst,
self.regs.get(src1) >> (self.regs.get(src2) & 0b11111)
)
def instruction_srai(self, ins: "Instruction"): def instruction_srai(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
dst = ins.get_reg(0) dst = ins.get_reg(0)
src1 = ins.get_reg(1) src1 = ins.get_reg(1)
imm = ins.get_imm(2) imm = ins.get_imm(2)
self.regs.set(dst, self.regs.get(src1) >> (imm & 0b11111)) self.regs.set(
dst,
self.regs.get(src1) >> (imm & 0b11111)
)
def instruction_add(self, ins: "Instruction"): def instruction_add(self, ins: 'Instruction'):
# FIXME: once configuration is figured out, add flag to support immediate arg in add instruction # FIXME: once configuration is figured out, add flag to support immediate arg in add instruction
dst, rs1, rs2 = self.parse_rd_rs_rs(ins) dst, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(dst, rs1 + rs2) self.regs.set(
dst,
rs1 + rs2
)
def instruction_addi(self, ins: "Instruction"): def instruction_addi(self, ins: 'Instruction'):
dst, rs1, imm = self.parse_rd_rs_imm(ins) dst, rs1, imm = self.parse_rd_rs_imm(ins)
self.regs.set(dst, rs1 + imm) self.regs.set(
dst,
rs1 + imm
)
def instruction_sub(self, ins: "Instruction"): def instruction_sub(self, ins: 'Instruction'):
dst, rs1, rs2 = self.parse_rd_rs_rs(ins) dst, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(dst, rs1 - rs2) self.regs.set(
dst,
rs1 - rs2
)
def instruction_lui(self, ins: "Instruction"): def instruction_lui(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 2) ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0) reg = ins.get_reg(0)
imm = UInt32(ins.get_imm(1) << 12) imm = UInt32(ins.get_imm(1) << 12)
self.regs.set(reg, Int32(imm)) self.regs.set(reg, Int32(imm))
def instruction_auipc(self, ins: "Instruction"): def instruction_auipc(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 2) ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0) reg = ins.get_reg(0)
imm = UInt32(ins.get_imm(1) << 12) imm = UInt32(ins.get_imm(1) << 12)
self.regs.set(reg, imm.signed() + self.pc) self.regs.set(reg, imm.signed() + self.pc)
def instruction_xor(self, ins: "Instruction"): def instruction_xor(self, ins: 'Instruction'):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins) rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, rs1 ^ rs2) self.regs.set(
rd,
rs1 ^ rs2
)
def instruction_xori(self, ins: "Instruction"): def instruction_xori(self, ins: 'Instruction'):
rd, rs1, imm = self.parse_rd_rs_imm(ins) rd, rs1, imm = self.parse_rd_rs_imm(ins)
self.regs.set(rd, rs1 ^ imm) self.regs.set(
rd,
rs1 ^ imm
)
def instruction_or(self, ins: "Instruction"): def instruction_or(self, ins: 'Instruction'):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins) rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, rs1 | rs2) self.regs.set(
rd,
rs1 | rs2
)
def instruction_ori(self, ins: "Instruction"): def instruction_ori(self, ins: 'Instruction'):
rd, rs1, imm = self.parse_rd_rs_imm(ins) rd, rs1, imm = self.parse_rd_rs_imm(ins)
self.regs.set(rd, rs1 | imm) self.regs.set(
rd,
rs1 | imm
)
def instruction_and(self, ins: "Instruction"): def instruction_and(self, ins: 'Instruction'):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins) rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, rs1 & rs2) self.regs.set(
rd,
rs1 & rs2
)
def instruction_andi(self, ins: "Instruction"): def instruction_andi(self, ins: 'Instruction'):
rd, rs1, imm = self.parse_rd_rs_imm(ins) rd, rs1, imm = self.parse_rd_rs_imm(ins)
self.regs.set(rd, rs1 & imm) self.regs.set(
rd,
rs1 & imm
)
def instruction_slt(self, ins: "Instruction"): def instruction_slt(self, ins: 'Instruction'):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins) rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, Int32(int(rs1 < rs2))) self.regs.set(
rd,
Int32(int(rs1 < rs2))
)
def instruction_slti(self, ins: "Instruction"): def instruction_slti(self, ins: 'Instruction'):
rd, rs1, imm = self.parse_rd_rs_imm(ins) rd, rs1, imm = self.parse_rd_rs_imm(ins)
self.regs.set(rd, Int32(int(rs1 < imm))) self.regs.set(
rd,
Int32(int(rs1 < imm))
)
def instruction_sltu(self, ins: "Instruction"): def instruction_sltu(self, ins: 'Instruction'):
dst, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False) dst, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False)
self.regs.set(dst, Int32(int(rs1 < rs2))) self.regs.set(
dst,
Int32(int(rs1 < rs2))
)
def instruction_sltiu(self, ins: "Instruction"): def instruction_sltiu(self, ins: 'Instruction'):
dst, rs1, imm = self.parse_rd_rs_imm(ins, signed=False) dst, rs1, imm = self.parse_rd_rs_imm(ins, signed=False)
self.regs.set(dst, Int32(int(rs1 < imm))) self.regs.set(
dst,
Int32(int(rs1 < imm))
)
def instruction_beq(self, ins: "Instruction"): def instruction_beq(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins) rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 == rs2: if rs1 == rs2:
self.pc = dst.unsigned_value self.pc = dst.unsigned_value
def instruction_bne(self, ins: "Instruction"): def instruction_bne(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins) rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 != rs2: if rs1 != rs2:
self.pc = dst.unsigned_value self.pc = dst.unsigned_value
def instruction_blt(self, ins: "Instruction"): def instruction_blt(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins) rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 < rs2: if rs1 < rs2:
self.pc = dst.unsigned_value self.pc = dst.unsigned_value
def instruction_bge(self, ins: "Instruction"): def instruction_bge(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins) rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 >= rs2: if rs1 >= rs2:
self.pc = dst.unsigned_value self.pc = dst.unsigned_value
def instruction_bltu(self, ins: "Instruction"): def instruction_bltu(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False)
if rs1 < rs2: if rs1 < rs2:
self.pc = dst.unsigned_value self.pc = dst.unsigned_value
def instruction_bgeu(self, ins: "Instruction"): def instruction_bgeu(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False)
if rs1 >= rs2: if rs1 >= rs2:
self.pc = dst.unsigned_value self.pc = dst.unsigned_value
# technically deprecated # technically deprecated
def instruction_j(self, ins: "Instruction"): def instruction_j(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 1) ASSERT_LEN(ins.args, 1)
addr = ins.get_imm(0) addr = ins.get_imm(0)
self.pc = addr self.pc = addr
def instruction_jal(self, ins: "Instruction"): def instruction_jal(self, ins: 'Instruction'):
reg = "ra" # default register is ra reg = 'ra' # default register is ra
if len(ins.args) == 1: if len(ins.args) == 1:
addr = ins.get_imm(0) addr = ins.get_imm(0)
else: else:
@ -211,61 +266,58 @@ class RV32I(InstructionSet):
self.regs.set(reg, Int32(self.pc)) self.regs.set(reg, Int32(self.pc))
self.pc = addr self.pc = addr
def instruction_jalr(self, ins: "Instruction"): def instruction_jalr(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 2) ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0) reg = ins.get_reg(0)
base = ins.get_reg(1) addr = ins.get_imm(1)
addr = ins.get_imm(2)
self.regs.set(reg, Int32(self.pc)) self.regs.set(reg, Int32(self.pc))
self.pc = self.regs.get(base).unsigned_value + addr self.pc = addr
def instruction_ret(self, ins: "Instruction"): def instruction_ret(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0) ASSERT_LEN(ins.args, 0)
self.pc = self.regs.get("ra").value self.pc = self.regs.get('ra').value
def instruction_ecall(self, ins: "Instruction"): def instruction_ecall(self, ins: 'Instruction'):
self.instruction_scall(ins) self.instruction_scall(ins)
def instruction_ebreak(self, ins: "Instruction"): def instruction_ebreak(self, ins: 'Instruction'):
self.instruction_sbreak(ins) self.instruction_sbreak(ins)
def instruction_scall(self, ins: "Instruction"): def instruction_scall(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0) ASSERT_LEN(ins.args, 0)
if not isinstance(self.cpu, UserModeCPU): if not isinstance(self.cpu, UserModeCPU):
# FIXME: add exception for syscall not supported or something # FIXME: add exception for syscall not supported or something
raise raise
syscall = Syscall(self.regs.get("a7"), self.cpu) syscall = Syscall(self.regs.get('a7'), self.cpu)
self.cpu.syscall_int.handle_syscall(syscall) self.cpu.syscall_int.handle_syscall(syscall)
def instruction_sbreak(self, ins: "Instruction"): def instruction_sbreak(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0) ASSERT_LEN(ins.args, 0)
if self.cpu.conf.debug_instruction: if self.cpu.conf.debug_instruction:
print( print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE)
FMT_DEBUG
+ "Debug instruction encountered at 0x{:08X}".format(self.pc - 1)
+ FMT_NONE
)
raise LaunchDebuggerException() raise LaunchDebuggerException()
def instruction_nop(self, ins: "Instruction"): def instruction_nop(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 0) ASSERT_LEN(ins.args, 0)
pass pass
def instruction_li(self, ins: "Instruction"): def instruction_li(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 2) ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0) reg = ins.get_reg(0)
immediate = ins.get_imm(1) immediate = ins.get_imm(1)
self.regs.set(reg, Int32(immediate)) self.regs.set(reg, Int32(immediate))
def instruction_la(self, ins: "Instruction"): def instruction_la(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 2) ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0) reg = ins.get_reg(0)
immediate = ins.get_imm(1) immediate = ins.get_imm(1)
self.regs.set(reg, Int32(immediate)) self.regs.set(reg, Int32(immediate))
def instruction_mv(self, ins: "Instruction"): def instruction_mv(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 2) ASSERT_LEN(ins.args, 2)
rd, rs = ins.get_reg(0), ins.get_reg(1) rd, rs = ins.get_reg(0), ins.get_reg(1)
self.regs.set(rd, self.regs.get(rs)) self.regs.set(rd, self.regs.get(rs))

@ -12,33 +12,50 @@ class RV32M(InstructionSet):
""" """
The RV32M Instruction set, containing multiplication and division instructions The RV32M Instruction set, containing multiplication and division instructions
""" """
def instruction_mul(self, ins: 'Instruction'):
def instruction_mul(self, ins: "Instruction"):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins) rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, rs1 * rs2) self.regs.set(
rd,
rs1 * rs2
)
def instruction_mulh(self, ins: "Instruction"): def instruction_mulh(self, ins: 'Instruction'):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins) rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, (rs1 * rs2) >> 32) self.regs.set(
rd,
(rs1 * rs2) >> 32
)
def instruction_mulhsu(self, ins: "Instruction"): def instruction_mulhsu(self, ins: 'Instruction'):
INS_NOT_IMPLEMENTED(ins) INS_NOT_IMPLEMENTED(ins)
def instruction_mulhu(self, ins: "Instruction"): def instruction_mulhu(self, ins: 'Instruction'):
INS_NOT_IMPLEMENTED(ins) INS_NOT_IMPLEMENTED(ins)
def instruction_div(self, ins: "Instruction"): def instruction_div(self, ins: 'Instruction'):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins) rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, rs1 // rs2) self.regs.set(
rd,
rs1 // rs2
)
def instruction_divu(self, ins: "Instruction"): def instruction_divu(self, ins: 'Instruction'):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False) rd, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False)
self.regs.set(rd, rs1 // rs2) self.regs.set(
rd,
rs1 // rs2
)
def instruction_rem(self, ins: "Instruction"): def instruction_rem(self, ins: 'Instruction'):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins) rd, rs1, rs2 = self.parse_rd_rs_rs(ins)
self.regs.set(rd, rs1 % rs2) self.regs.set(
rd,
rs1 % rs2
)
def instruction_remu(self, ins: "Instruction"): def instruction_remu(self, ins: 'Instruction'):
rd, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False) rd, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False)
self.regs.set(rd, rs1 % rs2) self.regs.set(
rd,
rs1 % rs2
)

@ -1,33 +0,0 @@
from .instruction_set import InstructionSet, Instruction
class RV_Debug(InstructionSet):
def instruction_print(self, ins: Instruction):
reg = ins.get_reg(0)
print("register {} contains value {}".format(reg, self.regs.get(reg)))
def instruction_print_float(self, ins: Instruction):
reg = ins.get_reg(0)
print("register {} contains value {}".format(reg, self.regs.get_f(reg).value))
def instruction_print_uint(self, ins: Instruction):
reg = ins.get_reg(0)
print(
"register {} contains value {}".format(
reg, self.regs.get(reg).unsigned_value
)
)
def instruction_print_hex(self, ins: Instruction):
reg = ins.get_reg(0)
print(
"register {} contains value {}".format(reg, hex(self.regs.get(reg).value))
)
def instruction_print_uhex(self, ins: Instruction):
reg = ins.get_reg(0)
print(
"register {} contains value {}".format(
reg, hex(self.regs.get(reg).unsigned_value)
)
)

@ -6,11 +6,11 @@ SPDX-License-Identifier: MIT
This package holds all instruction sets, available to the processor This package holds all instruction sets, available to the processor
""" """
from .instruction_set import InstructionSet, Instruction from .instruction_set import InstructionSet
from .RV32M import RV32M from .RV32M import RV32M
from .RV32I import RV32I from .RV32I import RV32I
from .RV32A import RV32A from .RV32A import RV32A
from .RV32F import RV32F
from .RV_Debug import RV_Debug
InstructionSetDict = {v.__name__: v for v in [RV32I, RV32M, RV32A, RV32F, RV_Debug]} InstructionSetDict = {
v.__name__: v for v in [RV32I, RV32M, RV32A]
}

@ -23,21 +23,23 @@ class InstructionSet(ABC):
instructions containing a dot '.' should replace it with an underscore. instructions containing a dot '.' should replace it with an underscore.
""" """
def __init__(self, cpu: "CPU"): def __init__(self, cpu: 'CPU'):
"""Create a new instance of the Instruction set. This requires access to a CPU, and grabs vertain things """Create a new instance of the Instruction set. This requires access to a CPU, and grabs vertain things
from it such as access to the MMU and registers. from it such as access to the MMU and registers.
""" """
self.name = self.__class__.__name__ self.name = self.__class__.__name__
self.cpu = cpu self.cpu = cpu
def load(self) -> Dict[str, Callable[["Instruction"], None]]: def load(self) -> Dict[str, Callable[['Instruction'], None]]:
""" """
This is called by the CPU once it instantiates this instruction set This is called by the CPU once it instantiates this instruction set
It returns a dictionary of all instructions in this instruction set, It returns a dictionary of all instructions in this instruction set,
pointing to the correct handler for it pointing to the correct handler for it
""" """
return {name: ins for name, ins in self.get_instructions()} return {
name: ins for name, ins in self.get_instructions()
}
def get_instructions(self): def get_instructions(self):
""" """
@ -46,10 +48,10 @@ class InstructionSet(ABC):
converts underscores in names to dots converts underscores in names to dots
""" """
for member in dir(self): for member in dir(self):
if member.startswith("instruction_"): if member.startswith('instruction_'):
yield member[12:].replace("_", "."), getattr(self, member) yield member[12:].replace('_', '.'), getattr(self, member)
def parse_mem_ins(self, ins: "Instruction") -> Tuple[str, Int32]: def parse_mem_ins(self, ins: 'Instruction') -> Tuple[str, Int32]:
""" """
parses both rd, rs, imm and rd, imm(rs) argument format and returns (rd, imm+rs1) parses both rd, rs, imm and rd, imm(rs) argument format and returns (rd, imm+rs1)
(so a register and address tuple for memory instructions) (so a register and address tuple for memory instructions)
@ -67,69 +69,51 @@ class InstructionSet(ABC):
rd = ins.get_reg(0) rd = ins.get_reg(0)
return rd, rs + imm return rd, rs + imm
def parse_rd_rs_rs( def parse_rd_rs_rs(self, ins: 'Instruction', signed=True) -> Tuple[str, Int32, Int32]:
self, ins: "Instruction", signed=True
) -> Tuple[str, Int32, Int32]:
""" """
Assumes the command is in <name> rd, rs1, rs2 format Assumes the command is in <name> rd, rs1, rs2 format
Returns the name of rd, and the values in rs1 and rs2 Returns the name of rd, and the values in rs1 and rs2
""" """
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
if signed: if signed:
return ( return ins.get_reg(0), \
ins.get_reg(0), self.get_reg_content(ins, 1), \
Int32(self.get_reg_content(ins, 1)), self.get_reg_content(ins, 2)
Int32(self.get_reg_content(ins, 2)),
)
else: else:
return ( return ins.get_reg(0), \
ins.get_reg(0), UInt32(self.get_reg_content(ins, 1)), \
UInt32(self.get_reg_content(ins, 1)), UInt32(self.get_reg_content(ins, 2))
UInt32(self.get_reg_content(ins, 2)),
)
def parse_rd_rs_imm( def parse_rd_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[str, Int32, Int32]:
self, ins: "Instruction", signed=True
) -> Tuple[str, Int32, Int32]:
""" """
Assumes the command is in <name> rd, rs, imm format Assumes the command is in <name> rd, rs, imm format
Returns the name of rd, the value in rs and the immediate imm Returns the name of rd, the value in rs and the immediate imm
""" """
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
if signed: if signed:
return ( return ins.get_reg(0), \
ins.get_reg(0), Int32(self.get_reg_content(ins, 1)), \
Int32(self.get_reg_content(ins, 1)), Int32(ins.get_imm(2))
Int32(ins.get_imm(2)),
)
else: else:
return ( return ins.get_reg(0), \
ins.get_reg(0), UInt32(self.get_reg_content(ins, 1)), \
UInt32(self.get_reg_content(ins, 1)), UInt32(ins.get_imm(2))
UInt32(ins.get_imm(2)),
)
def parse_rs_rs_imm( def parse_rs_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[Int32, Int32, Int32]:
self, ins: "Instruction", signed=True
) -> Tuple[Int32, Int32, Int32]:
""" """
Assumes the command is in <name> rs1, rs2, imm format Assumes the command is in <name> rs1, rs2, imm format
Returns the values in rs1, rs2 and the immediate imm Returns the values in rs1, rs2 and the immediate imm
""" """
if signed: if signed:
return ( return Int32(self.get_reg_content(ins, 0)), \
Int32(self.get_reg_content(ins, 0)), Int32(self.get_reg_content(ins, 1)), \
Int32(self.get_reg_content(ins, 1)), Int32(ins.get_imm(2))
Int32(ins.get_imm(2)),
)
else: else:
return ( return UInt32(self.get_reg_content(ins, 0)), \
UInt32(self.get_reg_content(ins, 0)), UInt32(self.get_reg_content(ins, 1)), \
UInt32(self.get_reg_content(ins, 1)), UInt32(ins.get_imm(2))
UInt32(ins.get_imm(2)),
)
def get_reg_content(self, ins: "Instruction", ind: int) -> Int32: def get_reg_content(self, ins: 'Instruction', ind: int) -> Int32:
""" """
get the register name from ins and then return the register contents get the register name from ins and then return the register contents
""" """
@ -156,5 +140,6 @@ class InstructionSet(ABC):
def __repr__(self): def __repr__(self):
return "InstructionSet[{}] with {} instructions".format( return "InstructionSet[{}] with {} instructions".format(
self.__class__.__name__, len(list(self.get_instructions())) self.__class__.__name__,
len(list(self.get_instructions()))
) )

@ -3,34 +3,26 @@ import sys
from riscemu import RunConfig from riscemu import RunConfig
from riscemu.types import InstructionMemorySection, SimpleInstruction, Program from riscemu.types import InstructionMemorySection, SimpleInstruction, Program
if __name__ == "__main__": if __name__ == '__main__':
from .CPU import UserModeCPU from .CPU import UserModeCPU
from .instructions import InstructionSetDict from .instructions import InstructionSetDict
from .debug import launch_debug_session from .debug import launch_debug_session
cpu = UserModeCPU(list(InstructionSetDict.values()), RunConfig(verbosity=4)) cpu = UserModeCPU(list(InstructionSetDict.values()), RunConfig(verbosity=4))
program = Program("interactive session", base=0x100) program = Program('interactive session', base=0x100)
context = program.context context = program.context
program.add_section( program.add_section(InstructionMemorySection([
InstructionMemorySection( SimpleInstruction('ebreak', (), context, 0x100),
[ SimpleInstruction('addi', ('a0', 'zero', '0'), context, 0x104),
SimpleInstruction("ebreak", (), context, 0x100), SimpleInstruction('addi', ('a7', 'zero', '93'), context, 0x108),
SimpleInstruction("addi", ("a0", "zero", "0"), context, 0x104), SimpleInstruction('scall', (), context, 0x10C),
SimpleInstruction("addi", ("a7", "zero", "93"), context, 0x108), ], '.text', context, program.name, 0x100))
SimpleInstruction("scall", (), context, 0x10C),
],
".text",
context,
program.name,
0x100,
)
)
cpu.load_program(program) cpu.load_program(program)
cpu.setup_stack() cpu.setup_stack()
cpu.launch() cpu.launch(program)
sys.exit(cpu.exit_code) sys.exit(cpu.exit_code)

@ -16,32 +16,28 @@ from .types.exceptions import ParseException
def parse_instruction(token: Token, args: Tuple[str], context: ParseContext): def parse_instruction(token: Token, args: Tuple[str], context: ParseContext):
if context.section is None: if context.section is None:
context.new_section(".text", MemorySectionType.Instructions) context.new_section('.text', MemorySectionType.Instructions)
if context.section.type != MemorySectionType.Instructions: if context.section.type != MemorySectionType.Instructions:
raise ParseException( raise ParseException("{} {} encountered in invalid context: {}".format(token, args, context))
"{} {} encountered in invalid context: {}".format(token, args, context) ins = SimpleInstruction(token.value, args, context.context, context.current_address())
)
ins = SimpleInstruction(
token.value, args, context.context, context.current_address()
)
context.section.data.append(ins) context.section.data.append(ins)
def parse_label(token: Token, args: Tuple[str], context: ParseContext): def parse_label(token: Token, args: Tuple[str], context: ParseContext):
name = token.value[:-1] name = token.value[:-1]
if re.match(r"^\d+$", name): if re.match(r'^\d+$', name):
# relative label: # relative label:
context.context.numbered_labels[name].append(context.current_address()) context.context.numbered_labels[name].append(context.current_address())
else: else:
if name in context.context.labels: if name in context.context.labels:
print(FMT_PARSE + "Warn: Symbol {} defined twice!".format(name)) print(FMT_PARSE + 'Warn: Symbol {} defined twice!'.format(name))
context.add_label(name, context.current_address(), is_relative=True) context.add_label(name, context.current_address(), is_relative=True)
PARSERS: Dict[TokenType, Callable[[Token, Tuple[str], ParseContext], None]] = { PARSERS: Dict[TokenType, Callable[[Token, Tuple[str], ParseContext], None]] = {
TokenType.PSEUDO_OP: AssemblerDirectives.handle_instruction, TokenType.PSEUDO_OP: AssemblerDirectives.handle_instruction,
TokenType.LABEL: parse_label, TokenType.LABEL: parse_label,
TokenType.INSTRUCTION_NAME: parse_instruction, TokenType.INSTRUCTION_NAME: parse_instruction
} }
@ -62,9 +58,7 @@ def parse_tokens(name: str, tokens_iter: Iterable[Token]) -> Program:
return context.finalize() return context.finalize()
def composite_tokenizer( def composite_tokenizer(tokens_iter: Iterable[Token]) -> Iterable[Tuple[Token, Tuple[str]]]:
tokens_iter: Iterable[Token],
) -> Iterable[Tuple[Token, Tuple[str]]]:
""" """
Convert an iterator over tokens into an iterator over tuples: (token, list(token)) Convert an iterator over tokens into an iterator over tuples: (token, list(token))
@ -77,11 +71,7 @@ def composite_tokenizer(
while not tokens.is_empty(): while not tokens.is_empty():
token = next(tokens) token = next(tokens)
if token.type in ( if token.type in (TokenType.PSEUDO_OP, TokenType.LABEL, TokenType.INSTRUCTION_NAME):
TokenType.PSEUDO_OP,
TokenType.LABEL,
TokenType.INSTRUCTION_NAME,
):
yield token, tuple(take_arguments(tokens)) yield token, tuple(take_arguments(tokens))
@ -116,14 +106,10 @@ class AssemblyFileLoader(ProgramLoader):
The AssemblyFileLoader loads .asm, .S and .s files by default, and acts as a weak fallback to all other filetypes. The AssemblyFileLoader loads .asm, .S and .s files by default, and acts as a weak fallback to all other filetypes.
""" """
def parse(self) -> Program: def parse(self) -> Program:
with open(self.source_path, "r") as f: with open(self.source_path, 'r') as f:
return parse_tokens(self.filename, tokenize(f)) return parse_tokens(self.filename, tokenize(f))
def parse_io(self, io: Iterable[str]):
return parse_tokens(self.filename, tokenize(io))
@classmethod @classmethod
def can_parse(cls, source_path: str) -> float: def can_parse(cls, source_path: str) -> float:
""" """
@ -134,10 +120,10 @@ class AssemblyFileLoader(ProgramLoader):
:return: :return:
""" """
# gcc recognizes these line endings as assembly. So we will do too. # gcc recognizes these line endings as assembly. So we will do too.
if source_path.split(".")[-1] in ("asm", "S", "s"): if source_path.split('.')[-1] in ('asm', 'S', 's'):
return 1 return 1
return 0.01 return 0.01
@classmethod @classmethod
def get_options(cls, argv: List[str]) -> Tuple[List[str], T_ParserOpts]: def get_options(cls, argv: List[str]) -> [List[str], T_ParserOpts]:
return argv, {} return argv, {}

@ -12,7 +12,6 @@ class CSR:
""" """
This holds all Control and Status Registers (CSR) This holds all Control and Status Registers (CSR)
""" """
regs: Dict[int, UInt32] regs: Dict[int, UInt32]
""" """
All Control and Status Registers are stored here All Control and Status Registers are stored here
@ -53,9 +52,7 @@ class CSR:
return self.virtual_regs[addr]() return self.virtual_regs[addr]()
return self.regs[addr] return self.regs[addr]
def set_listener( def set_listener(self, addr: Union[str, int], listener: Callable[[UInt32, UInt32], None]):
self, addr: Union[str, int], listener: Callable[[UInt32, UInt32], None]
):
addr = self._name_to_addr(addr) addr = self._name_to_addr(addr)
if addr is None: if addr is None:
print("unknown csr address name: {}".format(addr)) print("unknown csr address name: {}".format(addr))
@ -76,11 +73,11 @@ class CSR:
""" """
size = 2 if name in MSTATUS_LEN_2 else 1 size = 2 if name in MSTATUS_LEN_2 else 1
off = MSTATUS_OFFSETS[name] off = MSTATUS_OFFSETS[name]
mask = (2**size - 1) << off mask = (2 ** size - 1) << off
old_val = self.get("mstatus") old_val = self.get('mstatus')
erased = old_val & (~mask) erased = old_val & (~mask)
new_val = erased | (val << off) new_val = erased | (val << off)
self.set("mstatus", new_val) self.set('mstatus', new_val)
def get_mstatus(self, name) -> UInt32: def get_mstatus(self, name) -> UInt32:
if not self.mstatus_cache_dirty and name in self.mstatus_cache: if not self.mstatus_cache_dirty and name in self.mstatus_cache:
@ -88,8 +85,8 @@ class CSR:
size = 2 if name in MSTATUS_LEN_2 else 1 size = 2 if name in MSTATUS_LEN_2 else 1
off = MSTATUS_OFFSETS[name] off = MSTATUS_OFFSETS[name]
mask = (2**size - 1) << off mask = (2 ** size - 1) << off
val = (self.get("mstatus") & mask) >> off val = (self.get('mstatus') & mask) >> off
if self.mstatus_cache_dirty: if self.mstatus_cache_dirty:
self.mstatus_cache = dict(name=val) self.mstatus_cache = dict(name=val)
else: else:

@ -1,81 +1,81 @@
from typing import Dict, Tuple from typing import Dict, Tuple
MCAUSE_TRANSLATION: Dict[Tuple[int, int], str] = { MCAUSE_TRANSLATION: Dict[Tuple[int, int], str]= {
(1, 0): "User software interrupt", (1, 0): 'User software interrupt',
(1, 1): "Supervisor software interrupt", (1, 1): 'Supervisor software interrupt',
(1, 3): "Machine software interrupt", (1, 3): 'Machine software interrupt',
(1, 4): "User timer interrupt", (1, 4): 'User timer interrupt',
(1, 5): "Supervisor timer interrupt", (1, 5): 'Supervisor timer interrupt',
(1, 7): "Machine timer interrupt", (1, 7): 'Machine timer interrupt',
(1, 8): "User external interrupt", (1, 8): 'User external interrupt',
(1, 9): "Supervisor external interrupt", (1, 9): 'Supervisor external interrupt',
(1, 11): "Machine external interrupt", (1, 11): 'Machine external interrupt',
(0, 0): "Instruction address misaligned", (0, 0): 'Instruction address misaligned',
(0, 1): "Instruction access fault", (0, 1): 'Instruction access fault',
(0, 2): "Illegal instruction", (0, 2): 'Illegal instruction',
(0, 3): "Breakpoint", (0, 3): 'Breakpoint',
(0, 4): "Load address misaligned", (0, 4): 'Load address misaligned',
(0, 5): "Load access fault", (0, 5): 'Load access fault',
(0, 6): "Store/AMO address misaligned", (0, 6): 'Store/AMO address misaligned',
(0, 7): "Store/AMO access fault", (0, 7): 'Store/AMO access fault',
(0, 8): "environment call from user mode", (0, 8): 'environment call from user mode',
(0, 9): "environment call from supervisor mode", (0, 9): 'environment call from supervisor mode',
(0, 11): "environment call from machine mode", (0, 11): 'environment call from machine mode',
(0, 12): "Instruction page fault", (0, 12): 'Instruction page fault',
(0, 13): "Load page fault", (0, 13): 'Load page fault',
(0, 15): "Store/AMO page fault", (0, 15): 'Store/AMO page fault',
} }
""" """
Assigns tuple (interrupt bit, exception code) to their respective readable names Assigns tuple (interrupt bit, exception code) to their respective readable names
""" """
MSTATUS_OFFSETS: Dict[str, int] = { MSTATUS_OFFSETS: Dict[str, int] = {
"uie": 0, 'uie': 0,
"sie": 1, 'sie': 1,
"mie": 3, 'mie': 3,
"upie": 4, 'upie': 4,
"spie": 5, 'spie': 5,
"mpie": 7, 'mpie': 7,
"spp": 8, 'spp': 8,
"mpp": 11, 'mpp': 11,
"fs": 13, 'fs': 13,
"xs": 15, 'xs': 15,
"mpriv": 17, 'mpriv': 17,
"sum": 18, 'sum': 18,
"mxr": 19, 'mxr': 19,
"tvm": 20, 'tvm': 20,
"tw": 21, 'tw': 21,
"tsr": 22, 'tsr': 22,
"sd": 31, 'sd': 31
} }
""" """
Offsets for all mstatus bits Offsets for all mstatus bits
""" """
MSTATUS_LEN_2 = ("mpp", "fs", "xs") MSTATUS_LEN_2 = ('mpp', 'fs', 'xs')
""" """
All mstatus parts that have length 2. All other mstatus parts have length 1 All mstatus parts that have length 2. All other mstatus parts have length 1
""" """
CSR_NAME_TO_ADDR: Dict[str, int] = { CSR_NAME_TO_ADDR: Dict[str, int] = {
"mstatus": 0x300, 'mstatus': 0x300,
"misa": 0x301, 'misa': 0x301,
"mie": 0x304, 'mie': 0x304,
"mtvec": 0x305, 'mtvec': 0x305,
"mepc": 0x341, 'mepc': 0x341,
"mcause": 0x342, 'mcause': 0x342,
"mtval": 0x343, 'mtval': 0x343,
"mip": 0x344, 'mip': 0x344,
"mvendorid": 0xF11, 'mvendorid': 0xF11,
"marchid": 0xF12, 'marchid': 0xF12,
"mimpid": 0xF13, 'mimpid': 0xF13,
"mhartid": 0xF14, 'mhartid': 0xF14,
"time": 0xC01, 'time': 0xc01,
"timeh": 0xC81, 'timeh': 0xc81,
"halt": 0x789, 'halt': 0x789,
"mtimecmp": 0x780, 'mtimecmp': 0x780,
"mtimecmph": 0x781, 'mtimecmph': 0x781,
} }
""" """
Translation for named registers Translation for named registers
""" """

@ -11,7 +11,7 @@ if typing.TYPE_CHECKING:
from elftools.elf.elffile import ELFFile from elftools.elf.elffile import ELFFile
from elftools.elf.sections import Section, SymbolTableSection from elftools.elf.sections import Section, SymbolTableSection
INCLUDE_SEC = (".text", ".stack", ".bss", ".sdata", ".sbss") INCLUDE_SEC = ('.text', '.stack', '.bss', '.sdata', '.sbss')
class ElfBinaryFileLoader(ProgramLoader): class ElfBinaryFileLoader(ProgramLoader):
@ -20,7 +20,6 @@ class ElfBinaryFileLoader(ProgramLoader):
This loader respects local and global symbols. This loader respects local and global symbols.
""" """
program: Program program: Program
def __init__(self, source_path: str, options: T_ParserOpts): def __init__(self, source_path: str, options: T_ParserOpts):
@ -29,8 +28,8 @@ class ElfBinaryFileLoader(ProgramLoader):
@classmethod @classmethod
def can_parse(cls, source_path: str) -> float: def can_parse(cls, source_path: str) -> float:
with open(source_path, "rb") as f: with open(source_path, 'rb') as f:
if f.read(4) == b"\x7f\x45\x4c\x46": if f.read(4) == b'\x7f\x45\x4c\x46':
return 1 return 1
return 0 return 0
@ -43,33 +42,23 @@ class ElfBinaryFileLoader(ProgramLoader):
from elftools.elf.elffile import ELFFile from elftools.elf.elffile import ELFFile
from elftools.elf.sections import Section, SymbolTableSection from elftools.elf.sections import Section, SymbolTableSection
with open(self.source_path, "rb") as f: with open(self.source_path, 'rb') as f:
print( print(FMT_ELF + "[ElfLoader] Loading elf executable from: {}".format(self.source_path) + FMT_NONE)
FMT_ELF
+ "[ElfLoader] Loading elf executable from: {}".format(
self.source_path
)
+ FMT_NONE
)
self._read_elf(ELFFile(f)) self._read_elf(ELFFile(f))
except ImportError as e: except ImportError as e:
print( print(FMT_PARSE + "[ElfLoader] Cannot load elf files without PyElfTools package! You can install them "
FMT_PARSE "using pip install pyelftools!" + FMT_NONE)
+ "[ElfLoader] Cannot load elf files without PyElfTools package! You can install them "
"using pip install pyelftools!" + FMT_NONE
)
raise e raise e
return self.program return self.program
def _read_elf(self, elf: "ELFFile"): def _read_elf(self, elf: 'ELFFile'):
if not elf.header.e_machine == "EM_RISCV": if not elf.header.e_machine == 'EM_RISCV':
raise InvalidElfException("Not a RISC-V elf file!") raise InvalidElfException("Not a RISC-V elf file!")
if not elf.header.e_ident.EI_CLASS == "ELFCLASS32": if not elf.header.e_ident.EI_CLASS == 'ELFCLASS32':
raise InvalidElfException("Only 32bit executables are supported!") raise InvalidElfException("Only 32bit executables are supported!")
from elftools.elf.sections import SymbolTableSection from elftools.elf.sections import SymbolTableSection
for sec in elf.iter_sections(): for sec in elf.iter_sections():
if isinstance(sec, SymbolTableSection): if isinstance(sec, SymbolTableSection):
self._parse_symtab(sec) self._parse_symtab(sec)
@ -80,22 +69,18 @@ class ElfBinaryFileLoader(ProgramLoader):
self._add_sec(self._lms_from_elf_sec(sec, self.filename)) self._add_sec(self._lms_from_elf_sec(sec, self.filename))
def _lms_from_elf_sec(self, sec: "Section", owner: str): def _lms_from_elf_sec(self, sec: 'Section', owner: str):
is_code = sec.name in (".text",) is_code = sec.name in ('.text',)
data = bytearray(sec.data()) data = bytearray(sec.data())
if len(data) < sec.data_size: if len(data) < sec.data_size:
data += bytearray(len(data) - sec.data_size) data += bytearray(len(data) - sec.data_size)
flags = MemoryFlags(is_code, is_code) flags = MemoryFlags(is_code, is_code)
print( print(FMT_ELF + "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr) + FMT_NONE)
FMT_ELF
+ "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr)
+ FMT_NONE
)
return ElfMemorySection( return ElfMemorySection(
data, sec.name, self.program.context, owner, sec.header.sh_addr, flags data, sec.name, self.program.context, owner, sec.header.sh_addr, flags
) )
def _parse_symtab(self, symtab: "SymbolTableSection"): def _parse_symtab(self, symtab: 'SymbolTableSection'):
from elftools.elf.enums import ENUM_ST_VISIBILITY from elftools.elf.enums import ENUM_ST_VISIBILITY
for sym in symtab.iter_symbols(): for sym in symtab.iter_symbols():
@ -103,26 +88,18 @@ class ElfBinaryFileLoader(ProgramLoader):
continue continue
self.program.context.labels[sym.name] = sym.entry.st_value self.program.context.labels[sym.name] = sym.entry.st_value
# check if it has st_visibility bit set # check if it has st_visibility bit set
if sym.entry.st_info.bind == "STB_GLOBAL": if sym.entry.st_info.bind == 'STB_GLOBAL':
self.program.global_labels.add(sym.name) self.program.global_labels.add(sym.name)
print( print(FMT_PARSE + "LOADED GLOBAL SYMBOL {}: {}".format(sym.name, sym.entry.st_value) + FMT_NONE)
FMT_PARSE
+ "LOADED GLOBAL SYMBOL {}: {}".format(sym.name, sym.entry.st_value)
+ FMT_NONE
)
def _add_sec(self, new_sec: "ElfMemorySection"): def _add_sec(self, new_sec: 'ElfMemorySection'):
for sec in self.program.sections: for sec in self.program.sections:
if sec.base < sec.end <= new_sec.base or sec.end > sec.base >= new_sec.end: if sec.base < sec.end <= new_sec.base or sec.end > sec.base >= new_sec.end:
continue continue
else: else:
print( print(FMT_ELF + "[ElfLoader] Invalid elf layout: Two sections overlap: \n\t{}\n\t{}".format(
FMT_ELF sec, new_sec
+ "[ElfLoader] Invalid elf layout: Two sections overlap: \n\t{}\n\t{}".format( ) + FMT_NONE)
sec, new_sec
)
+ FMT_NONE
)
raise RuntimeError("Cannot load elf with overlapping sections!") raise RuntimeError("Cannot load elf with overlapping sections!")
self.program.add_section(new_sec) self.program.add_section(new_sec)

@ -42,12 +42,10 @@ class CpuTrap(BaseException):
priv: PrivModes priv: PrivModes
""" """
The privilege level this trap targets The privilege level this trap targets
""" """
def __init__( def __init__(self, code: int, mtval, type: CpuTrapType, priv: PrivModes = PrivModes.MACHINE):
self, code: int, mtval, type: CpuTrapType, priv: PrivModes = PrivModes.MACHINE
):
self.interrupt = 0 if type == CpuTrapType.EXCEPTION else 1 self.interrupt = 0 if type == CpuTrapType.EXCEPTION else 1
self.code = code self.code = code
self.mtval = UInt32(mtval) self.mtval = UInt32(mtval)
@ -65,9 +63,7 @@ class CpuTrap(BaseException):
name = "Reserved interrupt({}, {})".format(self.interrupt, self.code) name = "Reserved interrupt({}, {})".format(self.interrupt, self.code)
if (self.interrupt, self.code) in MCAUSE_TRANSLATION: if (self.interrupt, self.code) in MCAUSE_TRANSLATION:
name = MCAUSE_TRANSLATION[(self.interrupt, self.code)] + "({}, {})".format( name = MCAUSE_TRANSLATION[(self.interrupt, self.code)] + "({}, {})".format(self.interrupt, self.code)
self.interrupt, self.code
)
return "{} {{priv={}, type={}, mtval={:x}}} {}".format( return "{} {{priv={}, type={}, mtval={:x}}} {}".format(
name, self.priv.name, self.type.name, self.mtval, self.message() name, self.priv.name, self.type.name, self.mtval, self.message()
@ -78,7 +74,7 @@ class CpuTrap(BaseException):
class IllegalInstructionTrap(CpuTrap): class IllegalInstructionTrap(CpuTrap):
def __init__(self, ins: "ElfInstruction"): def __init__(self, ins: 'ElfInstruction'):
super().__init__(2, ins.encoded, CpuTrapType.EXCEPTION) super().__init__(2, ins.encoded, CpuTrapType.EXCEPTION)
@ -108,9 +104,7 @@ class InvalidElfException(RiscemuBaseException):
self.msg = msg self.msg = msg
def message(self): def message(self):
return ( return FMT_PARSE + "{}(\"{}\")".format(self.__class__.__name__, self.msg) + FMT_NONE
FMT_PARSE + '{}("{}")'.format(self.__class__.__name__, self.msg) + FMT_NONE
)
class LoadAccessFault(CpuTrap): class LoadAccessFault(CpuTrap):
@ -123,5 +117,8 @@ class LoadAccessFault(CpuTrap):
def message(self): def message(self):
return "(During {} at 0x{:08x} of size {}: {})".format( return "(During {} at 0x{:08x} of size {}: {})".format(
self.op, self.addr, self.size, self.msg self.op,
self.addr,
self.size,
self.msg
) )

@ -14,9 +14,10 @@ from ..types import MemoryFlags, ProgramLoader, Program, T_ParserOpts
class MemoryImageLoader(ProgramLoader): class MemoryImageLoader(ProgramLoader):
@classmethod @classmethod
def can_parse(cls, source_path: str) -> float: def can_parse(cls, source_path: str) -> float:
if source_path.split(".")[-1] == "img": if source_path.split('.')[-1] == 'img':
return 1 return 1
return 0 return 0
@ -25,14 +26,14 @@ class MemoryImageLoader(ProgramLoader):
return argv, {} return argv, {}
def parse(self) -> Iterable[Program]: def parse(self) -> Iterable[Program]:
if "debug" not in self.options: if 'debug' not in self.options:
yield self.parse_no_debug() yield self.parse_no_debug()
return return
with open(self.options.get("debug"), "r") as debug_file: with open(self.options.get('debug'), 'r') as debug_file:
debug_info = MemoryImageDebugInfos.load(debug_file.read()) debug_info = MemoryImageDebugInfos.load(debug_file.read())
with open(self.source_path, "rb") as source_file: with open(self.source_path, 'rb') as source_file:
data: bytearray = bytearray(source_file.read()) data: bytearray = bytearray(source_file.read())
for name, sections in debug_info.sections.items(): for name, sections in debug_info.sections.items():
@ -42,15 +43,11 @@ class MemoryImageLoader(ProgramLoader):
if program.base is None: if program.base is None:
program.base = start program.base = start
# in_code_sec = get_section_base_name(sec_name) in INSTRUCTION_SECTION_NAMES #in_code_sec = get_section_base_name(sec_name) in INSTRUCTION_SECTION_NAMES
program.add_section( program.add_section(
ElfMemorySection( ElfMemorySection(
data[start : start + size], data[start:start+size], sec_name, program.context,
sec_name, name, start, MemoryFlags(False, True)
program.context,
name,
start,
MemoryFlags(False, True),
) )
) )
@ -60,27 +57,19 @@ class MemoryImageLoader(ProgramLoader):
yield program yield program
def parse_no_debug(self) -> Program: def parse_no_debug(self) -> Program:
print( print(FMT_PARSE + "[MemoryImageLoader] Warning: loading memory image without debug information!" + FMT_NONE)
FMT_PARSE
+ "[MemoryImageLoader] Warning: loading memory image without debug information!"
+ FMT_NONE
)
with open(self.source_path, "rb") as source_file: with open(self.source_path, 'rb') as source_file:
data: bytes = source_file.read() data: bytes = source_file.read()
p = Program(self.filename) p = Program(self.filename)
p.add_section( p.add_section(ElfMemorySection(
ElfMemorySection( bytearray(data), '.text', p.context, p.name, 0, MemoryFlags(False, True)
bytearray(data), ".text", p.context, p.name, 0, MemoryFlags(False, True) ))
)
)
return p return p
@classmethod @classmethod
def instantiate(cls, source_path: str, options: T_ParserOpts) -> "ProgramLoader": def instantiate(cls, source_path: str, options: T_ParserOpts) -> 'ProgramLoader':
if os.path.isfile(source_path + ".dbg"): if os.path.isfile(source_path + '.dbg'):
return MemoryImageLoader( return MemoryImageLoader(source_path, dict(**options, debug=source_path + '.dbg'))
source_path, dict(**options, debug=source_path + ".dbg")
)
return MemoryImageLoader(source_path, options) return MemoryImageLoader(source_path, options)

@ -87,11 +87,7 @@ class PrivCPU(CPU):
if self.halted: if self.halted:
print() print()
print( print(FMT_CPU + "[CPU] System halted with code {}".format(self.exit_code) + FMT_NONE)
FMT_CPU
+ "[CPU] System halted with code {}".format(self.exit_code)
+ FMT_NONE
)
sys.exit(self.exit_code) sys.exit(self.exit_code)
elif launch_debug: elif launch_debug:
@ -100,68 +96,55 @@ class PrivCPU(CPU):
self.run(verbose) self.run(verbose)
else: else:
print() print()
print( print(FMT_CPU + "[CPU] System stopped without halting - perhaps you stopped the debugger?" + FMT_NONE)
FMT_CPU
+ "[CPU] System stopped without halting - perhaps you stopped the debugger?"
+ FMT_NONE
)
def launch(self, program: Optional[Program] = None, verbose: bool = False): def launch(self, program: Optional[Program] = None, verbose: bool = False):
print( print(FMT_CPU + '[CPU] Started running from 0x{:08X} ({})'.format(self.pc, "kernel") + FMT_NONE)
FMT_CPU
+ "[CPU] Started running from 0x{:08X} ({})".format(self.pc, "kernel")
+ FMT_NONE
)
self._time_start = time.perf_counter_ns() // self.TIME_RESOLUTION_NS self._time_start = time.perf_counter_ns() // self.TIME_RESOLUTION_NS
self.run(self.conf.verbosity > 1 or verbose) self.run(self.conf.verbosity > 1 or verbose)
def load_program(self, program: Program): def load_program(self, program: Program):
if program.name == "kernel": if program.name == 'kernel':
self.pc = program.entrypoint self.pc = program.entrypoint
super().load_program(program) super().load_program(program)
def _init_csr(self): def _init_csr(self):
# set up CSR # set up CSR
self.csr = CSR() self.csr = CSR()
self.csr.set("mhartid", UInt32(0)) # core id self.csr.set('mhartid', UInt32(0)) # core id
# TODO: set correct value # TODO: set correct value
self.csr.set("mimpid", UInt32(0)) # implementation id self.csr.set('mimpid', UInt32(0)) # implementation id
# set mxl to 1 (32 bit) and set bits for i and m isa # set mxl to 1 (32 bit) and set bits for i and m isa
self.csr.set("misa", UInt32((1 << 30) + (1 << 8) + (1 << 12))) # available ISA self.csr.set('misa', UInt32((1 << 30) + (1 << 8) + (1 << 12))) # available ISA
# CSR write callbacks: # CSR write callbacks:
@self.csr.callback("halt") @self.csr.callback('halt')
def halt(old: UInt32, new: UInt32): def halt(old: UInt32, new: UInt32):
if new != 0: if new != 0:
self.halted = True self.halted = True
self.exit_code = new.value self.exit_code = new.value
@self.csr.callback("mtimecmp") @self.csr.callback('mtimecmp')
def mtimecmp(old: UInt32, new: UInt32): def mtimecmp(old: UInt32, new: UInt32):
self._time_timecmp = (self.csr.get("mtimecmph") << 32) + new self._time_timecmp = (self.csr.get('mtimecmph') << 32) + new
self._time_interrupt_enabled = True self._time_interrupt_enabled = True
@self.csr.callback("mtimecmph") @self.csr.callback('mtimecmph')
def mtimecmph(old: UInt32, new: UInt32): def mtimecmph(old: UInt32, new: UInt32):
self._time_timecmp = (new << 32) + self.csr.get("mtimecmp") self._time_timecmp = (new << 32) + self.csr.get('mtimecmp')
self._time_interrupt_enabled = True self._time_interrupt_enabled = True
# virtual CSR registers: # virtual CSR registers:
@self.csr.virtual_register("time") @self.csr.virtual_register('time')
def get_time(): def get_time():
return UInt32( return UInt32(time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start)
time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start
)
@self.csr.virtual_register("timeh") @self.csr.virtual_register('timeh')
def get_timeh(): def get_timeh():
return UInt32( return UInt32((time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) >> 32)
(time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start)
>> 32
)
# add minstret and mcycle counters # add minstret and mcycle counters
@ -177,21 +160,17 @@ class PrivCPU(CPU):
self._check_interrupt() self._check_interrupt()
ins = self.mmu.read_ins(self.pc) ins = self.mmu.read_ins(self.pc)
if verbose and (self.mode == PrivModes.USER or self.conf.verbosity > 4): if verbose and (self.mode == PrivModes.USER or self.conf.verbosity > 4):
print( print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)
)
self.run_instruction(ins) self.run_instruction(ins)
self.pc += self.INS_XLEN self.pc += self.INS_XLEN
except CpuTrap as trap: except CpuTrap as trap:
self._handle_trap(trap) self._handle_trap(trap)
if trap.interrupt == 0 and not isinstance(trap, EcallTrap): if trap.interrupt == 0 and not isinstance(trap, EcallTrap):
print( print(FMT_CPU + "[CPU] Trap {} encountered at {} (0x{:x})".format(
FMT_CPU trap,
+ "[CPU] Trap {} encountered at {} (0x{:x})".format( self.mmu.translate_address(self.pc),
trap, self.mmu.translate_address(self.pc), self.pc self.pc
) ) + FMT_NONE)
+ FMT_NONE
)
breakpoint() breakpoint()
if self.conf.debug_on_exception: if self.conf.debug_on_exception:
raise LaunchDebuggerException() raise LaunchDebuggerException()
@ -200,15 +179,12 @@ class PrivCPU(CPU):
def _timer_step(self): def _timer_step(self):
if not self._time_interrupt_enabled: if not self._time_interrupt_enabled:
return return
if ( if self._time_timecmp <= (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start:
self._time_timecmp
<= (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start
):
self.pending_traps.append(TimerInterrupt()) self.pending_traps.append(TimerInterrupt())
self._time_interrupt_enabled = False self._time_interrupt_enabled = False
def _check_interrupt(self): def _check_interrupt(self):
if not (len(self.pending_traps) > 0 and self.csr.get_mstatus("mie")): if not (len(self.pending_traps) > 0 and self.csr.get_mstatus('mie')):
return return
# select best interrupt # select best interrupt
# FIXME: actually select based on the official ranking # FIXME: actually select based on the official ranking
@ -218,30 +194,24 @@ class PrivCPU(CPU):
self.regs.dump_reg_a() self.regs.dump_reg_a()
if trap.priv != PrivModes.MACHINE: if trap.priv != PrivModes.MACHINE:
print( print(FMT_CPU + "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!" + FMT_NONE)
FMT_CPU
+ "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!"
+ FMT_NONE
)
raise Exception("Undefined behaviour!") raise Exception("Undefined behaviour!")
if self.mode != PrivModes.USER: if self.mode != PrivModes.USER:
print(FMT_CPU + "[CPU] Trap triggered outside of user mode?!" + FMT_NONE) print(FMT_CPU + "[CPU] Trap triggered outside of user mode?!" + FMT_NONE)
self.csr.set_mstatus("mpie", self.csr.get_mstatus("mie")) self.csr.set_mstatus('mpie', self.csr.get_mstatus('mie'))
self.csr.set_mstatus("mpp", self.mode.value) self.csr.set_mstatus('mpp', self.mode.value)
self.csr.set_mstatus("mie", UInt32(0)) self.csr.set_mstatus('mie', UInt32(0))
self.csr.set("mcause", trap.mcause) self.csr.set('mcause', trap.mcause)
self.csr.set("mepc", self.pc - self.INS_XLEN) self.csr.set('mepc', self.pc - self.INS_XLEN)
self.csr.set("mtval", trap.mtval) self.csr.set('mtval', trap.mtval)
self.mode = trap.priv self.mode = trap.priv
mtvec = self.csr.get("mtvec") mtvec = self.csr.get('mtvec')
if mtvec & 0b11 == 0: if mtvec & 0b11 == 0:
self.pc = mtvec.value self.pc = mtvec.value
if mtvec & 0b11 == 1: if mtvec & 0b11 == 1:
self.pc = ( self.pc = ((mtvec & 0b11111111111111111111111111111100) + (trap.code * 4)).value
(mtvec & 0b11111111111111111111111111111100) + (trap.code * 4)
).value
self.record_perf_profile() self.record_perf_profile()
if len(self._perf_counters) > 100: if len(self._perf_counters) > 100:
self.show_perf() self.show_perf()
@ -262,10 +232,7 @@ class PrivCPU(CPU):
cycled = cycle cycled = cycle
timed = time_ns timed = time_ns
cps_list.append(cps) cps_list.append(cps)
print( print(" on average {:.0f} instructions/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE)
" on average {:.0f} instructions/s".format(sum(cps_list) / len(cps_list))
+ FMT_NONE
)
self._perf_counters = list() self._perf_counters = list()
def record_perf_profile(self): def record_perf_profile(self):
@ -273,4 +240,6 @@ class PrivCPU(CPU):
@classmethod @classmethod
def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]: def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]:
return [AssemblyFileLoader, MemoryImageLoader, ElfBinaryFileLoader] return [
AssemblyFileLoader, MemoryImageLoader, ElfBinaryFileLoader
]

@ -9,6 +9,7 @@ if typing.TYPE_CHECKING:
class PrivMMU(MMU): class PrivMMU(MMU):
def get_sec_containing(self, addr: T_AbsoluteAddress) -> MemorySection: def get_sec_containing(self, addr: T_AbsoluteAddress) -> MemorySection:
# try to get an existing section # try to get an existing section
existing_sec = super().get_sec_containing(addr) existing_sec = super().get_sec_containing(addr)
@ -17,9 +18,7 @@ class PrivMMU(MMU):
return existing_sec return existing_sec
# get section preceding empty space at addr # get section preceding empty space at addr
sec_before = next( sec_before = next((sec for sec in reversed(self.sections) if sec.end < addr), None)
(sec for sec in reversed(self.sections) if sec.end < addr), None
)
# get sec succeeding empty space at addr # get sec succeeding empty space at addr
sec_after = next((sec for sec in self.sections if sec.base > addr), None) sec_after = next((sec for sec in self.sections if sec.base > addr), None)
@ -32,14 +31,7 @@ class PrivMMU(MMU):
# end at the start of the next section, or address + 0xFFFF (aligned to 16 byte boundary) # end at the start of the next section, or address + 0xFFFF (aligned to 16 byte boundary)
end = min(next_sec_start, align_addr(addr + 0xFFFF, 16)) end = min(next_sec_start, align_addr(addr + 0xFFFF, 16))
sec = ElfMemorySection( sec = ElfMemorySection(bytearray(end - start), '.empty', self.global_instruction_context(), '', start, MemoryFlags(False, True))
bytearray(end - start),
".empty",
self.global_instruction_context(),
"",
start,
MemoryFlags(False, True),
)
self.sections.append(sec) self.sections.append(sec)
self._update_state() self._update_state()
@ -48,4 +40,4 @@ class PrivMMU(MMU):
def global_instruction_context(self) -> InstructionContext: def global_instruction_context(self) -> InstructionContext:
context = InstructionContext() context = InstructionContext()
context.global_symbol_dict = self.global_symbols context.global_symbol_dict = self.global_symbols
return context return context

@ -16,159 +16,155 @@ if typing.TYPE_CHECKING:
class PrivRV32I(RV32I): class PrivRV32I(RV32I):
cpu: "PrivCPU" cpu: 'PrivCPU'
""" """
This is an extension of RV32I, written for the PrivCPU class This is an extension of RV32I, written for the PrivCPU class
""" """
def instruction_csrrw(self, ins: "Instruction"): def instruction_csrrw(self, ins: 'Instruction'):
rd, rs, csr_addr = self.parse_crs_ins(ins) rd, rs, csr_addr = self.parse_crs_ins(ins)
old_val = None old_val = None
if rd != "zero": if rd != 'zero':
self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr) self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr)
old_val = self.cpu.csr.get(csr_addr) old_val = self.cpu.csr.get(csr_addr)
if rs != "zero": if rs != 'zero':
new_val = self.regs.get(rs) new_val = self.regs.get(rs)
self.cpu.csr.assert_can_write(self.cpu.mode, csr_addr) self.cpu.csr.assert_can_write(self.cpu.mode, csr_addr)
self.cpu.csr.set(csr_addr, new_val) self.cpu.csr.set(csr_addr, new_val)
if old_val is not None: if old_val is not None:
self.regs.set(rd, old_val) self.regs.set(rd, old_val)
def instruction_csrrs(self, ins: "Instruction"): def instruction_csrrs(self, ins: 'Instruction'):
rd, rs, csr_addr = self.parse_crs_ins(ins) rd, rs, csr_addr = self.parse_crs_ins(ins)
if rs != "zero": if rs != 'zero':
# oh no, this should not happen! # oh no, this should not happen!
INS_NOT_IMPLEMENTED(ins) INS_NOT_IMPLEMENTED(ins)
if rd != "zero": if rd != 'zero':
self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr) self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr)
old_val = self.cpu.csr.get(csr_addr) old_val = self.cpu.csr.get(csr_addr)
self.regs.set(rd, old_val) self.regs.set(rd, old_val)
def instruction_csrrc(self, ins: "Instruction"): def instruction_csrrc(self, ins: 'Instruction'):
INS_NOT_IMPLEMENTED(ins) INS_NOT_IMPLEMENTED(ins)
def instruction_csrrsi(self, ins: "Instruction"): def instruction_csrrsi(self, ins: 'Instruction'):
INS_NOT_IMPLEMENTED(ins) INS_NOT_IMPLEMENTED(ins)
def instruction_csrrwi(self, ins: "Instruction"): def instruction_csrrwi(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
rd, imm, addr = ins.get_reg(0), ins.get_imm(1), ins.get_imm(2) rd, imm, addr = ins.get_reg(0), ins.get_imm(1), ins.get_imm(2)
if rd != "zero": if rd != 'zero':
self.cpu.csr.assert_can_read(self.cpu.mode, addr) self.cpu.csr.assert_can_read(self.cpu.mode, addr)
old_val = self.cpu.csr.get(addr) old_val = self.cpu.csr.get(addr)
self.regs.set(rd, old_val) self.regs.set(rd, old_val)
self.cpu.csr.assert_can_write(self.cpu.mode, addr) self.cpu.csr.assert_can_write(self.cpu.mode, addr)
self.cpu.csr.set(addr, imm) self.cpu.csr.set(addr, imm)
def instruction_csrrci(self, ins: "Instruction"): def instruction_csrrci(self, ins: 'Instruction'):
INS_NOT_IMPLEMENTED(ins) INS_NOT_IMPLEMENTED(ins)
def instruction_mret(self, ins: "Instruction"): def instruction_mret(self, ins: 'Instruction'):
if self.cpu.mode != PrivModes.MACHINE: if self.cpu.mode != PrivModes.MACHINE:
print("MRET not inside machine level code!") print("MRET not inside machine level code!")
raise IllegalInstructionTrap(ins) raise IllegalInstructionTrap(ins)
# retore mie # retore mie
mpie = self.cpu.csr.get_mstatus("mpie") mpie = self.cpu.csr.get_mstatus('mpie')
self.cpu.csr.set_mstatus("mie", mpie) self.cpu.csr.set_mstatus('mie', mpie)
# restore priv # restore priv
mpp = self.cpu.csr.get_mstatus("mpp") mpp = self.cpu.csr.get_mstatus('mpp')
self.cpu.mode = PrivModes(mpp) self.cpu.mode = PrivModes(mpp)
# restore pc # restore pc
mepc = self.cpu.csr.get("mepc") mepc = self.cpu.csr.get('mepc')
self.cpu.pc = (mepc - self.cpu.INS_XLEN).value self.cpu.pc = (mepc - self.cpu.INS_XLEN).value
if self.cpu.conf.verbosity > 0: if self.cpu.conf.verbosity > 0:
sec = self.mmu.get_sec_containing(mepc.value) sec = self.mmu.get_sec_containing(mepc.value)
if sec is not None: if sec is not None:
print( print(FMT_CPU + "[CPU] returning to mode {} in {} (0x{:x})".format(
FMT_CPU PrivModes(mpp).name,
+ "[CPU] returning to mode {} in {} (0x{:x})".format( self.mmu.translate_address(mepc),
PrivModes(mpp).name, self.mmu.translate_address(mepc), mepc mepc
) ) + FMT_NONE)
+ FMT_NONE
)
if self.cpu.conf.verbosity > 1: if self.cpu.conf.verbosity > 1:
self.regs.dump_reg_a() self.regs.dump_reg_a()
def instruction_uret(self, ins: "Instruction"): def instruction_uret(self, ins: 'Instruction'):
raise IllegalInstructionTrap(ins) raise IllegalInstructionTrap(ins)
def instruction_sret(self, ins: "Instruction"): def instruction_sret(self, ins: 'Instruction'):
raise IllegalInstructionTrap(ins) raise IllegalInstructionTrap(ins)
def instruction_scall(self, ins: "Instruction"): def instruction_scall(self, ins: 'Instruction'):
""" """
Overwrite the scall from userspace RV32I Overwrite the scall from userspace RV32I
""" """
raise EcallTrap(self.cpu.mode) raise EcallTrap(self.cpu.mode)
def instruction_beq(self, ins: "Instruction"): def instruction_beq(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins) rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 == rs2: if rs1 == rs2:
self.pc += dst.value - 4 self.pc += dst.value - 4
def instruction_bne(self, ins: "Instruction"): def instruction_bne(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins) rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 != rs2: if rs1 != rs2:
self.pc += dst.value - 4 self.pc += dst.value - 4
def instruction_blt(self, ins: "Instruction"): def instruction_blt(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins) rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 < rs2: if rs1 < rs2:
self.pc += dst.value - 4 self.pc += dst.value - 4
def instruction_bge(self, ins: "Instruction"): def instruction_bge(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins) rs1, rs2, dst = self.parse_rs_rs_imm(ins)
if rs1 >= rs2: if rs1 >= rs2:
self.pc += dst.value - 4 self.pc += dst.value - 4
def instruction_bltu(self, ins: "Instruction"): def instruction_bltu(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False)
if rs1 < rs2: if rs1 < rs2:
self.pc += dst.value - 4 self.pc += dst.value - 4
def instruction_bgeu(self, ins: "Instruction"): def instruction_bgeu(self, ins: 'Instruction'):
rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False) rs1, rs2, dst = self.parse_rs_rs_imm(ins, signed=False)
if rs1 >= rs2: if rs1 >= rs2:
self.pc += dst.value - 4 self.pc += dst.value - 4
# technically deprecated # technically deprecated
def instruction_j(self, ins: "Instruction"): def instruction_j(self, ins: 'Instruction'):
raise NotImplementedError("Should never be reached!") raise NotImplementedError("Should never be reached!")
def instruction_jal(self, ins: "Instruction"): def instruction_jal(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 2) ASSERT_LEN(ins.args, 2)
reg = ins.get_reg(0) reg = ins.get_reg(0)
addr = ins.get_imm(1) addr = ins.get_imm(1)
if reg == "ra" and ( if reg == 'ra' and (
(self.cpu.mode == PrivModes.USER and self.cpu.conf.verbosity > 1) (self.cpu.mode == PrivModes.USER and self.cpu.conf.verbosity > 1) or
or (self.cpu.conf.verbosity > 3) (self.cpu.conf.verbosity > 3)
): ):
print( print(FMT_CPU + 'Jumping from 0x{:x} to {} (0x{:x})'.format(
FMT_CPU self.pc,
+ "Jumping from 0x{:x} to {} (0x{:x})".format( self.mmu.translate_address(self.pc + addr),
self.pc, self.mmu.translate_address(self.pc + addr), self.pc + addr self.pc + addr
) ) + FMT_NONE)
+ FMT_NONE
)
self.regs.dump_reg_a() self.regs.dump_reg_a()
self.regs.set(reg, Int32(self.pc)) self.regs.set(reg, Int32(self.pc))
self.pc += addr - 4 self.pc += addr - 4
def instruction_jalr(self, ins: "Instruction"): def instruction_jalr(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
rd, rs, imm = self.parse_rd_rs_imm(ins) rd, rs, imm = self.parse_rd_rs_imm(ins)
self.regs.set(rd, Int32(self.pc)) self.regs.set(rd, Int32(self.pc))
self.pc = rs.value + imm.value - 4 self.pc = rs.value + imm.value - 4
def instruction_sbreak(self, ins: "Instruction"): def instruction_sbreak(self, ins: 'Instruction'):
raise LaunchDebuggerException() raise LaunchDebuggerException()
def parse_crs_ins(self, ins: "Instruction"): def parse_crs_ins(self, ins: 'Instruction'):
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
return ins.get_reg(0), ins.get_reg(1), ins.get_imm(2) return ins.get_reg(0), ins.get_reg(1), ins.get_imm(2)
def parse_mem_ins(self, ins: "Instruction") -> Tuple[str, int]: def parse_mem_ins(self, ins: 'Instruction') -> Tuple[str, int]:
ASSERT_LEN(ins.args, 3) ASSERT_LEN(ins.args, 3)
addr = self.get_reg_content(ins, 1) + ins.get_imm(2) addr = self.get_reg_content(ins, 1) + ins.get_imm(2)
reg = ins.get_reg(0) reg = ins.get_reg(0)

@ -12,4 +12,4 @@ Syscalls will have to be intercepted by your assembly code.
The PrivCPU Implements the Risc-V M/U Model, meaning there is machine mode and user mode. No PMP or paging is available. The PrivCPU Implements the Risc-V M/U Model, meaning there is machine mode and user mode. No PMP or paging is available.
""" """

@ -4,55 +4,27 @@ from .PrivCPU import PrivCPU
import sys import sys
if __name__ == "__main__": if __name__ == '__main__':
import argparse import argparse
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(description='RISC-V privileged architecture emulator', prog='riscemu')
description="RISC-V privileged architecture emulator", prog="riscemu"
)
parser.add_argument( parser.add_argument('source', type=str,
"source", help='Compiled RISC-V ELF file or memory image containing compiled RISC-V ELF files', nargs='+')
type=str, parser.add_argument('--debug-exceptions', help='Launch the interactive debugger when an exception is generated',
help="Compiled RISC-V ELF file or memory image containing compiled RISC-V ELF files", action='store_true')
nargs="+",
)
parser.add_argument(
"--debug-exceptions",
help="Launch the interactive debugger when an exception is generated",
action="store_true",
)
parser.add_argument( parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count',
"-v", default=0)
"--verbose",
help="Verbosity level (can be used multiple times)",
action="count",
default=0,
)
parser.add_argument( parser.add_argument('--slowdown', help="Slow down the emulated CPU clock by a factor", type=float, default=1)
"--slowdown",
help="Slow down the emulated CPU clock by a factor",
type=float,
default=1,
)
args = parser.parse_args() args = parser.parse_args()
cpu = PrivCPU( cpu = PrivCPU(RunConfig(verbosity=args.verbose, debug_on_exception=args.debug_exceptions, slowdown=args.slowdown))
RunConfig(
verbosity=args.verbose,
debug_on_exception=args.debug_exceptions,
slowdown=args.slowdown,
)
)
for source_path in args.source: for source_path in args.source:
loader = max( loader = max((loader for loader in cpu.get_loaders()), key=lambda l: l.can_parse(source_path))
(loader for loader in cpu.get_loaders()),
key=lambda l: l.can_parse(source_path),
)
argv, opts = loader.get_options(sys.argv) argv, opts = loader.get_options(sys.argv)
program = loader.instantiate(source_path, opts).parse() program = loader.instantiate(source_path, opts).parse()
if isinstance(program, Program): if isinstance(program, Program):

@ -6,19 +6,9 @@ from typing import Tuple, Dict, Set
from riscemu.colors import FMT_NONE, FMT_PARSE from riscemu.colors import FMT_NONE, FMT_PARSE
from riscemu.decoder import format_ins, RISCV_REGS, decode from riscemu.decoder import format_ins, RISCV_REGS, decode
from riscemu.priv.Exceptions import ( from riscemu.priv.Exceptions import InstructionAccessFault, InstructionAddressMisalignedTrap, LoadAccessFault
InstructionAccessFault, from riscemu.types import Instruction, InstructionContext, T_RelativeAddress, MemoryFlags, T_AbsoluteAddress, \
InstructionAddressMisalignedTrap, BinaryDataMemorySection
LoadAccessFault,
)
from riscemu.types import (
Instruction,
InstructionContext,
T_RelativeAddress,
MemoryFlags,
T_AbsoluteAddress,
BinaryDataMemorySection,
)
@dataclass(frozen=True) @dataclass(frozen=True)
@ -37,45 +27,34 @@ class ElfInstruction(Instruction):
return RISCV_REGS[self.args[num]] return RISCV_REGS[self.args[num]]
def __repr__(self) -> str: def __repr__(self) -> str:
if self.name == "jal" and self.args[0] == 0: if self.name == 'jal' and self.args[0] == 0:
return "j {}".format(self.args[1]) return "j {}".format(self.args[1])
if self.name == "addi" and self.args[2] == 0: if self.name == 'addi' and self.args[2] == 0:
return "mv {}, {}".format(self.get_reg(0), self.get_reg(1)) return "mv {}, {}".format(self.get_reg(0), self.get_reg(1))
if self.name == "addi" and self.args[1] == 0: if self.name == 'addi' and self.args[1] == 0:
return "li {}, {}".format(self.get_reg(0), self.args[2]) return "li {}, {}".format(self.get_reg(0), self.args[2])
if self.name == "ret" and len(self.args) == 0: if self.name == 'ret' and len(self.args) == 0:
return "ret" return "ret"
return format_ins(self.encoded, self.name) return format_ins(self.encoded, self.name)
class ElfMemorySection(BinaryDataMemorySection): class ElfMemorySection(BinaryDataMemorySection):
def __init__( def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int,
self, flags: MemoryFlags):
data: bytearray,
name: str,
context: InstructionContext,
owner: str,
base: int,
flags: MemoryFlags,
):
super().__init__(data, name, context, owner, base=base, flags=flags) super().__init__(data, name, context, owner, base=base, flags=flags)
self.read_ins = lru_cache(maxsize=self.size // 4)(self.read_ins) self.read_ins = lru_cache(maxsize=self.size // 4)(self.read_ins)
def read_ins(self, offset): def read_ins(self, offset):
if not self.flags.executable: if not self.flags.executable:
print( print(FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE)
FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE
)
raise InstructionAccessFault(offset + self.base) raise InstructionAccessFault(offset + self.base)
if offset % 4 != 0: if offset % 4 != 0:
raise InstructionAddressMisalignedTrap(offset + self.base) raise InstructionAddressMisalignedTrap(offset + self.base)
return ElfInstruction(*decode(self.data[offset : offset + 4])) return ElfInstruction(*decode(self.data[offset:offset + 4]))
def write(self, offset: T_RelativeAddress, size: int, data: bytearray): def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
if self.flags.read_only: if self.flags.read_only:
raise LoadAccessFault( raise LoadAccessFault('read-only section', offset + self.base, size, 'write')
"read-only section", offset + self.base, size, "write"
)
self.read_ins.cache_clear() self.read_ins.cache_clear()
return super(ElfMemorySection, self).write(offset, size, data) return super(ElfMemorySection, self).write(offset, size, data)
@ -85,7 +64,7 @@ class ElfMemorySection(BinaryDataMemorySection):
class MemoryImageDebugInfos: class MemoryImageDebugInfos:
VERSION = "1.0.0" VERSION = '1.0.0'
""" """
Schema version Schema version
""" """
@ -110,13 +89,12 @@ class MemoryImageDebugInfos:
This dictionary contains the list of all global symbols of a given program This dictionary contains the list of all global symbols of a given program
""" """
def __init__( def __init__(self,
self, sections: Dict[str, Dict[str, Tuple[int, int]]],
sections: Dict[str, Dict[str, Tuple[int, int]]], symbols: Dict[str, Dict[str, int]],
symbols: Dict[str, Dict[str, int]], globals: Dict[str, Set[str]],
globals: Dict[str, Set[str]], base: int = 0
base: int = 0, ):
):
self.sections = sections self.sections = sections
self.symbols = symbols self.symbols = symbols
self.globals = globals self.globals = globals
@ -130,9 +108,7 @@ class MemoryImageDebugInfos:
return json.dumps(dict(obj), default=serialize) return json.dumps(dict(obj), default=serialize)
if isinstance(obj, (set, tuple)): if isinstance(obj, (set, tuple)):
return json.dumps(list(obj), default=serialize) return json.dumps(list(obj), default=serialize)
return "<<unserializable {}>>".format( return "<<unserializable {}>>".format(getattr(obj, '__qualname__', '{unknown}'))
getattr(obj, "__qualname__", "{unknown}")
)
return json.dumps( return json.dumps(
dict( dict(
@ -140,25 +116,22 @@ class MemoryImageDebugInfos:
symbols=self.symbols, symbols=self.symbols,
globals=self.globals, globals=self.globals,
base=self.base, base=self.base,
VERSION=self.VERSION, VERSION=self.VERSION
), ),
default=serialize, default=serialize
) )
@classmethod @classmethod
def load(cls, serialized_str: str) -> "MemoryImageDebugInfos": def load(cls, serialized_str: str) -> 'MemoryImageDebugInfos':
json_obj: dict = json.loads(serialized_str) json_obj: dict = json.loads(serialized_str)
if "VERSION" not in json_obj: if 'VERSION' not in json_obj:
raise RuntimeError("Unknown MemoryImageDebugInfo version!") raise RuntimeError("Unknown MemoryImageDebugInfo version!")
version: str = json_obj.pop("VERSION") version: str = json_obj.pop('VERSION')
# compare major version # compare major version
if ( if version != cls.VERSION and version.split('.')[0] != cls.VERSION.split('.')[0]:
version != cls.VERSION
and version.split(".")[0] != cls.VERSION.split(".")[0]
):
raise RuntimeError( raise RuntimeError(
"Unknown MemoryImageDebugInfo version! This emulator expects version {}, debug info version {}".format( "Unknown MemoryImageDebugInfo version! This emulator expects version {}, debug info version {}".format(
cls.VERSION, version cls.VERSION, version
@ -168,7 +141,7 @@ class MemoryImageDebugInfos:
return MemoryImageDebugInfos(**json_obj) return MemoryImageDebugInfos(**json_obj)
@classmethod @classmethod
def builder(cls) -> "MemoryImageDebugInfos": def builder(cls) -> 'MemoryImageDebugInfos':
return MemoryImageDebugInfos( return MemoryImageDebugInfos(
defaultdict(dict), defaultdict(dict), defaultdict(set) defaultdict(dict), defaultdict(dict), defaultdict(set)
) )

@ -1,15 +1,15 @@
""" """
RiscEmu (c) 2023 Anton Lydike RiscEmu (c) 2021-2022 Anton Lydike
SPDX-License-Identifier: MIT SPDX-License-Identifier: MIT
""" """
from collections import defaultdict from collections import defaultdict
from typing import Union
from .helpers import * from .helpers import *
from .types import Int32, Float32 if typing.TYPE_CHECKING:
from .types import Int32
class Registers: class Registers:
@ -17,115 +17,43 @@ class Registers:
Represents a bunch of registers Represents a bunch of registers
""" """
valid_regs = { def __init__(self):
"zero", from .types import Int32
"ra", self.vals = defaultdict(lambda: Int32(0))
"sp",
"gp",
"tp",
"s0",
"fp",
"t0",
"t1",
"t2",
"t3",
"t4",
"t5",
"t6",
"s1",
"s2",
"s3",
"s4",
"s5",
"s6",
"s7",
"s8",
"s9",
"s10",
"s11",
"a0",
"a1",
"a2",
"a3",
"a4",
"a5",
"a6",
"a7",
}
float_regs = {
"ft0",
"ft1",
"ft2",
"ft3",
"ft4",
"ft5",
"ft6",
"ft7",
"fs0",
"fs1",
"fs2",
"fs3",
"fs4",
"fs5",
"fs6",
"fs7",
"fs8",
"fs9",
"fs10",
"fs11",
"fa0",
"fa1",
"fa2",
"fa3",
"fa4",
"fa5",
"fa6",
"fa7",
}
def __init__(self, infinite_regs: bool = False):
self.vals: dict[str, Int32] = defaultdict(UInt32)
self.float_vals: dict[str, Float32] = defaultdict(Float32)
self.last_set = None self.last_set = None
self.last_read = None self.last_read = None
self.infinite_regs = infinite_regs def dump(self, full=False):
def dump(self, full: bool = False):
""" """
Dump all registers to stdout Dump all registers to stdout
:param full: If True, floating point registers are dumped too :param full: If True, floating point registers are dumped too
""" """
named_regs = [self._reg_repr(reg) for reg in Registers.named_registers()] named_regs = [self._reg_repr(reg) for reg in Registers.named_registers()]
lines: list[list[str]] = [[] for _ in range(12)] lines = [[] for i in range(12)]
if not full: if not full:
regs = [("a", 8), ("s", 12), ("t", 7)] regs = [('a', 8), ('s', 12), ('t', 7)]
else: else:
regs = [ regs = [
("a", 8), ('a', 8),
("s", 12), ('s', 12),
("t", 7), ('t', 7),
("ft", 8), ('ft', 8),
("fa", 8), ('fa', 8),
("fs", 12), ('fs', 12),
] ]
for name, s in regs: for name, s in regs:
for i in range(12): for i in range(12):
if i >= s: if i >= s:
lines[i].append(" " * 15) lines[i].append(" " * 15)
else: else:
reg = "{}{}".format(name, i) reg = '{}{}'.format(name, i)
lines[i].append(self._reg_repr(reg)) lines[i].append(self._reg_repr(reg))
print( print("Registers[{},{}](".format(
"Registers[{},{}](".format( FMT_ORANGE + FMT_UNDERLINE + 'read' + FMT_NONE,
FMT_ORANGE + FMT_UNDERLINE + "read" + FMT_NONE, FMT_ORANGE + FMT_BOLD + 'written' + FMT_NONE
FMT_ORANGE + FMT_BOLD + "written" + FMT_NONE, ))
)
)
if not full: if not full:
print("\t" + " ".join(named_regs[0:3])) print("\t" + " ".join(named_regs[0:3]))
print("\t" + " ".join(named_regs[3:])) print("\t" + " ".join(named_regs[3:]))
@ -141,35 +69,23 @@ class Registers:
""" """
Dump the a registers Dump the a registers
""" """
print( print("Registers[a]:" + " ".join(self._reg_repr('a{}'.format(i)) for i in range(8)))
"Registers[a]:"
+ " ".join(self._reg_repr("a{}".format(i)) for i in range(8)) def _reg_repr(self, reg):
) txt = '{:4}=0x{:08X}'.format(reg, self.get(reg, False))
if reg == 'fp':
def _reg_repr(self, reg: str, name_len=4, fmt="08X"): reg = 's0'
if reg in self.float_regs:
txt = "{:{}}={: .3e}".format(reg, name_len, self.get_f(reg, False))
else:
txt = "{:{}}=0x{:{}}".format(reg, name_len, self.get(reg, False), fmt)
if reg == "fp":
reg = "s0"
if reg == self.last_set: if reg == self.last_set:
return FMT_ORANGE + FMT_BOLD + txt + FMT_NONE return FMT_ORANGE + FMT_BOLD + txt + FMT_NONE
if reg == self.last_read: if reg == self.last_read:
return FMT_ORANGE + FMT_UNDERLINE + txt + FMT_NONE return FMT_ORANGE + FMT_UNDERLINE + txt + FMT_NONE
if reg == "zero": if reg == 'zero':
return txt return txt
should_grayscale_int = ( if self.get(reg, False) == 0 and reg not in Registers.named_registers():
reg in self.valid_regs
and self.get(reg, False) == 0
and reg not in Registers.named_registers()
)
should_grayscale_float = reg in self.float_regs and self.get_f(reg, False) == 0
if should_grayscale_int or should_grayscale_float:
return FMT_GRAY + txt + FMT_NONE return FMT_GRAY + txt + FMT_NONE
return txt return txt
def set(self, reg: str, val: "Int32", mark_set: bool = True) -> bool: def set(self, reg, val: 'Int32', mark_set=True) -> bool:
""" """
Set a register content to val Set a register content to val
:param reg: The register to set :param reg: The register to set
@ -178,29 +94,25 @@ class Registers:
:return: If the operation was successful :return: If the operation was successful
""" """
from .types import Int32
# remove after refactoring is complete # remove after refactoring is complete
if not isinstance(val, Int32): if not isinstance(val, Int32):
raise RuntimeError( raise RuntimeError("Setting register to non-Int32 value! Please refactor your code!")
"Setting register to non-Int32 value! Please refactor your code!"
)
if reg == "zero": if reg == 'zero':
return False return False
# if reg not in Registers.all_registers(): # if reg not in Registers.all_registers():
# raise InvalidRegisterException(reg) # raise InvalidRegisterException(reg)
# replace fp register with s1, as these are the same register # replace fp register with s1, as these are the same register
if reg == "fp": if reg == 'fp':
reg = "s1" reg = 's1'
if mark_set: if mark_set:
self.last_set = reg self.last_set = reg
# check 32 bit signed bounds
if not self.infinite_regs and reg not in self.valid_regs:
raise RuntimeError("Invalid register: {}".format(reg))
self.vals[reg] = val.unsigned() self.vals[reg] = val.unsigned()
return True return True
def get(self, reg: str, mark_read: bool = True) -> "Int32": def get(self, reg, mark_read=True) -> 'Int32':
""" """
Retuns the contents of register reg Retuns the contents of register reg
:param reg: The register name :param reg: The register name
@ -209,27 +121,25 @@ class Registers:
""" """
# if reg not in Registers.all_registers(): # if reg not in Registers.all_registers():
# raise InvalidRegisterException(reg) # raise InvalidRegisterException(reg)
if reg == "fp": if reg == 'fp':
reg = "s0" reg = 's0'
if not self.infinite_regs and reg not in self.valid_regs:
raise RuntimeError("Invalid register: {}".format(reg))
if mark_read: if mark_read:
self.last_read = reg self.last_read = reg
return self.vals[reg] return self.vals[reg]
def get_f(self, reg: str, mark_read: bool = True) -> "Float32": @staticmethod
if not self.infinite_regs and reg not in self.float_regs: def all_registers():
raise RuntimeError("Invalid float register: {}".format(reg)) """
if mark_read: Return a list of all valid registers
self.last_read = reg :return: The list
return self.float_vals[reg] """
return ['zero', 'ra', 'sp', 'gp', 'tp', 's0', 'fp',
def set_f(self, reg: str, val: Union[float, "Float32"]): 't0', 't1', 't2', 't3', 't4', 't5', 't6',
if not self.infinite_regs and reg not in self.float_regs: 's1', 's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11',
raise RuntimeError("Invalid float register: {}".format(reg)) 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7',
self.float_vals[reg] = Float32(val) 'ft0', 'ft1', 'ft2', 'ft3', 'ft4', 'ft5', 'ft6', 'ft7',
'fs0', 'fs1', 'fs2', 'fs3', 'fs4', 'fs5', 'fs6', 'fs7', 'fs8', 'fs9', 'fs10', 'fs11',
'fa0', 'fa1', 'fa2', 'fa3', 'fa4', 'fa5', 'fa6', 'fa7']
@staticmethod @staticmethod
def named_registers(): def named_registers():
@ -237,12 +147,4 @@ class Registers:
Return all named registers Return all named registers
:return: The list :return: The list
""" """
return ["zero", "ra", "sp", "gp", "tp", "fp"] return ['zero', 'ra', 'sp', 'gp', 'tp', 'fp']
def __repr__(self):
return "<Registers[{}]{}>".format(
"float" if self.supports_floats else "nofloat",
"{"
+ ", ".join(self._reg_repr("a{}".format(i), 2, "0x") for i in range(8))
+ "}",
)

@ -1,284 +0,0 @@
import argparse
import glob
import os
import sys
from typing import Type, Dict, List, Optional
from riscemu import AssemblyFileLoader, __version__, __copyright__
from riscemu.types import CPU, ProgramLoader, Program
from riscemu.instructions import InstructionSet, InstructionSetDict
from riscemu.config import RunConfig
from riscemu.CPU import UserModeCPU
class RiscemuMain:
"""
This represents the riscemu API exposed to other programs for better
interoperability.
"""
available_ins_sets: Dict[str, Type[InstructionSet]]
available_file_loaders: List[Type[ProgramLoader]]
cfg: Optional[RunConfig]
cpu: Optional[CPU]
input_files: List[str]
selected_ins_sets: List[Type[InstructionSet]]
def __init__(self, cfg: Optional[RunConfig] = None):
self.available_ins_sets = dict()
self.selected_ins_sets = []
self.available_file_loaders = []
self.cfg: Optional[RunConfig] = cfg
self.cpu: Optional[CPU] = None
self.input_files = []
self.selected_ins_sets = []
def instantiate_cpu(self):
self.cpu = UserModeCPU(self.selected_ins_sets, self.cfg)
self.configure_cpu()
def configure_cpu(self):
assert self.cfg is not None
if isinstance(self.cpu, UserModeCPU) and self.cfg.stack_size != 0:
self.cpu.setup_stack(self.cfg.stack_size)
def register_all_arguments(self, parser: argparse.ArgumentParser):
parser.add_argument(
"files",
metavar="file.asm",
type=str,
nargs="+",
help="The assembly files to load, the last one will be run",
)
parser.add_argument(
"--options",
"-o",
action=OptionStringAction,
keys=(
"disable_debug",
"no_syscall_symbols",
"fail_on_ex",
"add_accept_imm",
"unlimited_regs",
"libc",
"ignore_exit_code",
),
help="""Toggle options. Available options are:
disable_debug: Disable ebreak instructions
no_syscall_symbols: Don't add symbols for SCALL_EXIT and others
fail_on_ex: If set, exceptions won't trigger the debugger
add_accept_imm: Accept "add rd, rs, imm" instruction (instead of addi)
unlimited_regs: Allow an unlimited number of registers
libc: Load a libc-like runtime (for malloc, etc.)
ignore_exit_code: Don't exit with the programs exit code.""",
)
parser.add_argument(
"--syscall-opts",
"-so",
action=OptionStringAction,
keys=("fs_access", "disable_input"),
)
parser.add_argument(
"--instruction-sets",
"-is",
action=OptionStringAction,
help="Instruction sets to load, available are: {}. All are enabled by default".format(
", ".join(self.available_ins_sets)
),
keys={k: True for k in self.available_ins_sets},
omit_empty=True,
)
parser.add_argument(
"--stack_size",
type=int,
help="Stack size of loaded programs, defaults to 8MB",
nargs="?",
)
parser.add_argument(
"-v",
"--verbose",
help="Verbosity level (can be used multiple times)",
action="count",
default=0,
)
parser.add_argument(
"--interactive",
help="Launch the interactive debugger instantly instead of loading any "
"programs",
action="store_true",
)
parser.add_argument(
"--ignore-exit-code",
help="Ignore exit code of the program and always return 0 if the program ran to completion.",
action="store_true",
default=False,
)
def register_all_isas(self):
self.available_ins_sets.update(InstructionSetDict)
def register_all_program_loaders(self):
self.available_file_loaders.append(AssemblyFileLoader)
def parse_argv(self, argv: List[str]):
parser = argparse.ArgumentParser(
description="RISC-V Userspace emulator",
prog="riscemu",
formatter_class=argparse.RawTextHelpFormatter,
)
if "--version" in argv:
print(
"riscemu version {}\n{}\n\nAvailable ISA: {}".format(
__version__, __copyright__, ", ".join(self.available_ins_sets)
)
)
sys.exit()
self.register_all_arguments(parser)
# parse argv
args = parser.parse_args(argv)
# add ins
if not hasattr(args, "ins"):
setattr(args, "ins", {k: True for k in self.available_ins_sets})
# create RunConfig
self.cfg = self.create_config(args)
# set input files
self.input_files = args.files
# get selected ins sets
self.selected_ins_sets = list(
self.available_ins_sets[name]
for name, selected in args.ins.items()
if selected
)
# if use_libc is given, attach libc to path
if self.cfg.use_libc:
self.add_libc_to_input_files()
def add_libc_to_input_files(self):
"""
This adds the provided riscemu libc to the programs runtime.
"""
libc_path = os.path.join(
os.path.dirname(__file__),
"..",
"libc",
"*.s",
)
for path in glob.iglob(libc_path):
if path not in self.input_files:
self.input_files.append(path)
def create_config(self, args: argparse.Namespace) -> RunConfig:
# create a RunConfig from the cli args
cfg_dict = dict(
stack_size=args.stack_size,
debug_instruction=not args.options["disable_debug"],
include_scall_symbols=not args.options["no_syscall_symbols"],
debug_on_exception=not args.options["fail_on_ex"],
add_accept_imm=args.options["add_accept_imm"],
unlimited_registers=args.options["unlimited_regs"],
scall_fs=args.syscall_opts["fs_access"],
scall_input=not args.syscall_opts["disable_input"],
verbosity=args.verbose,
use_libc=args.options["libc"],
ignore_exit_code=args.options["ignore_exit_code"],
)
for k, v in dict(cfg_dict).items():
if v is None:
del cfg_dict[k]
return RunConfig(**cfg_dict)
def load_programs(self):
for path in self.input_files:
for loader in self.available_file_loaders:
if not loader.can_parse(path):
continue
programs = loader.instantiate(path, {}).parse()
if isinstance(programs, Program):
programs = [programs]
for p in programs:
self.cpu.mmu.load_program(p)
def run_from_cli(self, argv: List[str]):
# register everything
self.register_all_isas()
self.register_all_program_loaders()
# parse argv and set up cpu
self.parse_argv(argv)
self.instantiate_cpu()
self.load_programs()
# run the program
self.cpu.launch(self.cfg.verbosity > 1)
def run(self):
"""
This assumes that these values were set externally:
- available_file_loaders: A list of available file loaders.
Can be set using .register_all_program_loaders()
- cfg: The RunConfig object. Can be directly assigned to the attribute
- input_files: A list of assembly files to load.
- selected_ins_sets: A list of instruction sets the CPU should support.
"""
assert self.cfg is not None, "self.cfg must be set before calling run()"
assert self.selected_ins_sets, "self.selected_ins_sets cannot be empty"
assert self.input_files, "self.input_files cannot be empty"
if self.cfg.use_libc:
self.add_libc_to_input_files()
self.instantiate_cpu()
self.load_programs()
# run the program
self.cpu.launch(self.cfg.verbosity > 1)
class OptionStringAction(argparse.Action):
def __init__(self, option_strings, dest, keys=None, omit_empty=False, **kwargs):
if keys is None:
raise ValueError('must define "keys" argument')
if isinstance(keys, dict):
keys_d = keys
elif isinstance(keys, (list, tuple)):
keys_d = {}
for k in keys:
if isinstance(k, tuple):
k, v = k
else:
v = False
keys_d[k] = v
else:
keys_d = dict()
super().__init__(option_strings, dest, default=keys_d, **kwargs)
self.keys = keys_d
self.omit_empty = omit_empty
def __call__(self, parser, namespace, values, option_string=None):
d = {}
if not self.omit_empty:
d.update(self.keys)
for x in values.split(","):
if x in self.keys:
d[x] = True
else:
raise ValueError("Invalid parameter supplied: " + x)
setattr(namespace, self.dest, d)

@ -6,48 +6,33 @@ SPDX-License-Identifier: MIT
import sys import sys
from dataclasses import dataclass from dataclasses import dataclass
from math import log2, ceil from typing import Dict, IO
from typing import Dict, IO, Union
from .types import BinaryDataMemorySection, MemoryFlags
from .colors import FMT_SYSCALL, FMT_NONE from .colors import FMT_SYSCALL, FMT_NONE
from .types import Int32, CPU from .types import Int32, CPU
from .types.exceptions import InvalidSyscallException from .types.exceptions import InvalidSyscallException
SYSCALLS = { SYSCALLS = {
63: "read", 63: 'read',
64: "write", 64: 'write',
93: "exit", 93: 'exit',
192: "mmap2", 1024: 'open',
1024: "open", 1025: 'close',
1025: "close",
} }
""" """
This dict contains a mapping for all available syscalls (code->name) This dict contains a mapping for all available syscalls (code->name)
If you wish to add a syscall to the built-in system, you can extend this If you wish to add a syscall to the built-in system, you can extend this
dictionary and implement a method with the same name on the SyscallInterface dictionary and implement a method with the same name on the SyscallInterface
class. class.
""" """
ADDITIONAL_SYMBOLS = {
"MAP_PRIVATE": 1 << 0,
"MAP_SHARED": 1 << 1,
"MAP_ANON": 1 << 2,
"MAP_ANONYMOUS": 1 << 2,
"PROT_READ": 1 << 0,
"PROT_WRITE": 1 << 1,
}
"""
A set of additional symbols that are used by various syscalls.
"""
OPEN_MODES = { OPEN_MODES = {
0: "rb", 0: 'rb',
1: "wb", 1: 'wb',
2: "r+b", 2: 'r+b',
3: "x", 3: 'x',
4: "ab", 4: 'ab',
} }
"""All available file open modes""" """All available file open modes"""
@ -57,7 +42,6 @@ class Syscall:
""" """
Represents a syscall Represents a syscall
""" """
id: int id: int
"""The syscall number (e.g. 64 - write)""" """The syscall number (e.g. 64 - write)"""
cpu: CPU cpu: CPU
@ -68,10 +52,12 @@ class Syscall:
return SYSCALLS.get(self.id, "unknown") return SYSCALLS.get(self.id, "unknown")
def __repr__(self): def __repr__(self):
return "Syscall(id={}, name={})".format(self.id, self.name) return "Syscall(id={}, name={})".format(
self.id, self.name
)
def ret(self, code: Union[int, Int32]): def ret(self, code):
self.cpu.regs.set("a0", Int32(code)) self.cpu.regs.set('a0', Int32(code))
def get_syscall_symbols(): def get_syscall_symbols():
@ -80,26 +66,25 @@ def get_syscall_symbols():
:return: dictionary of all syscall symbols (SCALL_<name> -> id) :return: dictionary of all syscall symbols (SCALL_<name> -> id)
""" """
items: Dict[str, int] = { return {
("SCALL_" + name.upper()): num for num, name in SYSCALLS.items() ('SCALL_' + name.upper()): num for num, name in SYSCALLS.items()
} }
items.update(ADDITIONAL_SYMBOLS)
return items
class SyscallInterface: class SyscallInterface:
""" """
Handles syscalls Handles syscalls
""" """
open_files: Dict[int, IO] open_files: Dict[int, IO]
next_open_handle: int next_open_handle: int
def handle_syscall(self, scall: Syscall): def handle_syscall(self, scall: Syscall):
self.next_open_handle = 3 self.next_open_handle = 3
self.open_files = {0: sys.stdin, 1: sys.stdout, 2: sys.stderr} self.open_files = {
0: sys.stdin,
1: sys.stdout,
2: sys.stderr
}
if getattr(self, scall.name): if getattr(self, scall.name):
getattr(self, scall.name)(scall) getattr(self, scall.name)(scall)
@ -111,25 +96,21 @@ class SyscallInterface:
read syscall (63): read from file no a0, into addr a1, at most a2 bytes read syscall (63): read from file no a0, into addr a1, at most a2 bytes
on return a0 will be the number of read bytes or -1 if an error occured on return a0 will be the number of read bytes or -1 if an error occured
""" """
fileno = scall.cpu.regs.get("a0").unsigned_value fileno = scall.cpu.regs.get('a0').unsigned_value
addr = scall.cpu.regs.get("a1").unsigned_value addr = scall.cpu.regs.get('a1').unsigned_value
size = scall.cpu.regs.get("a2").unsigned_value size = scall.cpu.regs.get('a2').unsigned_value
if fileno not in self.open_files: if fileno not in self.open_files:
scall.ret(-1) scall.ret(-1)
return return
chars = self.open_files[fileno].readline(size) chars = self.open_files[fileno].readline(size)
try: try:
data = bytearray(chars, "ascii") data = bytearray(chars, 'ascii')
scall.cpu.mmu.write(addr, len(data), data) scall.cpu.mmu.write(addr, len(data), data)
return scall.ret(len(data)) return scall.ret(len(data))
except UnicodeEncodeError: except UnicodeEncodeError:
print( print(FMT_SYSCALL + '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars) + FMT_NONE)
FMT_SYSCALL
+ '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars)
+ FMT_NONE
)
return scall.ret(-1) return scall.ret(-1)
def write(self, scall: Syscall): def write(self, scall: Syscall):
@ -137,23 +118,19 @@ class SyscallInterface:
write syscall (64): write a2 bytes from addr a1 into fileno a0 write syscall (64): write a2 bytes from addr a1 into fileno a0
on return a0 will hold the number of bytes written or -1 if an error occured on return a0 will hold the number of bytes written or -1 if an error occured
""" """
fileno = scall.cpu.regs.get("a0").unsigned_value fileno = scall.cpu.regs.get('a0').unsigned_value
addr = scall.cpu.regs.get("a1").unsigned_value addr = scall.cpu.regs.get('a1').unsigned_value
size = scall.cpu.regs.get("a2").unsigned_value size = scall.cpu.regs.get('a2').unsigned_value
if fileno not in self.open_files: if fileno not in self.open_files:
return scall.ret(-1) return scall.ret(-1)
data = scall.cpu.mmu.read(addr, size) data = scall.cpu.mmu.read(addr, size)
if not isinstance(data, bytearray): if not isinstance(data, bytearray):
print( print(FMT_SYSCALL + '[Syscall] write: writing from .text region not supported.' + FMT_NONE)
FMT_SYSCALL
+ "[Syscall] write: writing from .text region not supported."
+ FMT_NONE
)
return scall.ret(-1) return scall.ret(-1)
self.open_files[fileno].write(data.decode("ascii")) self.open_files[fileno].write(data.decode('ascii'))
return scall.ret(size) return scall.ret(size)
def open(self, scall: Syscall): def open(self, scall: Syscall):
@ -172,29 +149,19 @@ class SyscallInterface:
""" """
# FIXME: this should be toggleable in a global setting or something # FIXME: this should be toggleable in a global setting or something
if True: if True:
print( print(FMT_SYSCALL + '[Syscall] open: opening files not supported without scall-fs flag!' + FMT_NONE)
FMT_SYSCALL
+ "[Syscall] open: opening files not supported without scall-fs flag!"
+ FMT_NONE
)
return scall.ret(-1) return scall.ret(-1)
mode = scall.cpu.regs.get("a0").unsigned_value mode = scall.cpu.regs.get('a0').unsigned_value
addr = scall.cpu.regs.get("a1").unsigned_value addr = scall.cpu.regs.get('a1').unsigned_value
size = scall.cpu.regs.get("a2").unsigned_value size = scall.cpu.regs.get('a2').unsigned_value
mode_st = OPEN_MODES.get( mode_st = OPEN_MODES.get(mode, )
mode,
)
if mode_st == -1: if mode_st == -1:
print( print(FMT_SYSCALL + '[Syscall] open: unknown opening mode {}!'.format(mode) + FMT_NONE)
FMT_SYSCALL
+ "[Syscall] open: unknown opening mode {}!".format(mode)
+ FMT_NONE
)
return scall.ret(-1) return scall.ret(-1)
path = scall.cpu.mmu.read(addr, size).decode("ascii") path = scall.cpu.mmu.read(addr, size).decode('ascii')
fileno = self.next_open_handle fileno = self.next_open_handle
self.next_open_handle += 1 self.next_open_handle += 1
@ -202,18 +169,10 @@ class SyscallInterface:
try: try:
self.open_files[fileno] = open(path, mode_st) self.open_files[fileno] = open(path, mode_st)
except OSError as err: except OSError as err:
print( print(FMT_SYSCALL + '[Syscall] open: encountered error during {}!'.format(err.strerror) + FMT_NONE)
FMT_SYSCALL
+ "[Syscall] open: encountered error during {}!".format(err.strerror)
+ FMT_NONE
)
return scall.ret(-1) return scall.ret(-1)
print( print(FMT_SYSCALL + '[Syscall] open: opened fd {} to {}!'.format(fileno, path) + FMT_NONE)
FMT_SYSCALL
+ "[Syscall] open: opened fd {} to {}!".format(fileno, path)
+ FMT_NONE
)
return scall.ret(fileno) return scall.ret(fileno)
def close(self, scall: Syscall): def close(self, scall: Syscall):
@ -222,17 +181,13 @@ class SyscallInterface:
return -1 if an error was encountered, otherwise returns 0 return -1 if an error was encountered, otherwise returns 0
""" """
fileno = scall.cpu.regs.get("a0").unsigned_value fileno = scall.cpu.regs.get('a0').unsigned_value
if fileno not in self.open_files: if fileno not in self.open_files:
print( print(FMT_SYSCALL + '[Syscall] close: unknown fileno {}!'.format(fileno) + FMT_NONE)
FMT_SYSCALL
+ "[Syscall] close: unknown fileno {}!".format(fileno)
+ FMT_NONE
)
return scall.ret(-1) return scall.ret(-1)
self.open_files[fileno].close() self.open_files[fileno].close()
print(FMT_SYSCALL + "[Syscall] close: closed fd {}!".format(fileno) + FMT_NONE) print(FMT_SYSCALL + '[Syscall] close: closed fd {}!'.format(fileno) + FMT_NONE)
del self.open_files[fileno] del self.open_files[fileno]
return scall.ret(0) return scall.ret(0)
@ -241,55 +196,10 @@ class SyscallInterface:
Exit syscall. Exits the system with status code a0 Exit syscall. Exits the system with status code a0
""" """
scall.cpu.halted = True scall.cpu.halted = True
scall.cpu.exit_code = scall.cpu.regs.get("a0").signed().value scall.cpu.exit_code = scall.cpu.regs.get('a0').value
def mmap2(self, scall: Syscall):
"""
mmap2 syscall:
void *mmap(void *addr, size_t length, int prot, int flags,
int fd, off_t offset);
Only supported modes:
addr = <any>
prot = either PROT_READ or PROT_READ | PROT_WRITE
flags = MAP_PRIVATE | MAP_ANONYMOUS
fd = <ignored>
off_t = <ignored>
"""
addr = scall.cpu.regs.get("a0").unsigned_value
size = scall.cpu.regs.get("a1").unsigned_value
prot = scall.cpu.regs.get("a2").unsigned_value
flags = scall.cpu.regs.get("a3").unsigned_value
# error out if prot is not 1 or 3:
# 1 = PROT_READ
# 3 = PROT_READ | PROT_WRITE
if prot != 1 and prot != 3:
return scall.ret(-1)
# round size up to multiple of 4096
size = 4096 * ceil(size / 4096)
section = BinaryDataMemorySection(
bytearray(size),
".data.runtime-allocated",
None,
"system",
base=addr,
flags=MemoryFlags(read_only=prot != 3, executable=False),
)
# try to insert section
if scall.cpu.mmu.load_section(section, addr != 0):
return scall.ret(section.base)
# if that failed, and we tried to force an address,
# try again at any address
elif addr != 0:
section.base = 0
if scall.cpu.mmu.load_section(section):
return scall.ret(section.base)
# if that didn't work, return error
return scall.ret(-1)
def __repr__(self): def __repr__(self):
return "{}(\n\tfiles={}\n)".format(self.__class__.__name__, self.open_files) return "{}(\n\tfiles={}\n)".format(
self.__class__.__name__,
self.open_files
)

@ -12,11 +12,9 @@ from typing import List, Iterable
from riscemu.decoder import RISCV_REGS from riscemu.decoder import RISCV_REGS
from riscemu.types.exceptions import ParseException from riscemu.types.exceptions import ParseException
LINE_COMMENT_STARTERS = ("#", ";", "//") LINE_COMMENT_STARTERS = ('#', ';', '//')
WHITESPACE_PATTERN = re.compile(r"\s+") WHITESPACE_PATTERN = re.compile(r'\s+')
MEMORY_ADDRESS_PATTERN = re.compile( MEMORY_ADDRESS_PATTERN = re.compile(r'^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]{0,2})\)$')
r"^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]*)\)$"
)
REGISTER_NAMES = RISCV_REGS REGISTER_NAMES = RISCV_REGS
@ -36,22 +34,22 @@ class Token:
def __str__(self): def __str__(self):
if self.type == TokenType.NEWLINE: if self.type == TokenType.NEWLINE:
return "\\n" return '\\n'
if self.type == TokenType.COMMA: if self.type == TokenType.COMMA:
return ", " return ', '
return "{}({})".format(self.type.name[0:3], self.value) return '{}({})'.format(self.type.name[0:3], self.value)
NEWLINE = Token(TokenType.NEWLINE, "\n") NEWLINE = Token(TokenType.NEWLINE, '\n')
COMMA = Token(TokenType.COMMA, ",") COMMA = Token(TokenType.COMMA, ',')
def tokenize(input: Iterable[str]) -> Iterable[Token]: def tokenize(input: Iterable[str]) -> Iterable[Token]:
for line in input: for line in input:
for line_comment_start in LINE_COMMENT_STARTERS: for line_comment_start in LINE_COMMENT_STARTERS:
if line_comment_start in line: if line_comment_start in line:
line = line[: line.index(line_comment_start)] line = line[:line.index(line_comment_start)]
line.strip(" \t\n") line.strip(' \t\n')
if not line: if not line:
continue continue
@ -66,9 +64,9 @@ def parse_line(parts: List[str]) -> Iterable[Token]:
return () return ()
first_token = parts[0] first_token = parts[0]
if first_token[0] == ".": if first_token[0] == '.':
yield Token(TokenType.PSEUDO_OP, first_token) yield Token(TokenType.PSEUDO_OP, first_token)
elif first_token[-1] == ":": elif first_token[-1] == ':':
yield Token(TokenType.LABEL, first_token) yield Token(TokenType.LABEL, first_token)
yield from parse_line(parts[1:]) yield from parse_line(parts[1:])
return return
@ -76,21 +74,22 @@ def parse_line(parts: List[str]) -> Iterable[Token]:
yield Token(TokenType.INSTRUCTION_NAME, first_token) yield Token(TokenType.INSTRUCTION_NAME, first_token)
for part in parts[1:]: for part in parts[1:]:
if part == ",": if part == ',':
yield COMMA yield COMMA
continue continue
yield from parse_arg(part) yield from parse_arg(part)
def parse_arg(arg: str) -> Iterable[Token]: def parse_arg(arg: str) -> Iterable[Token]:
comma = arg[-1] == "," comma = arg[-1] == ','
arg = arg[:-1] if comma else arg arg = arg[:-1] if comma else arg
mem_match_resul = re.match(MEMORY_ADDRESS_PATTERN, arg) mem_match_resul = re.match(MEMORY_ADDRESS_PATTERN, arg)
if mem_match_resul: if mem_match_resul:
register = mem_match_resul.group(2).lower() register = mem_match_resul.group(2).lower()
immediate = mem_match_resul.group(1) if register not in RISCV_REGS:
raise ParseException(f'"{register}" is not a valid register!')
yield Token(TokenType.ARGUMENT, register) yield Token(TokenType.ARGUMENT, register)
yield Token(TokenType.ARGUMENT, immediate) yield Token(TokenType.ARGUMENT, mem_match_resul.group(1))
else: else:
yield Token(TokenType.ARGUMENT, arg) yield Token(TokenType.ARGUMENT, arg)
if comma: if comma:
@ -99,7 +98,7 @@ def parse_arg(arg: str) -> Iterable[Token]:
def print_tokens(tokens: Iterable[Token]): def print_tokens(tokens: Iterable[Token]):
for token in tokens: for token in tokens:
print(token, end="\n" if token == NEWLINE else "") print(token, end='\n' if token == NEWLINE else '')
print("", flush=True, end="") print("", flush=True, end="")
@ -124,7 +123,7 @@ def split_whitespace_respecting_quotes(line: str) -> Iterable[str]:
part = "" part = ""
continue continue
if c in " \t\n": if c in ' \t\n':
if part: if part:
yield part yield part
part = "" part = ""

@ -1,2 +0,0 @@
#!/usr/bin/env bash
python3 -m riscemu "$@"

@ -1,4 +1,4 @@
from typing import Dict, Any from typing import Dict
import re import re
# define some base type aliases so we can keep track of absolute and relative addresses # define some base type aliases so we can keep track of absolute and relative addresses
@ -6,14 +6,13 @@ T_RelativeAddress = int
T_AbsoluteAddress = int T_AbsoluteAddress = int
# parser options are just dictionaries with arbitrary values # parser options are just dictionaries with arbitrary values
T_ParserOpts = Dict[str, Any] T_ParserOpts = Dict[str, any]
NUMBER_SYMBOL_PATTERN = re.compile(r"^\d+[fb]$") NUMBER_SYMBOL_PATTERN = re.compile(r'^\d+[fb]$')
# base classes # base classes
from .flags import MemoryFlags from .flags import MemoryFlags
from .int32 import UInt32, Int32 from .int32 import UInt32, Int32
from .float32 import Float32
from .instruction import Instruction from .instruction import Instruction
from .instruction_context import InstructionContext from .instruction_context import InstructionContext
from .memory_section import MemorySection from .memory_section import MemorySection
@ -25,17 +24,6 @@ from .instruction_memory_section import InstructionMemorySection
from .binary_data_memory_section import BinaryDataMemorySection from .binary_data_memory_section import BinaryDataMemorySection
# exceptions # exceptions
from .exceptions import ( from .exceptions import ParseException, NumberFormatException, MemoryAccessException, OutOfMemoryException, \
ParseException, LinkerException, LaunchDebuggerException, RiscemuBaseException, InvalidRegisterException, \
NumberFormatException, InvalidAllocationException, InvalidSyscallException, UnimplementedInstruction
MemoryAccessException,
OutOfMemoryException,
LinkerException,
LaunchDebuggerException,
RiscemuBaseException,
InvalidRegisterException,
InvalidAllocationException,
InvalidSyscallException,
UnimplementedInstruction,
INS_NOT_IMPLEMENTED,
)

@ -1,56 +1,29 @@
from typing import Optional from . import MemorySection, InstructionContext, MemoryFlags, T_RelativeAddress, Instruction
from . import (
MemorySection,
InstructionContext,
MemoryFlags,
T_RelativeAddress,
Instruction,
)
from ..types.exceptions import MemoryAccessException from ..types.exceptions import MemoryAccessException
class BinaryDataMemorySection(MemorySection): class BinaryDataMemorySection(MemorySection):
def __init__( def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int = 0, flags: MemoryFlags = None):
self, self.name = name
data: bytearray, self.base = base
name: str, self.context = context
context: InstructionContext, self.size = len(data)
owner: str, self.flags = flags if flags is not None else MemoryFlags(False, False)
base: int = 0,
flags: Optional[MemoryFlags] = None,
):
super().__init__(
name,
flags if flags is not None else MemoryFlags(False, False),
len(data),
base,
owner,
context,
)
self.data = data self.data = data
self.owner = owner
def read(self, offset: T_RelativeAddress, size: int) -> bytearray: def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
if offset + size > self.size: if offset + size > self.size:
raise MemoryAccessException( raise MemoryAccessException("Out of bounds access in {}".format(self), offset, size, 'read')
"Out of bounds access in {}".format(self), offset, size, "read" return self.data[offset:offset + size]
)
return self.data[offset : offset + size]
def write(self, offset: T_RelativeAddress, size: int, data: bytearray): def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
if offset + size > self.size: if offset + size > self.size:
raise MemoryAccessException( raise MemoryAccessException("Out of bounds access in {}".format(self), offset, size, 'write')
"Out of bounds access in {}".format(self), offset, size, "write"
)
if len(data[0:size]) != size: if len(data[0:size]) != size:
raise MemoryAccessException( raise MemoryAccessException("Invalid write parameter sizing", offset, size, 'write')
"Invalid write parameter sizing", offset, size, "write" self.data[offset:offset + size] = data[0:size]
)
self.data[offset : offset + size] = data[0:size]
def read_ins(self, offset: T_RelativeAddress) -> Instruction: def read_ins(self, offset: T_RelativeAddress) -> Instruction:
raise MemoryAccessException( raise MemoryAccessException("Tried reading instruction on non-executable section {}".format(self),
"Tried reading instruction on non-executable section {}".format(self), offset, 4, 'instruction fetch')
offset,
4,
"instruction fetch",
)

@ -1,15 +1,12 @@
import typing
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import List, Type, Callable, Set, Dict, TYPE_CHECKING, Iterable from typing import List, Type, Callable, Set, Dict
from ..registers import Registers from ..registers import Registers
from ..config import RunConfig from ..config import RunConfig
from ..colors import FMT_NONE, FMT_CPU from ..colors import FMT_RED, FMT_NONE, FMT_ERROR, FMT_CPU
from . import T_AbsoluteAddress, Instruction, Program, ProgramLoader from . import T_AbsoluteAddress, Instruction, Program, ProgramLoader
if TYPE_CHECKING:
from ..MMU import MMU
from ..instructions import InstructionSet
class CPU(ABC): class CPU(ABC):
# static cpu configuration # static cpu configuration
@ -17,7 +14,7 @@ class CPU(ABC):
# housekeeping variables # housekeeping variables
regs: Registers regs: Registers
mmu: "MMU" mmu: 'MMU'
pc: T_AbsoluteAddress pc: T_AbsoluteAddress
cycle: int cycle: int
halted: bool halted: bool
@ -27,19 +24,14 @@ class CPU(ABC):
# instruction information # instruction information
instructions: Dict[str, Callable[[Instruction], None]] instructions: Dict[str, Callable[[Instruction], None]]
instruction_sets: Set["InstructionSet"] instruction_sets: Set['InstructionSet']
# configuration # configuration
conf: RunConfig conf: RunConfig
def __init__( def __init__(self, mmu: 'MMU', instruction_sets: List[Type['InstructionSet']], conf: RunConfig):
self,
mmu: "MMU",
instruction_sets: List[Type["InstructionSet"]],
conf: RunConfig,
):
self.mmu = mmu self.mmu = mmu
self.regs = Registers(conf.unlimited_registers) self.regs = Registers()
self.conf = conf self.conf = conf
self.instruction_sets = set() self.instruction_sets = set()
@ -79,37 +71,32 @@ class CPU(ABC):
self.pc, self.pc,
self.cycle, self.cycle,
self.halted, self.halted,
" ".join(s.name for s in self.instruction_sets), " ".join(s.name for s in self.instruction_sets)
) )
@abstractmethod @abstractmethod
def step(self, verbose: bool = False): def step(self, verbose=False):
pass pass
@abstractmethod @abstractmethod
def run(self, verbose: bool = False): def run(self, verbose=False):
pass pass
def launch(self, verbose: bool = False): def launch(self, program: Program, verbose: bool = False):
entrypoint = self.mmu.find_entrypoint() if program not in self.mmu.programs:
print(FMT_ERROR + '[CPU] Cannot launch program that\'s not loaded!' + FMT_NONE)
if entrypoint is None: return
entrypoint = self.mmu.programs[0].entrypoint
if self.conf.verbosity > 0: if self.conf.verbosity > 0:
print( print(FMT_CPU + "[CPU] Started running from {}".format(
FMT_CPU self.mmu.translate_address(program.entrypoint)
+ "[CPU] Started running from {}".format( ) + FMT_NONE)
self.mmu.translate_address(entrypoint) print(program)
) self.pc = program.entrypoint
+ FMT_NONE
)
self.pc = entrypoint
self.run(verbose) self.run(verbose)
@classmethod @classmethod
@abstractmethod @abstractmethod
def get_loaders(cls) -> Iterable[Type[ProgramLoader]]: def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]:
pass pass
def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]: def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]:

@ -14,86 +14,63 @@ if typing.TYPE_CHECKING:
class RiscemuBaseException(BaseException): class RiscemuBaseException(BaseException):
@abstractmethod @abstractmethod
def message(self) -> str: def message(self):
raise NotImplemented pass
def print_stacktrace(self): def print_stacktrace(self):
import traceback import traceback
traceback.print_exception(type(self), self, self.__traceback__) traceback.print_exception(type(self), self, self.__traceback__)
# Parsing exceptions: # Parsing exceptions:
class ParseException(RiscemuBaseException): class ParseException(RiscemuBaseException):
def __init__(self, msg: str, data=None): def __init__(self, msg, data=None):
super().__init__(msg, data) super().__init__(msg, data)
self.msg = msg self.msg = msg
self.data = data self.data = data
def message(self): def message(self):
return ( return FMT_PARSE + "{}(\"{}\", data={})".format(self.__class__.__name__, self.msg, self.data) + FMT_NONE
FMT_PARSE
+ '{}("{}", data={})'.format(self.__class__.__name__, self.msg, self.data)
+ FMT_NONE
)
def ASSERT_EQ(a1, a2): def ASSERT_EQ(a1, a2):
if a1 != a2: if a1 != a2:
raise ParseException( raise ParseException("ASSERTION_FAILED: Expected elements to be equal!", (a1, a2))
"ASSERTION_FAILED: Expected elements to be equal!", (a1, a2)
)
def ASSERT_LEN(a1, size): def ASSERT_LEN(a1, size):
if len(a1) != size: if len(a1) != size:
raise ParseException( raise ParseException("ASSERTION_FAILED: Expected {} to be of length {}".format(a1, size), (a1, size))
"ASSERTION_FAILED: Expected {} to be of length {}".format(a1, size),
(a1, size),
)
def ASSERT_NOT_NULL(a1): def ASSERT_NOT_NULL(a1):
if a1 is None: if a1 is None:
raise ParseException( raise ParseException("ASSERTION_FAILED: Expected {} to be non null".format(a1), (a1,))
"ASSERTION_FAILED: Expected {} to be non null".format(a1), (a1,)
)
def ASSERT_NOT_IN(a1, a2): def ASSERT_NOT_IN(a1, a2):
if a1 in a2: if a1 in a2:
raise ParseException( raise ParseException("ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2))
"ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2)
)
def ASSERT_IN(a1, a2): def ASSERT_IN(a1, a2):
if a1 not in a2: if a1 not in a2:
raise ParseException( raise ParseException("ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2))
"ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2)
)
class LinkerException(RiscemuBaseException): class LinkerException(RiscemuBaseException):
def __init__(self, msg: str, data): def __init__(self, msg, data):
self.msg = msg self.msg = msg
self.data = data self.data = data
def message(self): def message(self):
return ( return FMT_PARSE + "{}(\"{}\", data={})".format(self.__class__.__name__, self.msg, self.data) + FMT_NONE
FMT_PARSE
+ '{}("{}", data={})'.format(self.__class__.__name__, self.msg, self.data)
+ FMT_NONE
)
# MMU Exceptions # MMU Exceptions
class MemoryAccessException(RiscemuBaseException): class MemoryAccessException(RiscemuBaseException):
def __init__(self, msg: str, addr, size, op): def __init__(self, msg, addr, size, op):
super(MemoryAccessException, self).__init__() super(MemoryAccessException, self).__init__()
self.msg = msg self.msg = msg
self.addr = addr self.addr = addr
@ -101,13 +78,13 @@ class MemoryAccessException(RiscemuBaseException):
self.op = op self.op = op
def message(self): def message(self):
return ( return FMT_MEM + "{}(During {} at 0x{:08x} of size {}: {})".format(
FMT_MEM self.__class__.__name__,
+ "{}(During {} at 0x{:08x} of size {}: {})".format( self.op,
self.__class__.__name__, self.op, self.addr, self.size, self.msg self.addr,
) self.size,
+ FMT_NONE self.msg
) ) + FMT_NONE
class OutOfMemoryException(RiscemuBaseException): class OutOfMemoryException(RiscemuBaseException):
@ -115,13 +92,10 @@ class OutOfMemoryException(RiscemuBaseException):
self.action = action self.action = action
def message(self): def message(self):
return ( return FMT_MEM + '{}(Ran out of memory during {})'.format(
FMT_MEM self.__class__.__name__,
+ "{}(Ran out of memory during {})".format( self.action
self.__class__.__name__, self.action ) + FMT_NONE
)
+ FMT_NONE
)
class InvalidAllocationException(RiscemuBaseException): class InvalidAllocationException(RiscemuBaseException):
@ -132,29 +106,28 @@ class InvalidAllocationException(RiscemuBaseException):
self.flags = flags self.flags = flags
def message(self): def message(self):
return FMT_MEM + "{}[{}](name={}, size={}, flags={})".format( return FMT_MEM + '{}[{}](name={}, size={}, flags={})'.format(
self.__class__.__name__, self.msg, self.name, self.size, self.flags self.__class__.__name__,
self.msg,
self.name,
self.size,
self.flags
) )
# CPU Exceptions # CPU Exceptions
class UnimplementedInstruction(RiscemuBaseException): class UnimplementedInstruction(RiscemuBaseException):
def __init__(self, ins: "Instruction", context=None): def __init__(self, ins: 'Instruction', context = None):
self.ins = ins self.ins = ins
self.context = context self.context = context
def message(self): def message(self):
return ( return FMT_CPU + "{}({}{})".format(
FMT_CPU self.__class__.__name__,
+ "{}({}{})".format( repr(self.ins),
self.__class__.__name__, ', context={}'.format(self.context) if self.context is not None else ''
repr(self.ins), ) + FMT_NONE
", context={}".format(self.context) if self.context is not None else "",
)
+ FMT_NONE
)
class InvalidRegisterException(RiscemuBaseException): class InvalidRegisterException(RiscemuBaseException):
@ -162,11 +135,10 @@ class InvalidRegisterException(RiscemuBaseException):
self.reg = reg self.reg = reg
def message(self): def message(self):
return ( return FMT_CPU + "{}(Invalid register {})".format(
FMT_CPU self.__class__.__name__,
+ "{}(Invalid register {})".format(self.__class__.__name__, self.reg) self.reg
+ FMT_NONE ) + FMT_NONE
)
class InvalidSyscallException(RiscemuBaseException): class InvalidSyscallException(RiscemuBaseException):
@ -174,11 +146,10 @@ class InvalidSyscallException(RiscemuBaseException):
self.scall = scall self.scall = scall
def message(self): def message(self):
return ( return FMT_SYSCALL + "{}(Invalid syscall: {})".format(
FMT_SYSCALL self.__class__.__name__,
+ "{}(Invalid syscall: {})".format(self.__class__.__name__, self.scall) self.scall
+ FMT_NONE ) + FMT_NONE
)
def INS_NOT_IMPLEMENTED(ins): def INS_NOT_IMPLEMENTED(ins):
@ -191,10 +162,13 @@ class NumberFormatException(RiscemuBaseException):
self.msg = msg self.msg = msg
def message(self): def message(self):
return "{}({})".format(self.__class__.__name__, self.msg) return "{}({})".format(
self.__class__.__name__,
self.msg
)
# this exception is not printed and simply signals that an interactive debugging session is # this exception is not printed and simply signals that an interactive debugging session is
class LaunchDebuggerException(RiscemuBaseException): class LaunchDebuggerException(RiscemuBaseException):
def message(self) -> str: def message(self):
return "" return ""

@ -8,5 +8,6 @@ class MemoryFlags:
def __repr__(self): def __repr__(self):
return "r{}{}".format( return "r{}{}".format(
"-" if self.read_only else "w", "x" if self.executable else "-" '-' if self.read_only else 'w',
'x' if self.executable else '-'
) )

@ -1,198 +0,0 @@
import struct
from ctypes import c_float
from typing import Union, Any
bytes_t = bytes
class Float32:
__slots__ = ("_val",)
_val: c_float
@property
def value(self) -> float:
"""
The value represented by this float
"""
return self._val.value
@property
def bytes(self) -> bytes:
"""
The values bit representation (as a bytes object)
"""
return struct.pack("<f", self.value)
@property
def bits(self) -> int:
"""
The values bit representation as an int (for easy bit manipulation)
"""
return int.from_bytes(self.bytes, byteorder="little")
@classmethod
def from_bytes(cls, val: Union[int, bytes_t, bytearray]):
if isinstance(val, int):
val = struct.unpack("!f", struct.pack("!I", val))[0]
return Float32(val)
def __init__(
self, val: Union[float, c_float, "Float32", bytes_t, bytearray, int] = 0
):
if isinstance(val, (float, int)):
self._val = c_float(val)
elif isinstance(val, c_float):
self._val = c_float(val.value)
elif isinstance(val, (bytes, bytearray)):
self._val = c_float(struct.unpack("<f", val)[0])
elif isinstance(val, Float32):
self._val = val._val
else:
raise ValueError(
"Unsupported value passed to Float32: {} ({})".format(
repr(val), type(val)
)
)
def __add__(self, other: Union["Float32", float]):
if isinstance(other, Float32):
other = other.value
return self.__class__(self.value + other)
def __sub__(self, other: Union["Float32", float]):
if isinstance(other, Float32):
other = other.value
return self.__class__(self.value - other)
def __mul__(self, other: Union["Float32", float]):
if isinstance(other, Float32):
other = other.value
return self.__class__(self.value * other)
def __truediv__(self, other: Any):
return self // other
def __floordiv__(self, other: Any):
if isinstance(other, Float32):
other = other.value
return self.__class__(self.value // other)
def __mod__(self, other: Union["Float32", float]):
if isinstance(other, Float32):
other = other.value
return self.__class__(self.value % other)
def __eq__(self, other: object) -> bool:
if isinstance(other, (float, int)):
return self.value == other
elif isinstance(other, Float32):
return self.value == other.value
return False
def __neg__(self):
return self.__class__(-self.value)
def __abs__(self):
return self.__class__(abs(self.value))
def __bytes__(self) -> bytes_t:
return self.bytes
def __repr__(self):
return "{}({})".format(self.__class__.__name__, self.value)
def __str__(self):
return str(self.value)
def __format__(self, format_spec: str):
return self.value.__format__(format_spec)
def __hash__(self):
return hash(self.value)
def __gt__(self, other: Any):
if isinstance(other, Float32):
other = other.value
return self.value > other
def __lt__(self, other: Any):
if isinstance(other, Float32):
other = other.value
return self.value < other
def __le__(self, other: Any):
if isinstance(other, Float32):
other = other.value
return self.value <= other
def __ge__(self, other: Any):
if isinstance(other, Float32):
other = other.value
return self.value >= other
def __bool__(self):
return bool(self.value)
def __pow__(self, power, modulo=None):
if modulo is not None:
raise ValueError("Float32 pow with modulo unsupported")
return self.__class__(self.value**power)
# right handed binary operators
def __radd__(self, other: Any):
return self + other
def __rsub__(self, other: Any):
return self.__class__(other) - self
def __rmul__(self, other: Any):
return self * other
def __rtruediv__(self, other: Any):
return self.__class__(other) // self
def __rfloordiv__(self, other: Any):
return self.__class__(other) // self
def __rmod__(self, other: Any):
return self.__class__(other) % self
def __rand__(self, other: Any):
return self.__class__(other) & self
def __ror__(self, other: Any):
return self.__class__(other) | self
def __rxor__(self, other: Any):
return self.__class__(other) ^ self
# bytewise operators:
def __and__(self, other: Union["Float32", float, int]):
if isinstance(other, float):
other = Float32(other)
if isinstance(other, Float32):
other = other.bits
return self.from_bytes(self.bits & other)
def __or__(self, other: Union["Float32", float]):
if isinstance(other, float):
other = Float32(other)
if isinstance(other, Float32):
other = other.bits
return self.from_bytes(self.bits | other)
def __xor__(self, other: Union["Float32", float]):
if isinstance(other, float):
other = Float32(other)
if isinstance(other, Float32):
other = other.bits
return self.from_bytes(self.bits ^ other)
def __lshift__(self, other: int):
return self.from_bytes(self.bits << other)
def __rshift__(self, other: int):
return self.from_bytes(self.bits >> other)

@ -18,8 +18,8 @@ class InstructionContext:
numbered_labels: Dict[str, List[T_RelativeAddress]] numbered_labels: Dict[str, List[T_RelativeAddress]]
""" """
This dictionary maps numbered labels (which can occur multiple times) to a list of (block-relative) addresses where This dictionary maps numbered labels (which can occur multiple times) to a list of (block-relative) addresses where
the label was placed the label was placed
""" """
global_symbol_dict: Dict[str, T_AbsoluteAddress] global_symbol_dict: Dict[str, T_AbsoluteAddress]
@ -33,32 +33,21 @@ class InstructionContext:
self.base_address = 0 self.base_address = 0
self.global_symbol_dict = dict() self.global_symbol_dict = dict()
def resolve_label( def resolve_label(self, symbol: str, address_at: Optional[T_RelativeAddress] = None) -> Optional[T_AbsoluteAddress]:
self, symbol: str, address_at: Optional[T_RelativeAddress] = None
) -> Optional[T_AbsoluteAddress]:
if NUMBER_SYMBOL_PATTERN.match(symbol): if NUMBER_SYMBOL_PATTERN.match(symbol):
if address_at is None: if address_at is None:
raise ParseException( raise ParseException("Cannot resolve relative symbol {} without an address!".format(symbol))
"Cannot resolve relative symbol {} without an address!".format(
symbol
)
)
direction = symbol[-1] direction = symbol[-1]
values = self.numbered_labels.get(symbol[:-1], []) values = self.numbered_labels.get(symbol[:-1], [])
if direction == "b": if direction == 'b':
return max( return max((addr + self.base_address for addr in values if addr < address_at), default=None)
(addr + self.base_address for addr in values if addr < address_at),
default=None,
)
else: else:
return min( return min((addr + self.base_address for addr in values if addr > address_at), default=None)
(addr + self.base_address for addr in values if addr > address_at),
default=None,
)
else: else:
# if it's not a local symbol, try the globals # if it's not a local symbol, try the globals
if symbol not in self.labels: if symbol not in self.labels:
return self.global_symbol_dict.get(symbol, None) return self.global_symbol_dict.get(symbol, None)
# otherwise return the local symbol # otherwise return the local symbol
return self.labels.get(symbol, None) return self.labels.get(symbol, None)

@ -1,24 +1,11 @@
from typing import List from typing import List
from . import ( from . import MemorySection, Instruction, InstructionContext, MemoryFlags, T_RelativeAddress
MemorySection,
Instruction,
InstructionContext,
MemoryFlags,
T_RelativeAddress,
)
from .exceptions import MemoryAccessException from .exceptions import MemoryAccessException
class InstructionMemorySection(MemorySection): class InstructionMemorySection(MemorySection):
def __init__( def __init__(self, instructions: List[Instruction], name: str, context: InstructionContext, owner: str, base: int = 0):
self,
instructions: List[Instruction],
name: str,
context: InstructionContext,
owner: str,
base: int = 0,
):
self.name = name self.name = name
self.base = base self.base = base
self.context = context self.context = context
@ -28,27 +15,13 @@ class InstructionMemorySection(MemorySection):
self.owner = owner self.owner = owner
def read(self, offset: T_RelativeAddress, size: int) -> bytearray: def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
raise MemoryAccessException( raise MemoryAccessException("Cannot read raw bytes from instruction section", self.base + offset, size, 'read')
"Cannot read raw bytes from instruction section",
self.base + offset,
size,
"read",
)
def write(self, offset: T_RelativeAddress, size: int, data: bytearray): def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
raise MemoryAccessException( raise MemoryAccessException("Cannot write raw bytes to instruction section", self.base + offset, size, 'write')
"Cannot write raw bytes to instruction section",
self.base + offset,
size,
"write",
)
def read_ins(self, offset: T_RelativeAddress) -> Instruction: def read_ins(self, offset: T_RelativeAddress) -> Instruction:
if offset % 4 != 0: if offset % 4 != 0:
raise MemoryAccessException( raise MemoryAccessException("Unaligned instruction fetch!", self.base + offset, 4, 'instruction fetch')
"Unaligned instruction fetch!",
self.base + offset,
4,
"instruction fetch",
)
return self.instructions[offset // 4] return self.instructions[offset // 4]

@ -1,4 +1,4 @@
from typing import Any, Union from typing import Union
from ctypes import c_int32, c_uint32 from ctypes import c_int32, c_uint32
@ -11,91 +11,82 @@ class Int32:
You can use it just like you would any other integer, just be careful when passing it You can use it just like you would any other integer, just be careful when passing it
to functions which actually expect an integer and not a Int32. to functions which actually expect an integer and not a Int32.
""" """
_type = c_int32 _type = c_int32
__slots__ = ("_val",) __slots__ = ('_val',)
def __init__( def __init__(self, val: Union[int, c_int32, c_uint32, 'Int32', bytes, bytearray] = 0):
self, val: Union[int, c_int32, c_uint32, "Int32", bytes, bytearray, bool] = 0
):
if isinstance(val, (bytes, bytearray)): if isinstance(val, (bytes, bytearray)):
signed = len(val) == 4 and self._type == c_int32 signed = len(val) == 4 and self._type == c_int32
self._val = self.__class__._type( self._val = self.__class__._type(int.from_bytes(val, 'little', signed=signed))
int.from_bytes(val, "little", signed=signed)
)
elif isinstance(val, self.__class__._type): elif isinstance(val, self.__class__._type):
self._val = val self._val = val
elif isinstance(val, (c_uint32, c_int32, Int32)): elif isinstance(val, (c_uint32, c_int32, Int32)):
self._val = self.__class__._type(val.value) self._val = self.__class__._type(val.value)
elif isinstance(val, (int, bool)): elif isinstance(val, int):
self._val = self.__class__._type(val) self._val = self.__class__._type(val)
else: else:
raise RuntimeError( raise RuntimeError(
"Unknonw {} input type: {} ({})".format( "Unknonw {} input type: {} ({})".format(self.__class__.__name__, type(val), val)
self.__class__.__name__, type(val), val
)
) )
def __add__(self, other: Union["Int32", int]): def __add__(self, other: Union['Int32', int]):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.__class__(self._val.value + other) return self.__class__(self._val.value + other)
def __sub__(self, other: Union["Int32", int]): def __sub__(self, other: Union['Int32', int]):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.__class__(self._val.value - other) return self.__class__(self._val.value - other)
def __mul__(self, other: Union["Int32", int]): def __mul__(self, other: Union['Int32', int]):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.__class__(self._val.value * other) return self.__class__(self._val.value * other)
def __truediv__(self, other: Any): def __truediv__(self, other):
return self // other return self // other
def __floordiv__(self, other: Any): def __floordiv__(self, other):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.__class__(self.value // other) return self.__class__(self.value // other)
def __mod__(self, other: Union["Int32", int]): def __mod__(self, other: Union['Int32', int]):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.__class__(self._val.value % other) return self.__class__(self._val.value % other)
def __and__(self, other: Union["Int32", int]): def __and__(self, other: Union['Int32', int]):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.__class__(self._val.value & other) return self.__class__(self._val.value & other)
def __or__(self, other: Union["Int32", int]): def __or__(self, other: Union['Int32', int]):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.__class__(self._val.value | other) return self.__class__(self._val.value | other)
def __xor__(self, other: Union["Int32", int]): def __xor__(self, other: Union['Int32', int]):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.__class__(self._val.value ^ other) return self.__class__(self._val.value ^ other)
def __lshift__(self, other: Union["Int32", int]): def __lshift__(self, other: Union['Int32', int]):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.__class__(self.value << other) return self.__class__(self.value << other)
def __rshift__(self, other: Union["Int32", int]): def __rshift__(self, other: Union['Int32', int]):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.__class__(self.value >> other) return self.__class__(self.value >> other)
def __eq__(self, other: object) -> bool: def __eq__(self, other: Union['Int32', int]):
if isinstance(other, int): if isinstance(other, Int32):
return self.value == other other = other.value
elif isinstance(other, Int32): return self.value == other
return self.value == other.value
return False
def __neg__(self): def __neg__(self):
return self.__class__(-self._val.value) return self.__class__(-self._val.value)
@ -107,33 +98,33 @@ class Int32:
return self.to_bytes(4) return self.to_bytes(4)
def __repr__(self): def __repr__(self):
return "{}({})".format(self.__class__.__name__, self.value) return '{}({})'.format(self.__class__.__name__, self.value)
def __str__(self): def __str__(self):
return str(self.value) return str(self.value)
def __format__(self, format_spec: str): def __format__(self, format_spec):
return self.value.__format__(format_spec) return self.value.__format__(format_spec)
def __hash__(self): def __hash__(self):
return hash(self.value) return hash(self.value)
def __gt__(self, other: Any): def __gt__(self, other):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.value > other return self.value > other
def __lt__(self, other: Any): def __lt__(self, other):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.value < other return self.value < other
def __le__(self, other: Any): def __le__(self, other):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.value <= other return self.value <= other
def __ge__(self, other: Any): def __ge__(self, other):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.value >= other return self.value >= other
@ -141,38 +132,38 @@ class Int32:
def __bool__(self): def __bool__(self):
return bool(self.value) return bool(self.value)
def __cmp__(self, other: Any): def __cmp__(self, other):
if isinstance(other, Int32): if isinstance(other, Int32):
other = other.value other = other.value
return self.value.__cmp__(other) return self.value.__cmp__(other)
# right handed binary operators # right handed binary operators
def __radd__(self, other: Any): def __radd__(self, other):
return self + other return self + other
def __rsub__(self, other: Any): def __rsub__(self, other):
return self.__class__(other) - self return self.__class__(other) - self
def __rmul__(self, other: Any): def __rmul__(self, other):
return self * other return self * other
def __rtruediv__(self, other: Any): def __rtruediv__(self, other):
return self.__class__(other) // self return self.__class__(other) // self
def __rfloordiv__(self, other: Any): def __rfloordiv__(self, other):
return self.__class__(other) // self return self.__class__(other) // self
def __rmod__(self, other: Any): def __rmod__(self, other):
return self.__class__(other) % self return self.__class__(other) % self
def __rand__(self, other: Any): def __rand__(self, other):
return self.__class__(other) & self return self.__class__(other) & self
def __ror__(self, other: Any): def __ror__(self, other):
return self.__class__(other) | self return self.__class__(other) | self
def __rxor__(self, other: Any): def __rxor__(self, other):
return self.__class__(other) ^ self return self.__class__(other) ^ self
@property @property
@ -183,7 +174,7 @@ class Int32:
""" """
return self._val.value return self._val.value
def unsigned(self) -> "UInt32": def unsigned(self) -> 'UInt32':
""" """
Convert to an unsigned representation. See :class:Uint32 Convert to an unsigned representation. See :class:Uint32
:return: :return:
@ -197,9 +188,9 @@ class Int32:
:param bytes: The length of the bytearray :param bytes: The length of the bytearray
:return: A little-endian representation of the contained integer :return: A little-endian representation of the contained integer
""" """
return bytearray(self.unsigned_value.to_bytes(4, "little"))[0:bytes] return bytearray(self.unsigned_value.to_bytes(4, 'little'))[0:bytes]
def signed(self) -> "Int32": def signed(self) -> 'Int32':
""" """
Convert to a signed representation. See :class:Int32 Convert to a signed representation. See :class:Int32
:return: :return:
@ -216,7 +207,7 @@ class Int32:
""" """
return c_uint32(self.value).value return c_uint32(self.value).value
def shift_right_logical(self, ammount: Union["Int32", int]) -> "Int32": def shift_right_logical(self, ammount: Union['Int32', int]) -> 'Int32':
""" """
This function implements logical right shifts, meaning that the sign bit is shifted as well. This function implements logical right shifts, meaning that the sign bit is shifted as well.
@ -246,12 +237,12 @@ class Int32:
:return: An instance of Int32, holding the sign-extended value :return: An instance of Int32, holding the sign-extended value
""" """
if isinstance(data, (bytes, bytearray)): if isinstance(data, (bytes, bytearray)):
data = int.from_bytes(data, "little") data = int.from_bytes(data, 'little')
sign = data >> (bits - 1) sign = data >> (bits - 1)
if sign > 1: if sign > 1:
print("overflow in Int32.sext!") print("overflow in Int32.sext!")
if sign: if sign:
data = (data & (2 ** (bits - 1) - 1)) - 2 ** (bits - 1) data = (data & (2 ** (bits - 1) - 1)) - 2**(bits-1)
return cls(data) return cls(data)
@ -259,10 +250,9 @@ class UInt32(Int32):
""" """
An unsigned version of :class:Int32. An unsigned version of :class:Int32.
""" """
_type = c_uint32 _type = c_uint32
def unsigned(self) -> "UInt32": def unsigned(self) -> 'UInt32':
""" """
Return a new instance representing the same bytes, but signed Return a new instance representing the same bytes, but signed
:return: :return:
@ -273,13 +263,11 @@ class UInt32(Int32):
def unsigned_value(self) -> int: def unsigned_value(self) -> int:
return self._val.value return self._val.value
def shift_right_logical(self, ammount: Union["Int32", int]) -> "UInt32": def shift_right_logical(self, ammount: Union['Int32', int]) -> 'UInt32':
""" """
see :meth:`Int32.shift_right_logical <Int32.shift_right_logical>` see :meth:`Int32.shift_right_logical <Int32.shift_right_logical>`
:param ammount: Number of positions to shift :param ammount: Number of positions to shift
:return: A new Int32 object representing the shifted value (keeps the signed-ness of the source) :return: A new Int32 object representing the shifted value (keeps the signed-ness of the source)
""" """
if isinstance(ammount, Int32): return self >> ammount
ammount = ammount.value
return UInt32(self.value >> ammount)

@ -4,14 +4,7 @@ from typing import Optional
from ..colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_ERROR from ..colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_ERROR
from ..helpers import format_bytes from ..helpers import format_bytes
from . import ( from . import MemoryFlags, T_AbsoluteAddress, InstructionContext, T_RelativeAddress, Instruction, Int32
MemoryFlags,
T_AbsoluteAddress,
InstructionContext,
T_RelativeAddress,
Instruction,
Int32,
)
@dataclass @dataclass
@ -39,16 +32,8 @@ class MemorySection(ABC):
def read_ins(self, offset: T_RelativeAddress) -> Instruction: def read_ins(self, offset: T_RelativeAddress) -> Instruction:
pass pass
def dump( def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress] = None, fmt: str = None,
self, bytes_per_row: int = None, rows: int = 10, group: int = None, highlight: int = None):
start: T_RelativeAddress,
end: Optional[T_RelativeAddress] = None,
fmt: Optional[str] = None,
bytes_per_row: Optional[int] = None,
rows: int = 10,
group: Optional[int] = None,
highlight: Optional[int] = None,
):
""" """
Dump the section. If no end is given, the rows around start are printed and start is highlighted. Dump the section. If no end is given, the rows around start are printed and start is highlighted.
@ -69,11 +54,11 @@ class MemorySection(ABC):
if fmt is None: if fmt is None:
if self.flags.executable and self.flags.read_only: if self.flags.executable and self.flags.read_only:
bytes_per_row = 4 bytes_per_row = 4
fmt = "asm" fmt = 'asm'
else: else:
fmt = "hex" fmt = 'hex'
if fmt == "char": if fmt == 'char':
if bytes_per_row is None: if bytes_per_row is None:
bytes_per_row = 4 bytes_per_row = 4
if group is None: if group is None:
@ -85,82 +70,50 @@ class MemorySection(ABC):
if bytes_per_row is None: if bytes_per_row is None:
bytes_per_row = 4 bytes_per_row = 4
if fmt not in ("asm", "hex", "int", "char"): if fmt not in ('asm', 'hex', 'int', 'char'):
print( print(FMT_ERROR + '[MemorySection] Unknown format {}, known formats are {}'.format(
FMT_ERROR fmt, ", ".join(('asm', 'hex', 'int', 'char'))
+ "[MemorySection] Unknown format {}, known formats are {}".format( ) + FMT_NONE)
fmt, ", ".join(("asm", "hex", "int", "char"))
)
+ FMT_NONE
)
if end is None: if end is None:
end = min(start + (bytes_per_row * (rows // 2)), self.size) end = min(start + (bytes_per_row * (rows // 2)), self.size)
highlight = start highlight = start
start = max(0, start - (bytes_per_row * (rows // 2))) start = max(0, start - (bytes_per_row * (rows // 2)))
if fmt == "asm": if fmt == 'asm':
print( print(FMT_MEM + "{}, viewing {} instructions:".format(
FMT_MEM self, (end - start) // 4
+ "{}, viewing {} instructions:".format(self, (end - start) // 4) ) + FMT_NONE)
+ FMT_NONE
)
for addr in range(start, end, 4): for addr in range(start, end, 4):
if addr == highlight: if addr == highlight:
print(FMT_UNDERLINE + FMT_ORANGE, end="") print(FMT_UNDERLINE + FMT_ORANGE, end='')
print( print("0x{:04x}: {}{}".format(
"0x{:04x}: {}{}".format( self.base + addr, self.read_ins(addr), FMT_NONE
self.base + addr, self.read_ins(addr), FMT_NONE ))
)
)
else: else:
print( print(FMT_MEM + "{}, viewing {} bytes:".format(
FMT_MEM + "{}, viewing {} bytes:".format(self, (end - start)) + FMT_NONE self, (end - start)
) ) + FMT_NONE)
aligned_end = ( aligned_end = end - (end % bytes_per_row) if end % bytes_per_row != 0 else end
end - (end % bytes_per_row) if end % bytes_per_row != 0 else end
)
for addr in range(start, aligned_end, bytes_per_row): for addr in range(start, aligned_end, bytes_per_row):
hi_ind = (highlight - addr) // group if highlight is not None else -1 hi_ind = (highlight - addr) // group if highlight is not None else -1
print( print("0x{:04x}: {}{}".format(
"0x{:04x}: {}{}".format( self.base + addr, format_bytes(self.read(addr, bytes_per_row), fmt, group, hi_ind), FMT_NONE
self.base + addr, ))
format_bytes(
self.read(addr, bytes_per_row), fmt, group, hi_ind
),
FMT_NONE,
)
)
if aligned_end != end: if aligned_end != end:
hi_ind = ( hi_ind = (highlight - aligned_end) // group if highlight is not None else -1
(highlight - aligned_end) // group if highlight is not None else -1 print("0x{:04x}: {}{}".format(
) self.base + aligned_end, format_bytes(
print( self.read(aligned_end, end % bytes_per_row), fmt, group, hi_ind
"0x{:04x}: {}{}".format( ), FMT_NONE
self.base + aligned_end, ))
format_bytes(
self.read(aligned_end, end % bytes_per_row), def dump_all(self, *args, **kwargs):
fmt, self.dump(0, self.size, *args, **kwargs)
group,
hi_ind,
),
FMT_NONE,
)
)
def dump_all(
self,
fmt: Optional[str] = None,
bytes_per_row: Optional[int] = None,
rows: int = 10,
group: Optional[int] = None,
highlight: Optional[int] = None,
):
self.dump(0, self.size, fmt, bytes_per_row, rows, group, highlight)
def __repr__(self): def __repr__(self):
return "{}[{}] at 0x{:08X} (size={}bytes, flags={}, owner={})".format( return "{}[{}] at 0x{:08X} (size={}bytes, flags={}, owner={})".format(
@ -169,5 +122,5 @@ class MemorySection(ABC):
self.base, self.base,
self.size, self.size,
self.flags, self.flags,
self.owner, self.owner
) )

@ -14,7 +14,6 @@ class Program:
the offset in the program, and everything will be taken care of for you. the offset in the program, and everything will be taken care of for you.
""" """
name: str name: str
context: InstructionContext context: InstructionContext
global_labels: Set[str] global_labels: Set[str]
@ -45,13 +44,9 @@ class Program:
if self.base is not None: if self.base is not None:
if sec.base < self.base: if sec.base < self.base:
print( print(
FMT_RED FMT_RED + FMT_BOLD + "WARNING: memory section {} in {} is placed before program base (0x{:x})".format(
+ FMT_BOLD
+ "WARNING: memory section {} in {} is placed before program base (0x{:x})".format(
sec, self.name, self.base sec, self.name, self.base
) ) + FMT_NONE)
+ FMT_NONE
)
self.sections.append(sec) self.sections.append(sec)
# keep section list ordered # keep section list ordered
@ -59,21 +54,18 @@ class Program:
def __repr__(self): def __repr__(self):
return "{}(name={},sections={},base={})".format( return "{}(name={},sections={},base={})".format(
self.__class__.__name__, self.__class__.__name__, self.name, self.global_labels,
self.name, [s.name for s in self.sections], self.base
self.global_labels,
[s.name for s in self.sections],
self.base,
) )
@property @property
def entrypoint(self): def entrypoint(self):
if "_start" in self.context.labels: if '_start' in self.context.labels:
return self.context.labels.get("_start") return self.context.labels.get('_start')
if "main" in self.context.labels: if 'main' in self.context.labels:
return self.context.labels.get("main") return self.context.labels.get('main')
for sec in self.sections: for sec in self.sections:
if get_section_base_name(sec.name) == ".text" and sec.flags.executable: if get_section_base_name(sec.name) == '.text' and sec.flags.executable:
return sec.base return sec.base
def loaded_trigger(self, at_addr: T_AbsoluteAddress): def loaded_trigger(self, at_addr: T_AbsoluteAddress):
@ -89,17 +81,12 @@ class Program:
""" """
if self.is_loaded: if self.is_loaded:
if at_addr != self.base: if at_addr != self.base:
raise RuntimeError( raise RuntimeError("Program loaded twice at different addresses! This will probably break things!")
"Program loaded twice at different addresses! This will probably break things!"
)
return return
if self.base is not None and self.base != at_addr: if self.base is not None and self.base != at_addr:
print( print(FMT_MEM + 'WARNING: Program loaded at different address then expected! (loaded at {}, '
FMT_MEM 'but expects to be loaded at {})'.format(at_addr, self.base) + FMT_NONE)
+ "WARNING: Program loaded at different address then expected! (loaded at {}, "
"but expects to be loaded at {})".format(at_addr, self.base) + FMT_NONE
)
# check if we are relocating # check if we are relocating
if self.base != at_addr: if self.base != at_addr:

@ -39,7 +39,7 @@ class ProgramLoader(ABC):
pass pass
@classmethod @classmethod
def instantiate(cls, source_path: str, options: T_ParserOpts) -> "ProgramLoader": def instantiate(cls, source_path: str, options: T_ParserOpts) -> 'ProgramLoader':
""" """
Instantiate a loader for the given source file with the required arguments Instantiate a loader for the given source file with the required arguments

@ -5,13 +5,8 @@ from ..helpers import parse_numeric_argument
class SimpleInstruction(Instruction): class SimpleInstruction(Instruction):
def __init__( def __init__(self, name: str, args: Union[Tuple[()], Tuple[str], Tuple[str, str], Tuple[str, str, str]],
self, context: InstructionContext, addr: T_RelativeAddress):
name: str,
args: Union[Tuple[()], Tuple[str], Tuple[str, str], Tuple[str, str, str]],
context: InstructionContext,
addr: T_RelativeAddress,
):
self.context = context self.context = context
self.name = name self.name = name
self.args = args self.args = args
@ -28,3 +23,4 @@ class SimpleInstruction(Instruction):
def get_reg(self, num: int) -> str: def get_reg(self, num: int) -> str:
return self.args[num] return self.args[num]

@ -1,5 +1,4 @@
import setuptools import setuptools
from glob import glob
import riscemu import riscemu
@ -11,7 +10,7 @@ setuptools.setup(
version=riscemu.__version__, version=riscemu.__version__,
author=riscemu.__author__, author=riscemu.__author__,
author_email="pip@antonlydike.de", author_email="pip@antonlydike.de",
description="RISC-V userspace and machine mode emulator", description="RISC-V userspace and privileged emulator",
long_description=long_description, long_description=long_description,
long_description_content_type="text/markdown", long_description_content_type="text/markdown",
url="https://github.com/antonlydike/riscemu", url="https://github.com/antonlydike/riscemu",
@ -24,21 +23,9 @@ setuptools.setup(
"Operating System :: OS Independent", "Operating System :: OS Independent",
], ],
package_dir={"": "."}, package_dir={"": "."},
packages=[ packages=["riscemu", "riscemu.decoder", "riscemu.instructions", "riscemu.IO", "riscemu.priv", "riscemu.types"],
"riscemu", python_requires=">=3.6",
"riscemu.decoder", install_requires=[
"riscemu.instructions", "pyelftools~=0.27"
"riscemu.IO", ]
"riscemu.priv", )
"riscemu.types",
],
package_data={
"riscemu": ["libc/*.s", "py.typed"],
},
data_files=[
("libc", glob("libc/*.s")),
],
scripts=["riscemu/tools/riscemu"],
python_requires=">=3.8",
install_requires=["pyelftools~=0.27"],
)

@ -1,4 +1,4 @@
build build
source/riscemu*.rst source/riscemu*.rst
source/modules.rst source/modules.rst
source/help source/help

@ -18,34 +18,37 @@ import subprocess
# import sys # import sys
# sys.path.insert(0, os.path.abspath('.')) # sys.path.insert(0, os.path.abspath('.'))
if os.getenv("READTHEDOCS", False) and not os.path.exists("riscemu.rst"): if os.getenv('READTHEDOCS', False) and not os.path.exists('riscemu.rst'):
subprocess.check_call(["../../generate-docs.sh", "generate"]) subprocess.check_call(['../../generate-docs.sh', 'generate'])
# -- Project information ----------------------------------------------------- # -- Project information -----------------------------------------------------
project = "RiscEmu" project = 'RiscEmu'
copyright = "2022, Anton Lydike" copyright = '2022, Anton Lydike'
author = "Anton Lydike" author = 'Anton Lydike'
# The full version, including alpha/beta/rc tags # The full version, including alpha/beta/rc tags
release = "2.0.0a2" release = '2.0.0a2'
# -- General configuration --------------------------------------------------- # -- General configuration ---------------------------------------------------
# Add any Sphinx extension module names here, as strings. They can be # Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones. # ones.
extensions = ["sphinx.ext.autodoc", "recommonmark"] extensions = [
'sphinx.ext.autodoc',
'recommonmark'
]
# autodoc options # autodoc options
autodoc_default_options = { autodoc_default_options = {
"members": True, 'members': True,
"member-order": "bysource", 'member-order': 'bysource',
"special-members": "__init__", 'special-members': '__init__',
} }
# Add any paths that contain templates here, relative to this directory. # Add any paths that contain templates here, relative to this directory.
templates_path = ["_templates"] templates_path = ['_templates']
# List of patterns, relative to source directory, that match files and # List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files. # directories to ignore when looking for source files.
@ -55,10 +58,10 @@ templates_path = ["_templates"]
from recommonmark.parser import CommonMarkParser from recommonmark.parser import CommonMarkParser
# The suffix of source filenames. # The suffix of source filenames.
source_suffix = [".rst", ".md"] source_suffix = ['.rst', '.md']
source_parsers = { source_parsers = {
".md": CommonMarkParser, '.md': CommonMarkParser,
} }
# -- Options for HTML output ------------------------------------------------- # -- Options for HTML output -------------------------------------------------
@ -66,18 +69,18 @@ source_parsers = {
# The theme to use for HTML and HTML Help pages. See the documentation for # The theme to use for HTML and HTML Help pages. See the documentation for
# a list of builtin themes. # a list of builtin themes.
# #
html_theme = "alabaster" html_theme = 'alabaster'
pygments_style = "sphinx" pygments_style = 'sphinx'
# Add any paths that contain custom static files (such as style sheets) here, # Add any paths that contain custom static files (such as style sheets) here,
# relative to this directory. They are copied after the builtin static files, # relative to this directory. They are copied after the builtin static files,
# so a file named "default.css" will overwrite the builtin "default.css". # so a file named "default.css" will overwrite the builtin "default.css".
html_static_path = ["_static"] html_static_path = ['_static']
sys.path.insert(0, os.path.abspath("../../")) sys.path.insert(0, os.path.abspath('../../'))
if os.getenv("READTHEDOCS", False): if os.getenv('READTHEDOCS', False):
import sphinx_rtd_theme import sphinx_rtd_theme
extensions.append("sphinx_rtd_theme") extensions.append("sphinx_rtd_theme")

@ -59,4 +59,4 @@ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 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 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE. SOFTWARE.

@ -1,3 +1,3 @@
from .test_tokenizer import * from .test_tokenizer import *
from .test_helpers import * from .test_helpers import *
from .test_integers import * from .test_integers import *

@ -0,0 +1,73 @@
import contextlib
import os
from abc import abstractmethod
from tempfile import NamedTemporaryFile
from typing import Optional, Union, Tuple
from unittest import TestCase
from riscemu import CPU, UserModeCPU, InstructionSetDict, RunConfig
from riscemu.types import Program
class EndToEndTest(TestCase):
def __init__(self, cpu: Optional[CPU] = None):
super().__init__()
if cpu is None:
cpu = UserModeCPU(InstructionSetDict.values(), RunConfig())
self.cpu = cpu
@abstractmethod
def get_source(self) -> Tuple[str, Union[bytes, str, bytearray]]:
"""
This method returns the source code of the program
:return:
"""
pass
def test_run_program(self):
"""
Runs the program and verifies output
:return:
"""
with self.with_source_file() as names:
fname, orig_name = names
loader = self.cpu.get_best_loader_for(fname)
self.program = loader.instantiate(fname, loader.get_options([])).parse()
self._change_program_file_name(self.program, orig_name)
self.cpu.load_program(self.program)
self.after_program_load(self.program)
if isinstance(self.cpu, UserModeCPU):
self.cpu.setup_stack()
try:
self.cpu.launch(self.program)
except Exception as ex:
if self.is_exception_expected(ex):
pass
raise ex
@contextlib.contextmanager
def with_source_file(self):
name, content = self.get_source()
if isinstance(content, str):
f = NamedTemporaryFile('w', suffix=name, delete=False)
else:
f = NamedTemporaryFile('wb', suffix=name, delete=False)
f.write(content)
f.flush()
f.close()
try:
yield f.name, name
finally:
os.unlink(f.name)
def after_program_load(self, program):
pass
def is_exception_expected(self, ex: Exception) -> bool:
return False
def _change_program_file_name(self, program: Program, new_name: str):
program.name = new_name
for sec in program.sections:
sec.owner = new_name

@ -1,2 +0,0 @@
.lit_test_times.txt
Output

@ -1,24 +0,0 @@
// RUN: python3 -m riscemu -v -o ignore_exit_code %s | filecheck %s
.data
fibs: .space 1024
.text
main:
addi s1, zero, 0 // storage index
addi s2, zero, 1024 // last storage index
addi t0, zero, 1 // t0 = F_{i}
addi t1, zero, 1 // t1 = F_{i+1}
loop:
sw t0, fibs(s1) // save
add t2, t1, t0 // t2 = F_{i+2}
addi t0, t1, 0 // t0 = t1
addi t1, t2, 0 // t1 = t2
addi s1, s1, 4 // increment storage pointer
blt s1, s2, loop // loop as long as we did not reach array length
ebreak
// exit gracefully
add a0, zero, t2
addi a7, zero, 93
scall // exit with code fibs(n) & 2^32
// CHECK: [CPU] Program exited with code 1265227608

@ -1,15 +0,0 @@
// RUN: python3 -m riscemu -v %s | filecheck %s
.data
msg: .ascii "Hello world\n"
.text
addi a0, zero, 1 // print to stdout
addi a1, zero, msg // load msg address
addi a2, zero, 12 // write 12 bytes
addi a7, zero, SCALL_WRITE // write syscall code
scall
addi a0, zero, 0 // set exit code to 0
addi a7, zero, SCALL_EXIT // exit syscall code
scall
// CHECK: Hello world
// CHECK: [CPU] Program exited with code 0

@ -1,114 +0,0 @@
// RUN: python3 -m riscemu -v %s -o libc | filecheck %s
.data
data:
.byte 0x11, 0x22, 0x33, 0x44, 0x55, 0x66, 0x77, 0x88
.byte 0x99, 0xAA, 0xBB, 0xCC, 0xDD, 0xEE, 0xFF, 0x00
dest:
.byte 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB
.byte 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB, 0xAB
small_str:
.string "test"
.text
.globl main
main:
// test that strlen(data) == 15
addi sp, sp, -4
sw ra, 0(sp)
la a0, data
// call strlen(data)
jal strlen
li t0, 15
bne a0, t0, _fail
// now memcpy strlen(data)+1 bytes from data to dest
la a0, dest
la a1, data
li a2, 16
// call strncpy(dest, data, 16)
jal strncpy
la a1, dest
// fail because strncpy should return pointer to dest
bne a0, a1, _fail
// check that dest and data are the same
jal check_data_dest_is_same
la a0, dest
li a1, 0x11
li a2, 16
// test that memset(dest) workds
// call memset(dest, 0x11, 16)
jal memset
// check that all of dest is 0x11111111
li t1, 0x11111111
la a0, dest
lw t0, 0(a0)
bne t0, t1, _fail
lw t0, 1(a0)
bne t0, t1, _fail
lw t0, 2(a0)
bne t0, t1, _fail
lw t0, 3(a0)
bne t0, t1, _fail
// test memchr
// test memchr
la a0, data
li a1, 0x55
li a2, 16
// memchr(data, 0x55, 16)
jal memchr
la t0, data
addi t0, t0, 4
// fail if a0 != data+4
bne a0, t0, _fail
la a0, data
li a1, 0x12
li a2, 16
// memchr(data, 0x12, 16)
jal memchr
// check that result is NULL
bne a0, zero, _fail
// test strcpy
la a0, dest
la a1, small_str
// call strcpy(dest, small_str)
jal strcpy
la t0, dest
lw t1, 0(a0)
// ascii for "tset", as risc-v is little endian
li t2, 0x74736574
bne t1, t2, _fail
// return to exit() wrapper
lw ra, 0(sp)
addi sp, sp, 4
li a0, 0
ret
_fail:
ebreak
// fail the test run
li a0, -1
jal exit
check_data_dest_is_same:
la a0, data
la a1, dest
li a2, 4
1:
lw t0, 0(a0)
lw t1, 0(a1)
bne t0, t1, _fail
addi a0, a0, 4
addi a1, a1, 4
addi a2, a2, -1
blt zero, a2, 1b
ret
//CHECK: [CPU] Program exited with code 0

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save