Compare commits
49 Commits
float_supp
...
master
Author | SHA1 | Date |
---|---|---|
Anton Lydike | f8b979b08a | 1 year ago |
Anton Lydike | d102376212 | 1 year ago |
KGrykiel | 3b89387e0a | 1 year ago |
Sasha Lopoukhine | 07265f26c9 | 1 year ago |
Alban Dutilleul | 801b165e70 | 1 year ago |
Alban Dutilleul | 7e34855ad1 | 1 year ago |
Anton Lydike | 26d7f65cc1 | 1 year ago |
Anton Lydike | 2ec134f612 | 1 year ago |
Anton Lydike | be90879f86 | 1 year ago |
Sasha Lopoukhine | 5a23804ad8 | 1 year ago |
Sasha Lopoukhine | a217705d1f | 1 year ago |
Anton Lydike | d508e01a6b | 1 year ago |
Anton Lydike | 47a9b12263 | 1 year ago |
Anton Lydike | 1c2dad94e2 | 1 year ago |
Anton Lydike | 8ac4a56c08 | 1 year ago |
Anton Lydike | 41d17daeaf | 1 year ago |
Anton Lydike | 283bb1ae14 | 1 year ago |
Anton Lydike | 270c3e7090 | 1 year ago |
Anton Lydike | 86250157b7 | 1 year ago |
Anton Lydike | dfc0ed7862 | 1 year ago |
Anton Lydike | d9058a0ca0 | 1 year ago |
Anton Lydike | 1bb0770061 | 1 year ago |
Anton Lydike | 1bd754953c | 1 year ago |
Anton Lydike | a28bf834ac | 1 year ago |
Anton Lydike | 207cf918ef | 1 year ago |
Anton Lydike | c7e14a3b42 | 1 year ago |
Anton Lydike | 7a4972d48f | 1 year ago |
Sasha Lopoukhine | 25d059da09 | 2 years ago |
Anton Lydike | d6d3a18aa6 | 2 years ago |
Anton Lydike | 1ea5bb2edc | 2 years ago |
Anton Lydike | 2f6073b4df | 2 years ago |
Anton Lydike | 189dc63ceb | 2 years ago |
Anton Lydike | 0c37be3c4d | 2 years ago |
Anton Lydike | a51681811f | 2 years ago |
Anton Lydike | 87968d08d9 | 2 years ago |
Anton Lydike | 94d01a97d9 | 2 years ago |
Anton Lydike | 448b19c144 | 2 years ago |
Anton Lydike | 5515c7795c | 2 years ago |
Anton Lydike | e1fbe4f11d | 2 years ago |
Anton Lydike | dd77d1b387 | 2 years ago |
Anton Lydike | 1b26497e4c | 2 years ago |
Anton Lydike | b5ebe13528 | 2 years ago |
Anton Lydike | 51d23a1630 | 2 years ago |
Anton Lydike | 636e06f243 | 2 years ago |
Anton Lydike | 86063d64d7 | 2 years ago |
Anton Lydike | 5caf0d604d | 2 years ago |
Anton Lydike | 36e8c9c9ce | 2 years ago |
Emilien Bauer | f7e7c41034 | 2 years ago |
K-W-Li | 1d65b236f4 | 2 years ago |
@ -0,0 +1,2 @@
|
|||||||
|
# introduces black formatting
|
||||||
|
5515c7795cfd690d346aad10ce17b30acf914648
|
@ -0,0 +1,49 @@
|
|||||||
|
# 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/
|
@ -0,0 +1,34 @@
|
|||||||
|
# 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
|
@ -0,0 +1,98 @@
|
|||||||
|
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
|
@ -0,0 +1,22 @@
|
|||||||
|
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
|
@ -1,6 +1,8 @@
|
|||||||
venv
|
|
||||||
__pycache__
|
__pycache__
|
||||||
.mypy_cache
|
.mypy_cache
|
||||||
dist/
|
.pytest_cache
|
||||||
riscemu.egg-info
|
|
||||||
build/
|
/venv*
|
||||||
|
/dist
|
||||||
|
/riscemu.egg-info
|
||||||
|
/build
|
||||||
|
@ -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.8 (riscemu)" project-jdk-type="Python SDK" />
|
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (riscemu)" project-jdk-type="Python SDK" />
|
||||||
</project>
|
</project>
|
||||||
|
@ -0,0 +1,12 @@
|
|||||||
|
# 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
|
@ -0,0 +1,53 @@
|
|||||||
|
{
|
||||||
|
"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
|
||||||
|
}
|
@ -1,25 +1,23 @@
|
|||||||
; 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
|
||||||
|
@ -1,154 +0,0 @@
|
|||||||
; 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
|
|
||||||
|
|
||||||
|
|
@ -0,0 +1,44 @@
|
|||||||
|
// 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
|
@ -0,0 +1,11 @@
|
|||||||
|
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
|
@ -0,0 +1,20 @@
|
|||||||
|
.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
|
@ -0,0 +1,29 @@
|
|||||||
|
# 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!
|
@ -0,0 +1,14 @@
|
|||||||
|
// 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
|
@ -0,0 +1,178 @@
|
|||||||
|
// 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
|
@ -0,0 +1,154 @@
|
|||||||
|
// 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
|
@ -0,0 +1,5 @@
|
|||||||
|
black==23.3.0
|
||||||
|
pre-commit==3.2.2
|
||||||
|
pytest==7.3.1
|
||||||
|
filecheck==0.0.23
|
||||||
|
lit==16.0.2
|
@ -1,22 +1,30 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC
|
||||||
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__(self, name: str, flags: MemoryFlags, size: int, owner: str = 'system', base: int = 0):
|
def __init__(
|
||||||
|
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 self.base <= addr < self.base + self.size and \
|
return (
|
||||||
self.base <= addr + size <= self.base + self.size
|
self.base <= addr < self.base + self.size
|
||||||
|
and self.base <= addr + size <= self.base + self.size
|
||||||
|
)
|
||||||
|
|
||||||
def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress] = None, fmt: str = 'hex',
|
def dump(self, *args, **kwargs):
|
||||||
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
|
||||||
)
|
)
|
||||||
|
@ -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,6 +1,34 @@
|
|||||||
RISCV_REGS = [
|
RISCV_REGS = [
|
||||||
'zero', 'ra', 'sp', 'gp', 'tp', 't0', 't1', 't2',
|
"zero",
|
||||||
's0', 's1', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7',
|
"ra",
|
||||||
's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11',
|
"sp",
|
||||||
't3', 't4', 't5', 't6'
|
"gp",
|
||||||
|
"tp",
|
||||||
|
"t0",
|
||||||
|
"t1",
|
||||||
|
"t2",
|
||||||
|
"s0",
|
||||||
|
"s1",
|
||||||
|
"a0",
|
||||||
|
"a1",
|
||||||
|
"a2",
|
||||||
|
"a3",
|
||||||
|
"a4",
|
||||||
|
"a5",
|
||||||
|
"a6",
|
||||||
|
"a7",
|
||||||
|
"s2",
|
||||||
|
"s3",
|
||||||
|
"s4",
|
||||||
|
"s5",
|
||||||
|
"s6",
|
||||||
|
"s7",
|
||||||
|
"s8",
|
||||||
|
"s9",
|
||||||
|
"s10",
|
||||||
|
"s11",
|
||||||
|
"t3",
|
||||||
|
"t4",
|
||||||
|
"t5",
|
||||||
|
"t6",
|
||||||
]
|
]
|
||||||
|
@ -0,0 +1,33 @@
|
|||||||
|
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)
|
||||||
|
)
|
||||||
|
)
|
@ -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
|
||||||
"""
|
"""
|
||||||
|
@ -0,0 +1,284 @@
|
|||||||
|
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)
|
@ -0,0 +1,2 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
python3 -m riscemu "$@"
|
@ -1,29 +1,56 @@
|
|||||||
from . import MemorySection, InstructionContext, MemoryFlags, T_RelativeAddress, Instruction
|
from typing import Optional
|
||||||
|
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__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int = 0, flags: MemoryFlags = None):
|
def __init__(
|
||||||
self.name = name
|
self,
|
||||||
self.base = base
|
data: bytearray,
|
||||||
self.context = context
|
name: str,
|
||||||
self.size = len(data)
|
context: InstructionContext,
|
||||||
self.flags = flags if flags is not None else MemoryFlags(False, False)
|
owner: str,
|
||||||
|
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("Out of bounds access in {}".format(self), offset, size, 'read')
|
raise MemoryAccessException(
|
||||||
return self.data[offset:offset + size]
|
"Out of bounds access in {}".format(self), offset, size, "read"
|
||||||
|
)
|
||||||
|
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("Out of bounds access in {}".format(self), offset, size, 'write')
|
raise MemoryAccessException(
|
||||||
|
"Out of bounds access in {}".format(self), offset, size, "write"
|
||||||
|
)
|
||||||
if len(data[0:size]) != size:
|
if len(data[0:size]) != size:
|
||||||
raise MemoryAccessException("Invalid write parameter sizing", offset, size, 'write')
|
raise MemoryAccessException(
|
||||||
self.data[offset:offset + size] = data[0:size]
|
"Invalid write parameter sizing", offset, size, "write"
|
||||||
|
)
|
||||||
|
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("Tried reading instruction on non-executable section {}".format(self),
|
raise MemoryAccessException(
|
||||||
offset, 4, 'instruction fetch')
|
"Tried reading instruction on non-executable section {}".format(self),
|
||||||
|
offset,
|
||||||
|
4,
|
||||||
|
"instruction fetch",
|
||||||
|
)
|
||||||
|
@ -0,0 +1,198 @@
|
|||||||
|
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)
|
@ -1,4 +1,4 @@
|
|||||||
build
|
build
|
||||||
source/riscemu*.rst
|
source/riscemu*.rst
|
||||||
source/modules.rst
|
source/modules.rst
|
||||||
source/help
|
source/help
|
||||||
|
@ -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 *
|
||||||
|
@ -1,73 +0,0 @@
|
|||||||
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
|
|
@ -0,0 +1,2 @@
|
|||||||
|
.lit_test_times.txt
|
||||||
|
Output
|
@ -0,0 +1,24 @@
|
|||||||
|
// 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
|
@ -0,0 +1,15 @@
|
|||||||
|
// 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
|
@ -0,0 +1,114 @@
|
|||||||
|
// 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…
Reference in New Issue