Compare commits
2 Commits
master
...
float_supp
Author | SHA1 | Date | |
---|---|---|---|
|
af545587df | ||
|
090600ef58 |
@ -1,2 +0,0 @@
|
||||
# introduces black formatting
|
||||
5515c7795cfd690d346aad10ce17b30acf914648
|
49
.github/workflows/ci-pytest.yml
vendored
49
.github/workflows/ci-pytest.yml
vendored
@ -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/
|
34
.github/workflows/code-formatting.yml
vendored
34
.github/workflows/code-formatting.yml
vendored
@ -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
|
98
.github/workflows/jupyterlite.yml
vendored
98
.github/workflows/jupyterlite.yml
vendored
@ -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
|
22
.github/workflows/pythonpublish.yml
vendored
22
.github/workflows/pythonpublish.yml
vendored
@ -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
10
.gitignore
vendored
@ -1,8 +1,6 @@
|
||||
venv
|
||||
__pycache__
|
||||
.mypy_cache
|
||||
.pytest_cache
|
||||
|
||||
/venv*
|
||||
/dist
|
||||
/riscemu.egg-info
|
||||
/build
|
||||
dist/
|
||||
riscemu.egg-info
|
||||
build/
|
||||
|
2
.idea/inspectionProfiles/profiles_settings.xml
generated
2
.idea/inspectionProfiles/profiles_settings.xml
generated
@ -3,4 +3,4 @@
|
||||
<option name="USE_PROJECT_PROFILE" value="false" />
|
||||
<version value="1.0" />
|
||||
</settings>
|
||||
</component>
|
||||
</component>
|
4
.idea/misc.xml
generated
4
.idea/misc.xml
generated
@ -1,4 +1,4 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project version="4">
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.10 (riscemu)" project-jdk-type="Python SDK" />
|
||||
</project>
|
||||
<component name="ProjectRootManager" version="2" project-jdk-name="Python 3.8 (riscemu)" project-jdk-type="Python SDK" />
|
||||
</project>
|
2
.idea/modules.xml
generated
2
.idea/modules.xml
generated
@ -5,4 +5,4 @@
|
||||
<module fileurl="file://$PROJECT_DIR$/.idea/riscemu.iml" filepath="$PROJECT_DIR$/.idea/riscemu.iml" />
|
||||
</modules>
|
||||
</component>
|
||||
</project>
|
||||
</project>
|
5
.idea/riscemu.iml
generated
5
.idea/riscemu.iml
generated
@ -2,7 +2,6 @@
|
||||
<module type="PYTHON_MODULE" version="4">
|
||||
<component name="NewModuleRootManager">
|
||||
<content url="file://$MODULE_DIR$">
|
||||
<sourceFolder url="file://$MODULE_DIR$/riscemu" isTestSource="false" />
|
||||
<sourceFolder url="file://$MODULE_DIR$/test" isTestSource="true" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/venv" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/dist" />
|
||||
@ -10,7 +9,7 @@
|
||||
<excludeFolder url="file://$MODULE_DIR$/riscemu.egg-info" />
|
||||
<excludeFolder url="file://$MODULE_DIR$/build" />
|
||||
</content>
|
||||
<orderEntry type="jdk" jdkName="Python 3.10 (riscemu)" jdkType="Python SDK" />
|
||||
<orderEntry type="inheritedJdk" />
|
||||
<orderEntry type="sourceFolder" forTests="false" />
|
||||
</component>
|
||||
</module>
|
||||
</module>
|
2
.idea/vcs.xml
generated
2
.idea/vcs.xml
generated
@ -3,4 +3,4 @@
|
||||
<component name="VcsDirectoryMappings">
|
||||
<mapping directory="$PROJECT_DIR$" vcs="Git" />
|
||||
</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
|
28
CHANGELOG.md
28
CHANGELOG.md
@ -1,29 +1,5 @@
|
||||
# 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
|
||||
|
||||
- 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()
|
||||
- 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
|
||||
- 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
|
||||
|
||||
@ -47,6 +23,6 @@
|
||||
|
||||
## 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
|
||||
- Completely reworked how assembly is parsed
|
||||
|
2
LICENSE
2
LICENSE
@ -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
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
18
README.md
18
README.md
@ -10,7 +10,7 @@ This emulator contains:
|
||||
* RISC-V Assembly loader
|
||||
* Emulation for most parts of the basic RISC-V instruction set and the M and A extensions
|
||||
* Naive memory emulator
|
||||
* Basic implementation of some syscalls
|
||||
* Basic implementation of some syscalls
|
||||
* A debugging environment
|
||||
|
||||
## Installation:
|
||||
@ -31,9 +31,7 @@ Hello world
|
||||
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 [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)
|
||||
A list of comma separated instruction sets you want to load:
|
||||
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
|
||||
later, maybe the `_init` section of each binary is executed before the main loop starts?
|
||||
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?
|
||||
|
||||
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).
|
||||
|
||||
## 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`.
|
||||
|
||||
## 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
|
||||
* 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
|
||||
|
||||
|
||||
## TODO:
|
||||
* 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
|
||||
* Move away from `print` and use `logging.logger` instead
|
||||
* 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
|
||||
* `.align <bytes>` currently a nop as cpu does not care about alignment as of now
|
||||
|
||||
## Sections:
|
||||
Currently only these three sections are supported:
|
||||
## Sections:
|
||||
Currently only these three sections are supported:
|
||||
* `data` read-write data (non-executable)
|
||||
* `rodata` read-only data (non-executable)
|
||||
* `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)
|
||||
[CPU] Started running from 0x00000138 (examples/fibs.asm)
|
||||
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,
|
||||
@ -58,8 +58,8 @@ and most common tasks should have helper methods for them.
|
||||
* `get(name)` get register content
|
||||
* `set(name, val)` set register content
|
||||
* `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:**
|
||||
|
||||
* `dump(regs | addr)` dumps either registers or memory address
|
||||
@ -71,4 +71,4 @@ and most common tasks should have helper methods for them.
|
||||
|
||||
Example:
|
||||
|
||||
![debuggin the fibs program](debug-session.png)
|
||||
![debuggin the fibs program](debug-session.png)
|
@ -1,19 +1,19 @@
|
||||
# Internal Structure
|
||||
|
||||
## 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:
|
||||
|
||||
|
||||
* An `RiscVInput` is created, this represents the file internally
|
||||
* An `RiscVTokenizer` is created by calling `cpu.get_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.
|
||||
This is done by creating an `ExecutableParser(tk: RiscVTokenizer)`
|
||||
and the calling `parse()`.
|
||||
* 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
|
||||
regions the executable contains (and some meta information such as
|
||||
symbols).
|
||||
@ -30,3 +30,4 @@ Creating a cpu with certain instruction sets is done by passing the CPU construc
|
||||
```
|
||||
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!
|
||||
|
||||
|
@ -7,7 +7,7 @@ Performing a syscall is quite simple:
|
||||
; set syscall args:
|
||||
addi a0, zero, 1 ; exit with code 1
|
||||
; 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.
|
||||
@ -47,4 +47,4 @@ Requires flag `--scall-fs` to be set to True
|
||||
|
||||
# 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
|
||||
// this calculates the fibonacci sequence and stores it in ram
|
||||
; Example program (c) by Anton Lydike
|
||||
; this calculates the fibonacci sequence and stores it in ram
|
||||
|
||||
.data
|
||||
fibs: .space 56
|
||||
|
||||
.text
|
||||
main:
|
||||
addi s1, zero, 0 // storage index
|
||||
addi s2, zero, 56 // last storage index
|
||||
addi t0, zero, 1 // t0 = F_{i}
|
||||
addi t1, zero, 1 // t1 = F_{i+1}
|
||||
addi s1, zero, 0 ; storage index
|
||||
addi s2, zero, 56 ; last storage index
|
||||
addi t0, zero, 1 ; t0 = F_{i}
|
||||
addi t1, zero, 1 ; t1 = F_{i+1}
|
||||
ebreak ; launch debugger
|
||||
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
|
||||
// exit gracefully
|
||||
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
|
||||
; exit gracefully
|
||||
ebreak ; launch debugger
|
||||
addi a0, zero, 0
|
||||
addi a7, zero, 93
|
||||
scall // exit with code 0
|
||||
scall ; exit with code 0
|
154
examples/lib/libstring.asm
Normal file
154
examples/lib/libstring.asm
Normal file
@ -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
|
||||
|
||||
# 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!
|
14
libc/crt0.s
14
libc/crt0.s
@ -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
|
178
libc/stdlib.s
178
libc/stdlib.s
@ -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
|
154
libc/string.s
154
libc/string.s
@ -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]
|
||||
requires = ["setuptools", "wheel"]
|
||||
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.
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, instruction_sets: List[Type["riscemu.InstructionSet"]], conf: RunConfig
|
||||
):
|
||||
def __init__(self, instruction_sets: List[Type['riscemu.InstructionSet']], conf: RunConfig):
|
||||
"""
|
||||
Creates a CPU instance.
|
||||
|
||||
@ -51,16 +49,12 @@ class UserModeCPU(CPU):
|
||||
syscall_symbols.update(self.mmu.global_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.
|
||||
"""
|
||||
if self.halted:
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "[CPU] Program exited with code {}".format(self.exit_code)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_CPU + "[CPU] Program exited with code {}".format(self.exit_code) + FMT_NONE)
|
||||
return
|
||||
|
||||
launch_debugger = False
|
||||
@ -69,9 +63,7 @@ class UserModeCPU(CPU):
|
||||
self.cycle += 1
|
||||
ins = self.mmu.read_ins(self.pc)
|
||||
if verbose:
|
||||
print(
|
||||
FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)
|
||||
)
|
||||
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
|
||||
self.pc += self.INS_XLEN
|
||||
self.run_instruction(ins)
|
||||
except RiscemuBaseException as ex:
|
||||
@ -80,29 +72,25 @@ class UserModeCPU(CPU):
|
||||
if self.debugger_active:
|
||||
raise ex
|
||||
|
||||
print(FMT_CPU + "[CPU] Debugger launch requested!" + FMT_NONE)
|
||||
print(FMT_CPU + '[CPU] Debugger launch requested!' + FMT_NONE)
|
||||
launch_debugger = True
|
||||
else:
|
||||
print(ex.message())
|
||||
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
|
||||
|
||||
if launch_debugger:
|
||||
launch_debug_session(self)
|
||||
|
||||
def run(self, verbose: bool = False):
|
||||
def run(self, verbose=False):
|
||||
while not self.halted:
|
||||
self.step(verbose)
|
||||
|
||||
if self.conf.verbosity > 0:
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "[CPU] Program exited with code {}".format(self.exit_code)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(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
|
||||
:param stack_size: the size of the required stack, defaults to 4Kib
|
||||
@ -110,26 +98,22 @@ class UserModeCPU(CPU):
|
||||
"""
|
||||
stack_sec = BinaryDataMemorySection(
|
||||
bytearray(stack_size),
|
||||
".stack",
|
||||
'.stack',
|
||||
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):
|
||||
print(FMT_ERROR + "[CPU] Could not insert stack section!" + FMT_NONE)
|
||||
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:
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "[CPU] Created stack of size {} at 0x{:x}".format(
|
||||
stack_size, stack_sec.base
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_CPU + "[CPU] Created stack of size {} at 0x{:x}".format(
|
||||
stack_size, stack_sec.base
|
||||
) + FMT_NONE)
|
||||
|
||||
return True
|
||||
|
||||
|
@ -1,30 +1,22 @@
|
||||
from abc import ABC
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Optional
|
||||
|
||||
from riscemu.types import MemorySection, MemoryFlags, T_RelativeAddress
|
||||
|
||||
|
||||
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)
|
||||
|
||||
def contains(self, addr, size: int = 0):
|
||||
return (
|
||||
self.base <= addr < self.base + self.size
|
||||
and self.base <= addr + size <= self.base + self.size
|
||||
)
|
||||
return self.base <= addr < 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)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}[{}] at 0x{:0X} (size={}bytes, flags={})".format(
|
||||
self.__class__.__name__, self.name, self.base, self.size, self.flags
|
||||
)
|
||||
)
|
@ -8,9 +8,7 @@ class TextIO(IOModule):
|
||||
raise InstructionAccessFault(self.base + offset)
|
||||
|
||||
def __init__(self, base: int, buflen: int = 128):
|
||||
super(TextIO, self).__init__(
|
||||
"TextIO", MemoryFlags(False, False), buflen + 4, base=base
|
||||
)
|
||||
super(TextIO, self).__init__('TextIO', MemoryFlags(False, False), buflen + 4, base=base)
|
||||
self.buff = bytearray(buflen)
|
||||
self.current_line = ""
|
||||
|
||||
@ -25,15 +23,15 @@ class TextIO(IOModule):
|
||||
self._print()
|
||||
return
|
||||
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):
|
||||
buff = self.buff
|
||||
self.buff = bytearray(self.size)
|
||||
if b"\x00" in buff:
|
||||
buff = buff.split(b"\x00")[0]
|
||||
text = buff.decode("ascii")
|
||||
if "\n" in text:
|
||||
if b'\x00' in buff:
|
||||
buff = buff.split(b'\x00')[0]
|
||||
text = buff.decode('ascii')
|
||||
if '\n' in text:
|
||||
lines = text.split("\n")
|
||||
lines[0] = self.current_line + lines[0]
|
||||
for line in lines[:-1]:
|
||||
|
157
riscemu/MMU.py
157
riscemu/MMU.py
@ -8,16 +8,8 @@ from typing import Dict, List, Optional, Union
|
||||
|
||||
from .colors import *
|
||||
from .helpers import align_addr
|
||||
from .types import (
|
||||
Instruction,
|
||||
MemorySection,
|
||||
MemoryFlags,
|
||||
T_AbsoluteAddress,
|
||||
Program,
|
||||
InstructionContext,
|
||||
Int32,
|
||||
Float32,
|
||||
)
|
||||
from .types import Instruction, MemorySection, MemoryFlags, T_AbsoluteAddress, \
|
||||
Program, InstructionContext, Int32
|
||||
from .types.exceptions import InvalidAllocationException, MemoryAccessException
|
||||
|
||||
|
||||
@ -73,7 +65,7 @@ class MMU:
|
||||
return sec
|
||||
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:
|
||||
if program.base <= addr < program.base + program.size:
|
||||
return program
|
||||
@ -88,14 +80,8 @@ class MMU:
|
||||
"""
|
||||
sec = self.get_sec_containing(addr)
|
||||
if sec is None:
|
||||
print(
|
||||
FMT_MEM
|
||||
+ "[MMU] Trying to read instruction form invalid region! (read at {}) ".format(
|
||||
addr
|
||||
)
|
||||
+ "Have you forgotten an exit syscall or ret statement?"
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_MEM + "[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!")
|
||||
return sec.read_ins(addr - sec.base)
|
||||
|
||||
@ -108,19 +94,12 @@ class MMU:
|
||||
:return: The bytearray at addr
|
||||
"""
|
||||
if isinstance(addr, Int32):
|
||||
breakpoint()
|
||||
addr = addr.unsigned_value
|
||||
sec = self.get_sec_containing(addr)
|
||||
if sec is None:
|
||||
print(
|
||||
FMT_MEM
|
||||
+ "[MMU] Trying to read data form invalid region at 0x{:x}! ".format(
|
||||
addr
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
raise MemoryAccessException(
|
||||
"region is non-initialized!", addr, size, "read"
|
||||
)
|
||||
print(FMT_MEM + "[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)
|
||||
|
||||
def write(self, addr: int, size: int, data: bytearray):
|
||||
@ -133,16 +112,8 @@ class MMU:
|
||||
"""
|
||||
sec = self.get_sec_containing(addr)
|
||||
if sec is None:
|
||||
print(
|
||||
FMT_MEM
|
||||
+ "[MMU] Invalid write into non-initialized region at 0x{:08X}".format(
|
||||
addr
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
raise MemoryAccessException(
|
||||
"region is non-initialized!", addr, size, "write"
|
||||
)
|
||||
print(FMT_MEM + '[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)
|
||||
|
||||
@ -156,11 +127,7 @@ class MMU:
|
||||
"""
|
||||
sec = self.get_sec_containing(addr)
|
||||
if sec is None:
|
||||
print(
|
||||
FMT_MEM
|
||||
+ "[MMU] No section containing addr 0x{:08X}".format(addr)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_MEM + "[MMU] No section containing addr 0x{:08X}".format(addr) + FMT_NONE)
|
||||
return
|
||||
sec.dump(addr - sec.base, *args, **kwargs)
|
||||
|
||||
@ -172,54 +139,33 @@ class MMU:
|
||||
"""
|
||||
print(FMT_MEM + "[MMU] Lookup for symbol {}:".format(symb) + FMT_NONE)
|
||||
if symb in self.global_symbols:
|
||||
print(
|
||||
" Found global symbol {}: 0x{:X}".format(
|
||||
symb, self.global_symbols[symb]
|
||||
)
|
||||
)
|
||||
print(" Found global symbol {}: 0x{:X}".format(symb, self.global_symbols[symb]))
|
||||
for bin in self.programs:
|
||||
if symb in bin.context.labels:
|
||||
print(
|
||||
" Found local labels {}: 0x{:X} in {}".format(
|
||||
symb, bin.context.labels[symb], bin.name
|
||||
)
|
||||
)
|
||||
print(" Found local labels {}: 0x{:X} in {}".format(symb, bin.context.labels[symb], bin.name))
|
||||
|
||||
def read_int(self, addr: int) -> Int32:
|
||||
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:
|
||||
sec = self.get_sec_containing(address)
|
||||
if not sec:
|
||||
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 []
|
||||
elf_markers = {
|
||||
"__global_pointer$",
|
||||
"_fdata",
|
||||
"_etext",
|
||||
"_gp",
|
||||
"_bss_start",
|
||||
"_bss_end",
|
||||
"_ftext",
|
||||
"_edata",
|
||||
"_end",
|
||||
"_fbss",
|
||||
'__global_pointer$', '_fdata', '_etext', '_gp',
|
||||
'_bss_start', '_bss_end', '_ftext', '_edata', '_end', '_fbss'
|
||||
}
|
||||
|
||||
def key(x):
|
||||
name, val = x
|
||||
return address - val
|
||||
|
||||
best_fit = sorted(
|
||||
filter(lambda x: x[1] <= address, sec.context.labels.items()), key=key
|
||||
)
|
||||
best_fit = sorted(filter(lambda x: x[1] <= address, sec.context.labels.items()), key=key)
|
||||
|
||||
best = ("", float("inf"))
|
||||
best = ('', float('inf'))
|
||||
for name, val in best_fit:
|
||||
if address - val < best[1]:
|
||||
best = (name, val)
|
||||
@ -233,14 +179,13 @@ class MMU:
|
||||
|
||||
if not name:
|
||||
return "{}:{} + 0x{:x} (0x{:x})".format(
|
||||
sec.owner, sec.name, address - sec.base, address
|
||||
sec.owner, sec.name,
|
||||
address - sec.base, address
|
||||
)
|
||||
|
||||
return str(
|
||||
"{}:{} at {} (0x{:0x}) + 0x{:0x}".format(
|
||||
sec.owner, sec.name, name, val, address - val
|
||||
)
|
||||
)
|
||||
return str('{}:{} at {} (0x{:0x}) + 0x{:0x}'.format(
|
||||
sec.owner, sec.name, name, val, address - val
|
||||
))
|
||||
|
||||
def has_continous_free_region(self, start: int, end: int) -> bool:
|
||||
# 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):
|
||||
if program.base is not None:
|
||||
if not self.has_continous_free_region(
|
||||
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
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
raise InvalidAllocationException(
|
||||
"Area occupied".format(
|
||||
program.name, program.base, program.base + program.size
|
||||
),
|
||||
program.name,
|
||||
program.size,
|
||||
MemoryFlags(False, True),
|
||||
)
|
||||
if not self.has_continous_free_region(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
|
||||
) + 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
|
||||
else:
|
||||
@ -311,19 +245,14 @@ class MMU:
|
||||
self.sections.append(sec)
|
||||
self._update_state()
|
||||
else:
|
||||
print(
|
||||
FMT_MEM
|
||||
+ "[MMU] Cannot place section {} at {}, space is occupied!".format(
|
||||
sec, sec.base
|
||||
)
|
||||
)
|
||||
print(FMT_MEM + '[MMU] Cannot place section {} at {}, space is occupied!'.format(sec, sec.base))
|
||||
return False
|
||||
else:
|
||||
at_addr = align_addr(self.get_guaranteed_free_address(), 8)
|
||||
sec.base = at_addr
|
||||
self.sections.append(sec)
|
||||
self._update_state()
|
||||
return True
|
||||
return True
|
||||
|
||||
def _update_state(self):
|
||||
"""
|
||||
@ -341,7 +270,8 @@ class MMU:
|
||||
|
||||
def __repr__(self):
|
||||
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:
|
||||
@ -352,12 +282,13 @@ class MMU:
|
||||
|
||||
return InstructionContext()
|
||||
|
||||
def find_entrypoint(self) -> Optional[int]:
|
||||
# try to find the global entrypoint
|
||||
if "_start" in self.global_symbols:
|
||||
return self.global_symbols["_start"]
|
||||
# otherwise find a main (that's not necessarily global)
|
||||
for p in self.programs:
|
||||
if "main" in p.context.labels:
|
||||
return p.context.resolve_label("main")
|
||||
return None
|
||||
def report_addr(self, addr: T_AbsoluteAddress):
|
||||
sec = self.get_sec_containing(addr)
|
||||
if not sec:
|
||||
print("addr is in no section!")
|
||||
return
|
||||
owner = [b for b in self.programs if b.name == sec.owner]
|
||||
if owner:
|
||||
print("owned by: {}".format(owner[0]))
|
||||
|
||||
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
|
||||
"""
|
||||
|
||||
from .types.exceptions import (
|
||||
RiscemuBaseException,
|
||||
LaunchDebuggerException,
|
||||
InvalidSyscallException,
|
||||
LinkerException,
|
||||
ParseException,
|
||||
NumberFormatException,
|
||||
InvalidRegisterException,
|
||||
MemoryAccessException,
|
||||
OutOfMemoryException,
|
||||
)
|
||||
from .types.exceptions import RiscemuBaseException, LaunchDebuggerException, InvalidSyscallException, LinkerException, \
|
||||
ParseException, NumberFormatException, InvalidRegisterException, MemoryAccessException, OutOfMemoryException
|
||||
|
||||
from .instructions import *
|
||||
|
||||
@ -33,5 +24,5 @@ from .config import RunConfig
|
||||
from .parser import tokenize, parse_tokens, AssemblyFileLoader
|
||||
|
||||
__author__ = "Anton Lydike <Anton@Lydike.com>"
|
||||
__copyright__ = "Copyright 2023 Anton Lydike"
|
||||
__version__ = "2.1.1"
|
||||
__copyright__ = "Copyright 2022 Anton Lydike"
|
||||
__version__ = '2.0.3'
|
||||
|
@ -5,18 +5,126 @@ SPDX-License-Identifier: MIT
|
||||
|
||||
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
|
||||
from riscemu.riscemu_main import RiscemuMain
|
||||
if __name__ == '__main__':
|
||||
from .config import RunConfig
|
||||
from .instructions import InstructionSetDict
|
||||
from .colors import FMT_BOLD, FMT_MAGENTA
|
||||
from .parser import AssemblyFileLoader
|
||||
import argparse
|
||||
import sys
|
||||
|
||||
try:
|
||||
main = RiscemuMain()
|
||||
main.run_from_cli(sys.argv[1:])
|
||||
sys.exit(main.cpu.exit_code if not main.cfg.ignore_exit_code else 0)
|
||||
all_ins_names = list(InstructionSetDict.keys())
|
||||
|
||||
except RiscemuBaseException as e:
|
||||
print("Error: {}".format(e.message()))
|
||||
e.print_stacktrace()
|
||||
if '--version' in sys.argv:
|
||||
print("riscemu version {}\n{}\n\nAvailable ISA: {}".format(
|
||||
__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 .helpers import parse_numeric_argument, align_addr, get_section_base_name
|
||||
from .tokenizer import Token
|
||||
from .types import (
|
||||
Program,
|
||||
T_RelativeAddress,
|
||||
InstructionContext,
|
||||
Instruction,
|
||||
BinaryDataMemorySection,
|
||||
InstructionMemorySection,
|
||||
Int32,
|
||||
)
|
||||
from .types import Program, T_RelativeAddress, InstructionContext, Instruction, BinaryDataMemorySection, \
|
||||
InstructionMemorySection, Int32
|
||||
|
||||
INSTRUCTION_SECTION_NAMES = (".text", ".init", ".fini")
|
||||
INSTRUCTION_SECTION_NAMES = ('.text', '.init', '.fini')
|
||||
"""
|
||||
A tuple containing all section names which contain executable code (instead of data)
|
||||
|
||||
@ -54,7 +47,8 @@ class CurrentSection:
|
||||
|
||||
def __repr__(self):
|
||||
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:
|
||||
section = BinaryDataMemorySection(
|
||||
self.section.data,
|
||||
self.section.name,
|
||||
self.context,
|
||||
self.program.name,
|
||||
self.section.base,
|
||||
self.section.data, self.section.name, self.context, self.program.name, self.section.base
|
||||
)
|
||||
self.program.add_section(section)
|
||||
elif self.section.type == MemorySectionType.Instructions:
|
||||
section = InstructionMemorySection(
|
||||
self.section.data,
|
||||
self.section.name,
|
||||
self.context,
|
||||
self.program.name,
|
||||
self.section.base,
|
||||
self.section.data, self.section.name, self.context, self.program.name, self.section.base
|
||||
)
|
||||
self.program.add_section(section)
|
||||
|
||||
@ -103,9 +89,7 @@ class ParseContext:
|
||||
self._finalize_section()
|
||||
self.section = CurrentSection(name, type, base)
|
||||
|
||||
def add_label(
|
||||
self, name: str, value: int, is_global: bool = False, is_relative: bool = False
|
||||
):
|
||||
def add_label(self, name: str, value: int, is_global: bool = False, is_relative: bool = False):
|
||||
self.context.labels[name] = value
|
||||
if is_global:
|
||||
self.program.global_labels.add(name)
|
||||
@ -125,16 +109,10 @@ class ParseContext:
|
||||
|
||||
def ASSERT_IN_SECTION_TYPE(context: ParseContext, type: MemorySectionType):
|
||||
if context.section is None:
|
||||
raise ParseException(
|
||||
"Error, expected to be in {} section, but no section is present...".format(
|
||||
type.name
|
||||
)
|
||||
)
|
||||
raise ParseException('Error, expected to be in {} section, but no section is present...'.format(type.name))
|
||||
if context.section.type != type:
|
||||
raise ParseException(
|
||||
"Error, expected to be in {} section, but currently in {}...".format(
|
||||
type.name, context.section
|
||||
)
|
||||
'Error, expected to be in {} section, but currently in {}...'.format(type.name, context.section)
|
||||
)
|
||||
|
||||
|
||||
@ -196,9 +174,7 @@ class AssemblerDirectives:
|
||||
cls.add_bytes(size, bytearray(size), context)
|
||||
|
||||
@classmethod
|
||||
def add_bytes(
|
||||
cls, size: int, content: Union[None, int, bytearray], context: ParseContext
|
||||
):
|
||||
def add_bytes(cls, size: int, content: Union[None, int, bytearray], context: ParseContext):
|
||||
ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data)
|
||||
|
||||
if content is None:
|
||||
@ -211,9 +187,9 @@ class AssemblerDirectives:
|
||||
@classmethod
|
||||
def add_text(cls, text: str, context: ParseContext, zero_terminate: bool = True):
|
||||
# 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:
|
||||
encoded_bytes += bytearray(1)
|
||||
cls.add_bytes(len(encoded_bytes), encoded_bytes, context)
|
||||
@ -221,36 +197,24 @@ class AssemblerDirectives:
|
||||
@classmethod
|
||||
def handle_instruction(cls, token: Token, args: Tuple[str], context: ParseContext):
|
||||
op = token.value[1:]
|
||||
if hasattr(cls, "op_" + op):
|
||||
getattr(cls, "op_" + op)(token, args, context)
|
||||
elif op in ("text", "data", "rodata", "bss", "sbss"):
|
||||
if hasattr(cls, 'op_' + op):
|
||||
getattr(cls, 'op_' + op)(token, args, context)
|
||||
elif op in ('text', 'data', 'rodata', 'bss', 'sbss'):
|
||||
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)
|
||||
cls.add_text(args[0], context, zero_terminate=(op != "ascii"))
|
||||
cls.add_text(args[0], context, op == 'ascii')
|
||||
elif op in DATA_OP_SIZES:
|
||||
size = DATA_OP_SIZES[op]
|
||||
for arg in args:
|
||||
cls.add_bytes(size, parse_numeric_argument(arg), context)
|
||||
else:
|
||||
print(
|
||||
FMT_PARSE
|
||||
+ "Unknown assembler directive: {} {} in {}".format(
|
||||
token, args, context
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_PARSE + "Unknown assembler directive: {} {} in {}".format(token, args, context) + FMT_NONE)
|
||||
|
||||
|
||||
DATA_OP_SIZES = {
|
||||
"byte": 1,
|
||||
"2byte": 2,
|
||||
"half": 2,
|
||||
"short": 2,
|
||||
"4byte": 4,
|
||||
"word": 4,
|
||||
"long": 4,
|
||||
"8byte": 8,
|
||||
"dword": 8,
|
||||
"quad": 8,
|
||||
'byte': 1,
|
||||
'2byte': 2, 'half': 2, 'short': 2,
|
||||
'4byte': 4, 'word': 4, 'long': 4,
|
||||
'8byte': 8, 'dword': 8, 'quad': 8,
|
||||
}
|
||||
|
@ -6,18 +6,18 @@ SPDX-License-Identifier: MIT
|
||||
|
||||
# Colors
|
||||
|
||||
FMT_RED = "\033[31m"
|
||||
FMT_ORANGE = "\033[33m"
|
||||
FMT_GRAY = "\033[37m"
|
||||
FMT_CYAN = "\033[36m"
|
||||
FMT_GREEN = "\033[32m"
|
||||
FMT_MAGENTA = "\033[35m"
|
||||
FMT_BLUE = "\033[34m"
|
||||
FMT_YELLOW = "\033[93m"
|
||||
FMT_RED = '\033[31m'
|
||||
FMT_ORANGE = '\033[33m'
|
||||
FMT_GRAY = '\033[37m'
|
||||
FMT_CYAN = '\033[36m'
|
||||
FMT_GREEN = '\033[32m'
|
||||
FMT_MAGENTA = '\033[35m'
|
||||
FMT_BLUE = '\033[34m'
|
||||
FMT_YELLOW = '\033[93m'
|
||||
|
||||
FMT_BOLD = "\033[1m"
|
||||
FMT_NONE = "\033[0m"
|
||||
FMT_UNDERLINE = "\033[4m"
|
||||
FMT_BOLD = '\033[1m'
|
||||
FMT_NONE = '\033[0m'
|
||||
FMT_UNDERLINE = '\033[4m'
|
||||
|
||||
FMT_ERROR = FMT_RED + FMT_BOLD
|
||||
|
||||
@ -26,4 +26,4 @@ FMT_PARSE = FMT_CYAN + FMT_BOLD
|
||||
FMT_CPU = FMT_BLUE + FMT_BOLD
|
||||
FMT_SYSCALL = FMT_YELLOW + 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
|
||||
verbosity: int = 0
|
||||
slowdown: float = 1
|
||||
unlimited_registers: bool = False
|
||||
# runtime config
|
||||
use_libc: bool = False
|
||||
ignore_exit_code: bool = False
|
||||
|
||||
|
||||
CONFIG = RunConfig()
|
||||
|
@ -11,10 +11,10 @@ from .helpers import *
|
||||
if typing.TYPE_CHECKING:
|
||||
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:
|
||||
return
|
||||
import code
|
||||
@ -39,7 +39,7 @@ def launch_debug_session(cpu: "CPU", prompt=""):
|
||||
mmu.dump(what, *args, **kwargs)
|
||||
|
||||
def dump_stack(*args, **kwargs):
|
||||
mmu.dump(regs.get("sp"), *args, **kwargs)
|
||||
mmu.dump(regs.get('sp'), *args, **kwargs)
|
||||
|
||||
def ins():
|
||||
print("Current instruction at 0x{:08X}:".format(cpu.pc))
|
||||
@ -51,7 +51,11 @@ def launch_debug_session(cpu: "CPU", prompt=""):
|
||||
return
|
||||
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)
|
||||
cpu.run_instruction(ins)
|
||||
|
||||
@ -59,7 +63,7 @@ def launch_debug_session(cpu: "CPU", prompt=""):
|
||||
try:
|
||||
cpu.run(verbose)
|
||||
except LaunchDebuggerException:
|
||||
print(FMT_DEBUG + "Returning to debugger...")
|
||||
print(FMT_DEBUG + 'Returning to debugger...')
|
||||
return
|
||||
|
||||
def step():
|
||||
@ -88,3 +92,4 @@ def launch_debug_session(cpu: "CPU", prompt=""):
|
||||
finally:
|
||||
cpu.debugger_active = False
|
||||
readline.write_history_file(HIST_FILE)
|
||||
|
||||
|
@ -1,2 +1,2 @@
|
||||
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 readline
|
||||
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.parse_and_bind("tab: complete")
|
||||
code.InteractiveConsole(sess_vars).interact(
|
||||
banner="Interaktive decoding session started...", exitmsg="Closing..."
|
||||
)
|
||||
code.InteractiveConsole(sess_vars).interact(banner="Interaktive decoding session started...", exitmsg="Closing...")
|
||||
|
@ -5,14 +5,13 @@ from typing import Tuple, List
|
||||
def print_ins(ins: int):
|
||||
print(" f7 rs2 rs1 f3 rd op")
|
||||
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]] = {
|
||||
0x00000013: ("nop", [], 0x00000013),
|
||||
0x00008067: ("ret", [], 0x00008067),
|
||||
0xFE010113: ("addi", [2, 2, -32], 0xFE010113),
|
||||
0xfe010113: ("addi", [2, 2, -32], 0xfe010113),
|
||||
0x02010113: ("addi", [2, 2, 32], 0x02010113),
|
||||
0x00100073: ("ebreak", [], 0x00100073),
|
||||
0x00000073: ("ecall", [], 0x00000073),
|
||||
@ -24,7 +23,7 @@ STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = {
|
||||
|
||||
|
||||
def int_from_ins(insn: bytearray):
|
||||
return int.from_bytes(insn, "little")
|
||||
return int.from_bytes(insn, 'little')
|
||||
|
||||
|
||||
def name_from_insn(ins: int):
|
||||
@ -46,7 +45,7 @@ def name_from_insn(ins: int):
|
||||
if isinstance(dec, str):
|
||||
return dec
|
||||
|
||||
if opcode == 0x1C and fun3 == 0:
|
||||
if opcode == 0x1c and fun3 == 0:
|
||||
# we have ecall/ebreak
|
||||
token = imm110(ins)
|
||||
if token in dec:
|
||||
|
@ -1,7 +1,6 @@
|
||||
from typing import Dict, Callable, List, Union
|
||||
from .regs import RISCV_REGS
|
||||
|
||||
|
||||
def op(ins: int):
|
||||
return (ins >> 2) & 31
|
||||
|
||||
@ -47,12 +46,7 @@ def imm_b(ins: int):
|
||||
lower = rd(ins)
|
||||
higher = funct7(ins)
|
||||
|
||||
num = (
|
||||
(lower & 0b11110)
|
||||
+ ((higher & 0b0111111) << 5)
|
||||
+ ((lower & 1) << 11)
|
||||
+ ((higher >> 6) << 12)
|
||||
)
|
||||
num = (lower & 0b11110) + ((higher & 0b0111111) << 5) + ((lower & 1) << 11) + ((higher >> 6) << 12)
|
||||
return sign_extend(num, 13)
|
||||
|
||||
|
||||
@ -62,11 +56,10 @@ def imm_u(ins: int):
|
||||
|
||||
def imm_j(ins: int):
|
||||
return sign_extend(
|
||||
(((ins >> 21) & 0b1111111111) << 1)
|
||||
+ (((ins >> 20) & 1) << 11)
|
||||
+ (((ins >> 12) & 0b11111111) << 12)
|
||||
+ (((ins >> 31) & 1) << 20),
|
||||
21,
|
||||
(((ins >> 21) & 0b1111111111) << 1) +
|
||||
(((ins >> 20) & 1) << 11) +
|
||||
(((ins >> 12) & 0b11111111) << 12) +
|
||||
(((ins >> 31) & 1) << 20), 21
|
||||
)
|
||||
|
||||
|
||||
@ -118,7 +111,7 @@ INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[int]]] = {
|
||||
0x0D: decode_u,
|
||||
0x18: decode_b,
|
||||
0x19: decode_i,
|
||||
0x1B: decode_j,
|
||||
0x1C: decode_i_unsigned,
|
||||
0b1011: decode_r,
|
||||
0x1b: decode_j,
|
||||
0x1c: decode_i_unsigned,
|
||||
0b1011: decode_r
|
||||
}
|
||||
|
@ -1,15 +1,5 @@
|
||||
from .formats import (
|
||||
INSTRUCTION_ARGS_DECODER,
|
||||
op,
|
||||
decode_i,
|
||||
decode_r,
|
||||
decode_u,
|
||||
decode_b,
|
||||
decode_j,
|
||||
decode_s,
|
||||
decode_i_shamt,
|
||||
decode_i_unsigned,
|
||||
)
|
||||
from .formats import INSTRUCTION_ARGS_DECODER, op, decode_i, decode_r, decode_u, decode_b, decode_j, decode_s, \
|
||||
decode_i_shamt, decode_i_unsigned
|
||||
from .regs import RISCV_REGS
|
||||
|
||||
|
||||
@ -19,9 +9,9 @@ def int_to_hex(num: int):
|
||||
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)
|
||||
if fmt == "hex":
|
||||
if fmt == 'hex':
|
||||
fmt = int_to_hex
|
||||
else:
|
||||
fmt = str
|
||||
@ -30,7 +20,7 @@ def format_ins(ins: int, name: str, fmt: str = "int"):
|
||||
return f"{name} <unknown op>"
|
||||
|
||||
decoder = INSTRUCTION_ARGS_DECODER[opcode]
|
||||
if name in ("ecall", "ebreak", "mret", "sret", "uret"):
|
||||
if name in ('ecall', 'ebreak', 'mret', 'sret', 'uret'):
|
||||
return name
|
||||
if opcode in (0x8, 0x0):
|
||||
r1, r2, imm = decoder(ins)
|
||||
|
@ -4,7 +4,7 @@ from .formats import *
|
||||
tbl = lambda: defaultdict(tbl)
|
||||
|
||||
RV32 = tbl()
|
||||
RV32[0x1B] = "jal"
|
||||
RV32[0x1b] = "jal"
|
||||
RV32[0x0D] = "lui"
|
||||
RV32[0x05] = "auipc"
|
||||
RV32[0x19][0] = "jalr"
|
||||
@ -36,26 +36,26 @@ RV32[0x08][0] = "sb"
|
||||
RV32[0x08][1] = "sh"
|
||||
RV32[0x08][2] = "sw"
|
||||
|
||||
RV32[0x1C][1] = "csrrw"
|
||||
RV32[0x1C][2] = "csrrs"
|
||||
RV32[0x1C][3] = "csrrc"
|
||||
RV32[0x1C][5] = "csrrwi"
|
||||
RV32[0x1C][6] = "csrrsi"
|
||||
RV32[0x1C][7] = "csrrci"
|
||||
RV32[0x1c][1] = "csrrw"
|
||||
RV32[0x1c][2] = "csrrs"
|
||||
RV32[0x1c][3] = "csrrc"
|
||||
RV32[0x1c][5] = "csrrwi"
|
||||
RV32[0x1c][6] = "csrrsi"
|
||||
RV32[0x1c][7] = "csrrci"
|
||||
|
||||
RV32[0x1C][0][0] = "ecall"
|
||||
RV32[0x1C][0][1] = "ebreak"
|
||||
RV32[0x1c][0][0] = "ecall"
|
||||
RV32[0x1c][0][1] = "ebreak"
|
||||
|
||||
RV32[0x0C][0][0] = "add"
|
||||
RV32[0x0C][0][0] = "add"
|
||||
RV32[0x0C][0][32] = "sub"
|
||||
RV32[0x0C][1][0] = "sll"
|
||||
RV32[0x0C][2][0] = "slt"
|
||||
RV32[0x0C][3][0] = "sltu"
|
||||
RV32[0x0C][4][0] = "xor"
|
||||
RV32[0x0C][5][0] = "srl"
|
||||
RV32[0x0C][1][0] = "sll"
|
||||
RV32[0x0C][2][0] = "slt"
|
||||
RV32[0x0C][3][0] = "sltu"
|
||||
RV32[0x0C][4][0] = "xor"
|
||||
RV32[0x0C][5][0] = "srl"
|
||||
RV32[0x0C][5][32] = "sra"
|
||||
RV32[0x0C][6][0] = "or"
|
||||
RV32[0x0C][7][0] = "and"
|
||||
RV32[0x0C][6][0] = "or"
|
||||
RV32[0x0C][7][0] = "and"
|
||||
|
||||
# rv32m
|
||||
RV32[0x0C][0][1] = "mul"
|
||||
|
@ -1,34 +1,6 @@
|
||||
RISCV_REGS = [
|
||||
"zero",
|
||||
"ra",
|
||||
"sp",
|
||||
"gp",
|
||||
"tp",
|
||||
"t0",
|
||||
"t1",
|
||||
"t2",
|
||||
"s0",
|
||||
"s1",
|
||||
"a0",
|
||||
"a1",
|
||||
"a2",
|
||||
"a3",
|
||||
"a4",
|
||||
"a5",
|
||||
"a6",
|
||||
"a7",
|
||||
"s2",
|
||||
"s3",
|
||||
"s4",
|
||||
"s5",
|
||||
"s6",
|
||||
"s7",
|
||||
"s8",
|
||||
"s9",
|
||||
"s10",
|
||||
"s11",
|
||||
"t3",
|
||||
"t4",
|
||||
"t5",
|
||||
"t6",
|
||||
'zero', 'ra', 'sp', 'gp', 'tp', 't0', 't1', 't2',
|
||||
's0', 's1', 'a0', 'a1', 'a2', 'a3', 'a4', 'a5', 'a6', 'a7',
|
||||
's2', 's3', 's4', 's5', 's6', 's7', 's8', 's9', 's10', 's11',
|
||||
't3', 't4', 't5', 't6'
|
||||
]
|
||||
|
@ -14,8 +14,6 @@ from .types.exceptions import *
|
||||
def align_addr(addr: int, to_bytes: int = 8) -> int:
|
||||
"""
|
||||
align an address to `to_bytes` (meaning addr & to_bytes = 0)
|
||||
|
||||
This will increase the address
|
||||
"""
|
||||
return addr + (-addr % to_bytes)
|
||||
|
||||
@ -25,19 +23,16 @@ def parse_numeric_argument(arg: str) -> int:
|
||||
parse hex or int strings
|
||||
"""
|
||||
try:
|
||||
if arg.lower().startswith("0x"):
|
||||
if arg.lower().startswith('0x'):
|
||||
return int(arg, 16)
|
||||
return int(arg)
|
||||
except ValueError as ex:
|
||||
raise ParseException(
|
||||
'Invalid immediate argument "{}", maybe missing symbol?'.format(arg),
|
||||
(arg, ex),
|
||||
)
|
||||
raise ParseException('Invalid immediate argument \"{}\", maybe missing symbol?'.format(arg), (arg, ex))
|
||||
|
||||
|
||||
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]]"""
|
||||
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):
|
||||
@ -50,31 +45,26 @@ def apply_highlight(item, ind, hi_ind):
|
||||
|
||||
|
||||
def highlight_in_list(items, hi_ind, joiner=" "):
|
||||
return joiner.join(
|
||||
[apply_highlight(item, i, hi_ind) for i, item in enumerate(items)]
|
||||
)
|
||||
return joiner.join([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):
|
||||
"""Format byte array as per fmt. Group into groups of size `group`, and highlight index `highlight`."""
|
||||
chunks = create_chunks(byte_arr, group)
|
||||
if fmt == "hex":
|
||||
return highlight_in_list(["0x{}".format(ch.hex()) for ch in chunks], highlight)
|
||||
if fmt == "int":
|
||||
if fmt == 'hex':
|
||||
return highlight_in_list(['0x{}'.format(ch.hex()) for ch in chunks], highlight)
|
||||
if fmt == 'int':
|
||||
spc = str(ceil(log10(2 ** (group * 8 - 1))) + 1)
|
||||
return highlight_in_list(
|
||||
[("{:0" + spc + "d}").format(Int32(ch)) for ch in chunks], highlight
|
||||
)
|
||||
if fmt == "uint":
|
||||
return highlight_in_list([('{:0' + spc + 'd}').format(Int32(ch)) for ch in chunks], highlight)
|
||||
if fmt == 'uint':
|
||||
spc = str(ceil(log10(2 ** (group * 8))))
|
||||
return highlight_in_list(
|
||||
[("{:0" + spc + "d}").format(UInt32(ch)) for ch in chunks], highlight
|
||||
)
|
||||
if fmt == "char":
|
||||
return highlight_in_list([('{:0' + spc + 'd}').format(UInt32(ch)) for ch in chunks],
|
||||
highlight)
|
||||
if fmt == 'char':
|
||||
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]):
|
||||
@ -108,10 +98,6 @@ class Peekable(Generic[T], Iterator[T]):
|
||||
|
||||
|
||||
def get_section_base_name(section_name: str) -> str:
|
||||
if "." not in section_name:
|
||||
print(
|
||||
FMT_PARSE
|
||||
+ f"Invalid section {section_name}, not starting with a dot!"
|
||||
+ FMT_NONE
|
||||
)
|
||||
return "." + section_name.split(".")[1]
|
||||
if '.' not in section_name:
|
||||
print(FMT_PARSE + 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?
|
||||
"""
|
||||
|
||||
def instruction_lr_w(self, ins: "Instruction"):
|
||||
def instruction_lr_w(self, ins: 'Instruction'):
|
||||
INS_NOT_IMPLEMENTED(ins)
|
||||
|
||||
def instruction_sc_w(self, ins: "Instruction"):
|
||||
def instruction_sc_w(self, ins: 'Instruction'):
|
||||
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)
|
||||
if dest == "zero":
|
||||
if dest == 'zero':
|
||||
self.mmu.write(addr, val.to_bytes())
|
||||
else:
|
||||
old = Int32(self.mmu.read(addr, 4))
|
||||
self.mmu.write(addr, val.to_bytes())
|
||||
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)
|
||||
old = Int32(self.mmu.read(addr, 4))
|
||||
self.mmu.write(addr, (old + val).to_bytes(4))
|
||||
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)
|
||||
old = Int32(self.mmu.read(addr, 4))
|
||||
self.mmu.write(addr, (old & val).to_bytes(4))
|
||||
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)
|
||||
old = Int32(self.mmu.read(addr, 4))
|
||||
self.mmu.write(addr, (old | val).to_bytes(4))
|
||||
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)
|
||||
old = Int32(self.mmu.read(addr, 4))
|
||||
self.mmu.write(addr, (old ^ val).to_bytes(4))
|
||||
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)
|
||||
old = Int32(self.mmu.read(addr, 4))
|
||||
self.mmu.write(addr, max(old, val).to_bytes(4))
|
||||
self.regs.set(dest, old)
|
||||
|
||||
def instruction_amomaxu_w(self, ins: "Instruction"):
|
||||
def instruction_amomaxu_w(self, ins: 'Instruction'):
|
||||
val: UInt32
|
||||
dest, addr, val = self.parse_rd_rs_rs(ins, signed=False)
|
||||
old = UInt32(self.mmu.read(addr, 4))
|
||||
@ -63,13 +63,13 @@ class RV32A(InstructionSet):
|
||||
self.mmu.write(addr, max(old, val).to_bytes())
|
||||
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)
|
||||
old = Int32(self.mmu.read(addr, 4))
|
||||
self.mmu.write(addr, min(old, val).to_bytes(4))
|
||||
self.regs.set(dest, old)
|
||||
|
||||
def instruction_amominu_w(self, ins: "Instruction"):
|
||||
def instruction_amominu_w(self, ins: 'Instruction'):
|
||||
val: UInt32
|
||||
dest, addr, val = self.parse_rd_rs_rs(ins, signed=False)
|
||||
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 result’s sign bit is rs2’s 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 result’s sign bit is opposite of rs2’s 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 result’s 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
|
||||
"""
|
||||
|
||||
def instruction_lb(self, ins: "Instruction"):
|
||||
def instruction_lb(self, ins: 'Instruction'):
|
||||
rd, addr = self.parse_mem_ins(ins)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
self.mmu.write(addr.unsigned_value, 4, self.regs.get(rd).to_bytes(4))
|
||||
|
||||
def instruction_sll(self, ins: "Instruction"):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
dst = ins.get_reg(0)
|
||||
src1 = ins.get_reg(1)
|
||||
src2 = ins.get_reg(2)
|
||||
self.regs.set(dst, self.regs.get(src1) << (self.regs.get(src2) & 0b11111))
|
||||
|
||||
def instruction_slli(self, ins: "Instruction"):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
dst = ins.get_reg(0)
|
||||
src1 = ins.get_reg(1)
|
||||
imm = ins.get_imm(2)
|
||||
self.regs.set(dst, self.regs.get(src1) << (imm & 0b11111))
|
||||
|
||||
def instruction_srl(self, ins: "Instruction"):
|
||||
def instruction_sll(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
dst = ins.get_reg(0)
|
||||
src1 = ins.get_reg(1)
|
||||
src2 = ins.get_reg(2)
|
||||
self.regs.set(
|
||||
dst, self.regs.get(src1).shift_right_logical(self.regs.get(src2) & 0b11111)
|
||||
dst,
|
||||
self.regs.get(src1) << (self.regs.get(src2) & 0b11111)
|
||||
)
|
||||
|
||||
def instruction_srli(self, ins: "Instruction"):
|
||||
def instruction_slli(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
dst = ins.get_reg(0)
|
||||
src1 = ins.get_reg(1)
|
||||
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) << (imm & 0b11111)
|
||||
)
|
||||
|
||||
def instruction_sra(self, ins: "Instruction"):
|
||||
def instruction_srl(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
dst = ins.get_reg(0)
|
||||
src1 = ins.get_reg(1)
|
||||
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).shift_right_logical(self.regs.get(src2) & 0b11111)
|
||||
)
|
||||
|
||||
def instruction_srai(self, ins: "Instruction"):
|
||||
def instruction_srli(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
dst = ins.get_reg(0)
|
||||
src1 = ins.get_reg(1)
|
||||
imm = ins.get_imm(2)
|
||||
self.regs.set(dst, self.regs.get(src1) >> (imm & 0b11111))
|
||||
self.regs.set(
|
||||
dst,
|
||||
self.regs.get(src1).shift_right_logical(imm & 0b11111)
|
||||
)
|
||||
|
||||
def instruction_add(self, ins: "Instruction"):
|
||||
def instruction_sra(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
dst = ins.get_reg(0)
|
||||
src1 = ins.get_reg(1)
|
||||
src2 = ins.get_reg(2)
|
||||
self.regs.set(
|
||||
dst,
|
||||
self.regs.get(src1) >> (self.regs.get(src2) & 0b11111)
|
||||
)
|
||||
|
||||
def instruction_srai(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
dst = ins.get_reg(0)
|
||||
src1 = ins.get_reg(1)
|
||||
imm = ins.get_imm(2)
|
||||
self.regs.set(
|
||||
dst,
|
||||
self.regs.get(src1) >> (imm & 0b11111)
|
||||
)
|
||||
|
||||
def instruction_add(self, ins: '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)
|
||||
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
reg = ins.get_reg(0)
|
||||
imm = UInt32(ins.get_imm(1) << 12)
|
||||
self.regs.set(reg, Int32(imm))
|
||||
|
||||
def instruction_auipc(self, ins: "Instruction"):
|
||||
def instruction_auipc(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 2)
|
||||
reg = ins.get_reg(0)
|
||||
imm = UInt32(ins.get_imm(1) << 12)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
if rs1 == rs2:
|
||||
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)
|
||||
if rs1 != rs2:
|
||||
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)
|
||||
if rs1 < rs2:
|
||||
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)
|
||||
if rs1 >= rs2:
|
||||
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)
|
||||
if rs1 < rs2:
|
||||
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)
|
||||
if rs1 >= rs2:
|
||||
self.pc = dst.unsigned_value
|
||||
|
||||
# technically deprecated
|
||||
def instruction_j(self, ins: "Instruction"):
|
||||
def instruction_j(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 1)
|
||||
addr = ins.get_imm(0)
|
||||
self.pc = addr
|
||||
|
||||
def instruction_jal(self, ins: "Instruction"):
|
||||
reg = "ra" # default register is ra
|
||||
def instruction_jal(self, ins: 'Instruction'):
|
||||
reg = 'ra' # default register is ra
|
||||
if len(ins.args) == 1:
|
||||
addr = ins.get_imm(0)
|
||||
else:
|
||||
@ -211,61 +266,58 @@ class RV32I(InstructionSet):
|
||||
self.regs.set(reg, Int32(self.pc))
|
||||
self.pc = addr
|
||||
|
||||
def instruction_jalr(self, ins: "Instruction"):
|
||||
def instruction_jalr(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 2)
|
||||
reg = ins.get_reg(0)
|
||||
base = ins.get_reg(1)
|
||||
addr = ins.get_imm(2)
|
||||
addr = ins.get_imm(1)
|
||||
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)
|
||||
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)
|
||||
|
||||
def instruction_ebreak(self, ins: "Instruction"):
|
||||
def instruction_ebreak(self, ins: 'Instruction'):
|
||||
self.instruction_sbreak(ins)
|
||||
|
||||
def instruction_scall(self, ins: "Instruction"):
|
||||
def instruction_scall(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 0)
|
||||
|
||||
if not isinstance(self.cpu, UserModeCPU):
|
||||
# FIXME: add exception for syscall not supported or something
|
||||
raise
|
||||
|
||||
syscall = Syscall(self.regs.get("a7"), self.cpu)
|
||||
syscall = Syscall(self.regs.get('a7'), self.cpu)
|
||||
self.cpu.syscall_int.handle_syscall(syscall)
|
||||
|
||||
def instruction_sbreak(self, ins: "Instruction"):
|
||||
def instruction_sbreak(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 0)
|
||||
if self.cpu.conf.debug_instruction:
|
||||
print(
|
||||
FMT_DEBUG
|
||||
+ "Debug instruction encountered at 0x{:08X}".format(self.pc - 1)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_DEBUG + "Debug instruction encountered at 0x{:08X}".format(self.pc - 1) + FMT_NONE)
|
||||
raise LaunchDebuggerException()
|
||||
|
||||
def instruction_nop(self, ins: "Instruction"):
|
||||
def instruction_nop(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 0)
|
||||
pass
|
||||
|
||||
def instruction_li(self, ins: "Instruction"):
|
||||
def instruction_li(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 2)
|
||||
reg = ins.get_reg(0)
|
||||
immediate = ins.get_imm(1)
|
||||
self.regs.set(reg, Int32(immediate))
|
||||
|
||||
def instruction_la(self, ins: "Instruction"):
|
||||
def instruction_la(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 2)
|
||||
reg = ins.get_reg(0)
|
||||
immediate = ins.get_imm(1)
|
||||
self.regs.set(reg, Int32(immediate))
|
||||
|
||||
def instruction_mv(self, ins: "Instruction"):
|
||||
def instruction_mv(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 2)
|
||||
rd, rs = ins.get_reg(0), ins.get_reg(1)
|
||||
self.regs.set(rd, self.regs.get(rs))
|
||||
|
||||
|
||||
|
@ -12,33 +12,50 @@ class RV32M(InstructionSet):
|
||||
"""
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
|
||||
def instruction_mulhu(self, ins: "Instruction"):
|
||||
def instruction_mulhu(self, ins: 'Instruction'):
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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)
|
||||
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
|
||||
"""
|
||||
|
||||
from .instruction_set import InstructionSet, Instruction
|
||||
from .instruction_set import InstructionSet
|
||||
from .RV32M import RV32M
|
||||
from .RV32I import RV32I
|
||||
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.
|
||||
"""
|
||||
|
||||
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
|
||||
from it such as access to the MMU and registers.
|
||||
"""
|
||||
self.name = self.__class__.__name__
|
||||
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
|
||||
|
||||
It returns a dictionary of all instructions in this instruction set,
|
||||
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):
|
||||
"""
|
||||
@ -46,10 +48,10 @@ class InstructionSet(ABC):
|
||||
converts underscores in names to dots
|
||||
"""
|
||||
for member in dir(self):
|
||||
if member.startswith("instruction_"):
|
||||
yield member[12:].replace("_", "."), getattr(self, member)
|
||||
if member.startswith('instruction_'):
|
||||
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)
|
||||
(so a register and address tuple for memory instructions)
|
||||
@ -67,69 +69,51 @@ class InstructionSet(ABC):
|
||||
rd = ins.get_reg(0)
|
||||
return rd, rs + imm
|
||||
|
||||
def parse_rd_rs_rs(
|
||||
self, ins: "Instruction", signed=True
|
||||
) -> Tuple[str, Int32, Int32]:
|
||||
def parse_rd_rs_rs(self, ins: 'Instruction', signed=True) -> Tuple[str, Int32, Int32]:
|
||||
"""
|
||||
Assumes the command is in <name> rd, rs1, rs2 format
|
||||
Returns the name of rd, and the values in rs1 and rs2
|
||||
"""
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
if signed:
|
||||
return (
|
||||
ins.get_reg(0),
|
||||
Int32(self.get_reg_content(ins, 1)),
|
||||
Int32(self.get_reg_content(ins, 2)),
|
||||
)
|
||||
return ins.get_reg(0), \
|
||||
self.get_reg_content(ins, 1), \
|
||||
self.get_reg_content(ins, 2)
|
||||
else:
|
||||
return (
|
||||
ins.get_reg(0),
|
||||
UInt32(self.get_reg_content(ins, 1)),
|
||||
UInt32(self.get_reg_content(ins, 2)),
|
||||
)
|
||||
return ins.get_reg(0), \
|
||||
UInt32(self.get_reg_content(ins, 1)), \
|
||||
UInt32(self.get_reg_content(ins, 2))
|
||||
|
||||
def parse_rd_rs_imm(
|
||||
self, ins: "Instruction", signed=True
|
||||
) -> Tuple[str, Int32, Int32]:
|
||||
def parse_rd_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[str, Int32, Int32]:
|
||||
"""
|
||||
Assumes the command is in <name> rd, rs, imm format
|
||||
Returns the name of rd, the value in rs and the immediate imm
|
||||
"""
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
if signed:
|
||||
return (
|
||||
ins.get_reg(0),
|
||||
Int32(self.get_reg_content(ins, 1)),
|
||||
Int32(ins.get_imm(2)),
|
||||
)
|
||||
return ins.get_reg(0), \
|
||||
Int32(self.get_reg_content(ins, 1)), \
|
||||
Int32(ins.get_imm(2))
|
||||
else:
|
||||
return (
|
||||
ins.get_reg(0),
|
||||
UInt32(self.get_reg_content(ins, 1)),
|
||||
UInt32(ins.get_imm(2)),
|
||||
)
|
||||
return ins.get_reg(0), \
|
||||
UInt32(self.get_reg_content(ins, 1)), \
|
||||
UInt32(ins.get_imm(2))
|
||||
|
||||
def parse_rs_rs_imm(
|
||||
self, ins: "Instruction", signed=True
|
||||
) -> Tuple[Int32, Int32, Int32]:
|
||||
def parse_rs_rs_imm(self, ins: 'Instruction', signed=True) -> Tuple[Int32, Int32, Int32]:
|
||||
"""
|
||||
Assumes the command is in <name> rs1, rs2, imm format
|
||||
Returns the values in rs1, rs2 and the immediate imm
|
||||
"""
|
||||
if signed:
|
||||
return (
|
||||
Int32(self.get_reg_content(ins, 0)),
|
||||
Int32(self.get_reg_content(ins, 1)),
|
||||
Int32(ins.get_imm(2)),
|
||||
)
|
||||
return Int32(self.get_reg_content(ins, 0)), \
|
||||
Int32(self.get_reg_content(ins, 1)), \
|
||||
Int32(ins.get_imm(2))
|
||||
else:
|
||||
return (
|
||||
UInt32(self.get_reg_content(ins, 0)),
|
||||
UInt32(self.get_reg_content(ins, 1)),
|
||||
UInt32(ins.get_imm(2)),
|
||||
)
|
||||
return UInt32(self.get_reg_content(ins, 0)), \
|
||||
UInt32(self.get_reg_content(ins, 1)), \
|
||||
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
|
||||
"""
|
||||
@ -156,5 +140,6 @@ class InstructionSet(ABC):
|
||||
|
||||
def __repr__(self):
|
||||
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.types import InstructionMemorySection, SimpleInstruction, Program
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
from .CPU import UserModeCPU
|
||||
from .instructions import InstructionSetDict
|
||||
from .debug import launch_debug_session
|
||||
|
||||
cpu = UserModeCPU(list(InstructionSetDict.values()), RunConfig(verbosity=4))
|
||||
|
||||
program = Program("interactive session", base=0x100)
|
||||
program = Program('interactive session', base=0x100)
|
||||
context = program.context
|
||||
program.add_section(
|
||||
InstructionMemorySection(
|
||||
[
|
||||
SimpleInstruction("ebreak", (), context, 0x100),
|
||||
SimpleInstruction("addi", ("a0", "zero", "0"), context, 0x104),
|
||||
SimpleInstruction("addi", ("a7", "zero", "93"), context, 0x108),
|
||||
SimpleInstruction("scall", (), context, 0x10C),
|
||||
],
|
||||
".text",
|
||||
context,
|
||||
program.name,
|
||||
0x100,
|
||||
)
|
||||
)
|
||||
program.add_section(InstructionMemorySection([
|
||||
SimpleInstruction('ebreak', (), context, 0x100),
|
||||
SimpleInstruction('addi', ('a0', 'zero', '0'), context, 0x104),
|
||||
SimpleInstruction('addi', ('a7', 'zero', '93'), context, 0x108),
|
||||
SimpleInstruction('scall', (), context, 0x10C),
|
||||
], '.text', context, program.name, 0x100))
|
||||
|
||||
cpu.load_program(program)
|
||||
|
||||
cpu.setup_stack()
|
||||
|
||||
cpu.launch()
|
||||
cpu.launch(program)
|
||||
|
||||
sys.exit(cpu.exit_code)
|
||||
|
@ -16,32 +16,28 @@ from .types.exceptions import ParseException
|
||||
|
||||
def parse_instruction(token: Token, args: Tuple[str], context: ParseContext):
|
||||
if context.section is None:
|
||||
context.new_section(".text", MemorySectionType.Instructions)
|
||||
context.new_section('.text', MemorySectionType.Instructions)
|
||||
if context.section.type != MemorySectionType.Instructions:
|
||||
raise ParseException(
|
||||
"{} {} encountered in invalid context: {}".format(token, args, context)
|
||||
)
|
||||
ins = SimpleInstruction(
|
||||
token.value, args, context.context, context.current_address()
|
||||
)
|
||||
raise ParseException("{} {} encountered in invalid context: {}".format(token, args, context))
|
||||
ins = SimpleInstruction(token.value, args, context.context, context.current_address())
|
||||
context.section.data.append(ins)
|
||||
|
||||
|
||||
def parse_label(token: Token, args: Tuple[str], context: ParseContext):
|
||||
name = token.value[:-1]
|
||||
if re.match(r"^\d+$", name):
|
||||
if re.match(r'^\d+$', name):
|
||||
# relative label:
|
||||
context.context.numbered_labels[name].append(context.current_address())
|
||||
else:
|
||||
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)
|
||||
|
||||
|
||||
PARSERS: Dict[TokenType, Callable[[Token, Tuple[str], ParseContext], None]] = {
|
||||
TokenType.PSEUDO_OP: AssemblerDirectives.handle_instruction,
|
||||
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()
|
||||
|
||||
|
||||
def composite_tokenizer(
|
||||
tokens_iter: Iterable[Token],
|
||||
) -> Iterable[Tuple[Token, Tuple[str]]]:
|
||||
def composite_tokenizer(tokens_iter: Iterable[Token]) -> Iterable[Tuple[Token, Tuple[str]]]:
|
||||
"""
|
||||
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():
|
||||
token = next(tokens)
|
||||
if token.type in (
|
||||
TokenType.PSEUDO_OP,
|
||||
TokenType.LABEL,
|
||||
TokenType.INSTRUCTION_NAME,
|
||||
):
|
||||
if token.type in (TokenType.PSEUDO_OP, TokenType.LABEL, TokenType.INSTRUCTION_NAME):
|
||||
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.
|
||||
"""
|
||||
|
||||
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))
|
||||
|
||||
def parse_io(self, io: Iterable[str]):
|
||||
return parse_tokens(self.filename, tokenize(io))
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, source_path: str) -> float:
|
||||
"""
|
||||
@ -134,10 +120,10 @@ class AssemblyFileLoader(ProgramLoader):
|
||||
:return:
|
||||
"""
|
||||
# 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 0.01
|
||||
|
||||
@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, {}
|
||||
|
@ -12,7 +12,6 @@ class CSR:
|
||||
"""
|
||||
This holds all Control and Status Registers (CSR)
|
||||
"""
|
||||
|
||||
regs: Dict[int, UInt32]
|
||||
"""
|
||||
All Control and Status Registers are stored here
|
||||
@ -53,9 +52,7 @@ class CSR:
|
||||
return self.virtual_regs[addr]()
|
||||
return self.regs[addr]
|
||||
|
||||
def set_listener(
|
||||
self, addr: Union[str, int], listener: Callable[[UInt32, UInt32], None]
|
||||
):
|
||||
def set_listener(self, addr: Union[str, int], listener: Callable[[UInt32, UInt32], None]):
|
||||
addr = self._name_to_addr(addr)
|
||||
if addr is None:
|
||||
print("unknown csr address name: {}".format(addr))
|
||||
@ -76,11 +73,11 @@ class CSR:
|
||||
"""
|
||||
size = 2 if name in MSTATUS_LEN_2 else 1
|
||||
off = MSTATUS_OFFSETS[name]
|
||||
mask = (2**size - 1) << off
|
||||
old_val = self.get("mstatus")
|
||||
mask = (2 ** size - 1) << off
|
||||
old_val = self.get('mstatus')
|
||||
erased = old_val & (~mask)
|
||||
new_val = erased | (val << off)
|
||||
self.set("mstatus", new_val)
|
||||
self.set('mstatus', new_val)
|
||||
|
||||
def get_mstatus(self, name) -> UInt32:
|
||||
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
|
||||
off = MSTATUS_OFFSETS[name]
|
||||
mask = (2**size - 1) << off
|
||||
val = (self.get("mstatus") & mask) >> off
|
||||
mask = (2 ** size - 1) << off
|
||||
val = (self.get('mstatus') & mask) >> off
|
||||
if self.mstatus_cache_dirty:
|
||||
self.mstatus_cache = dict(name=val)
|
||||
else:
|
||||
|
@ -1,81 +1,81 @@
|
||||
from typing import Dict, Tuple
|
||||
|
||||
MCAUSE_TRANSLATION: Dict[Tuple[int, int], str] = {
|
||||
(1, 0): "User software interrupt",
|
||||
(1, 1): "Supervisor software interrupt",
|
||||
(1, 3): "Machine software interrupt",
|
||||
(1, 4): "User timer interrupt",
|
||||
(1, 5): "Supervisor timer interrupt",
|
||||
(1, 7): "Machine timer interrupt",
|
||||
(1, 8): "User external interrupt",
|
||||
(1, 9): "Supervisor external interrupt",
|
||||
(1, 11): "Machine external interrupt",
|
||||
(0, 0): "Instruction address misaligned",
|
||||
(0, 1): "Instruction access fault",
|
||||
(0, 2): "Illegal instruction",
|
||||
(0, 3): "Breakpoint",
|
||||
(0, 4): "Load address misaligned",
|
||||
(0, 5): "Load access fault",
|
||||
(0, 6): "Store/AMO address misaligned",
|
||||
(0, 7): "Store/AMO access fault",
|
||||
(0, 8): "environment call from user mode",
|
||||
(0, 9): "environment call from supervisor mode",
|
||||
(0, 11): "environment call from machine mode",
|
||||
(0, 12): "Instruction page fault",
|
||||
(0, 13): "Load page fault",
|
||||
(0, 15): "Store/AMO page fault",
|
||||
MCAUSE_TRANSLATION: Dict[Tuple[int, int], str]= {
|
||||
(1, 0): 'User software interrupt',
|
||||
(1, 1): 'Supervisor software interrupt',
|
||||
(1, 3): 'Machine software interrupt',
|
||||
(1, 4): 'User timer interrupt',
|
||||
(1, 5): 'Supervisor timer interrupt',
|
||||
(1, 7): 'Machine timer interrupt',
|
||||
(1, 8): 'User external interrupt',
|
||||
(1, 9): 'Supervisor external interrupt',
|
||||
(1, 11): 'Machine external interrupt',
|
||||
(0, 0): 'Instruction address misaligned',
|
||||
(0, 1): 'Instruction access fault',
|
||||
(0, 2): 'Illegal instruction',
|
||||
(0, 3): 'Breakpoint',
|
||||
(0, 4): 'Load address misaligned',
|
||||
(0, 5): 'Load access fault',
|
||||
(0, 6): 'Store/AMO address misaligned',
|
||||
(0, 7): 'Store/AMO access fault',
|
||||
(0, 8): 'environment call from user mode',
|
||||
(0, 9): 'environment call from supervisor mode',
|
||||
(0, 11): 'environment call from machine mode',
|
||||
(0, 12): 'Instruction page fault',
|
||||
(0, 13): 'Load page fault',
|
||||
(0, 15): 'Store/AMO page fault',
|
||||
}
|
||||
"""
|
||||
Assigns tuple (interrupt bit, exception code) to their respective readable names
|
||||
"""
|
||||
|
||||
MSTATUS_OFFSETS: Dict[str, int] = {
|
||||
"uie": 0,
|
||||
"sie": 1,
|
||||
"mie": 3,
|
||||
"upie": 4,
|
||||
"spie": 5,
|
||||
"mpie": 7,
|
||||
"spp": 8,
|
||||
"mpp": 11,
|
||||
"fs": 13,
|
||||
"xs": 15,
|
||||
"mpriv": 17,
|
||||
"sum": 18,
|
||||
"mxr": 19,
|
||||
"tvm": 20,
|
||||
"tw": 21,
|
||||
"tsr": 22,
|
||||
"sd": 31,
|
||||
'uie': 0,
|
||||
'sie': 1,
|
||||
'mie': 3,
|
||||
'upie': 4,
|
||||
'spie': 5,
|
||||
'mpie': 7,
|
||||
'spp': 8,
|
||||
'mpp': 11,
|
||||
'fs': 13,
|
||||
'xs': 15,
|
||||
'mpriv': 17,
|
||||
'sum': 18,
|
||||
'mxr': 19,
|
||||
'tvm': 20,
|
||||
'tw': 21,
|
||||
'tsr': 22,
|
||||
'sd': 31
|
||||
}
|
||||
"""
|
||||
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
|
||||
"""
|
||||
|
||||
CSR_NAME_TO_ADDR: Dict[str, int] = {
|
||||
"mstatus": 0x300,
|
||||
"misa": 0x301,
|
||||
"mie": 0x304,
|
||||
"mtvec": 0x305,
|
||||
"mepc": 0x341,
|
||||
"mcause": 0x342,
|
||||
"mtval": 0x343,
|
||||
"mip": 0x344,
|
||||
"mvendorid": 0xF11,
|
||||
"marchid": 0xF12,
|
||||
"mimpid": 0xF13,
|
||||
"mhartid": 0xF14,
|
||||
"time": 0xC01,
|
||||
"timeh": 0xC81,
|
||||
"halt": 0x789,
|
||||
"mtimecmp": 0x780,
|
||||
"mtimecmph": 0x781,
|
||||
'mstatus': 0x300,
|
||||
'misa': 0x301,
|
||||
'mie': 0x304,
|
||||
'mtvec': 0x305,
|
||||
'mepc': 0x341,
|
||||
'mcause': 0x342,
|
||||
'mtval': 0x343,
|
||||
'mip': 0x344,
|
||||
'mvendorid': 0xF11,
|
||||
'marchid': 0xF12,
|
||||
'mimpid': 0xF13,
|
||||
'mhartid': 0xF14,
|
||||
'time': 0xc01,
|
||||
'timeh': 0xc81,
|
||||
'halt': 0x789,
|
||||
'mtimecmp': 0x780,
|
||||
'mtimecmph': 0x781,
|
||||
}
|
||||
"""
|
||||
Translation for named registers
|
||||
"""
|
||||
"""
|
@ -11,7 +11,7 @@ if typing.TYPE_CHECKING:
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from elftools.elf.sections import Section, SymbolTableSection
|
||||
|
||||
INCLUDE_SEC = (".text", ".stack", ".bss", ".sdata", ".sbss")
|
||||
INCLUDE_SEC = ('.text', '.stack', '.bss', '.sdata', '.sbss')
|
||||
|
||||
|
||||
class ElfBinaryFileLoader(ProgramLoader):
|
||||
@ -20,7 +20,6 @@ class ElfBinaryFileLoader(ProgramLoader):
|
||||
|
||||
This loader respects local and global symbols.
|
||||
"""
|
||||
|
||||
program: Program
|
||||
|
||||
def __init__(self, source_path: str, options: T_ParserOpts):
|
||||
@ -29,8 +28,8 @@ class ElfBinaryFileLoader(ProgramLoader):
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, source_path: str) -> float:
|
||||
with open(source_path, "rb") as f:
|
||||
if f.read(4) == b"\x7f\x45\x4c\x46":
|
||||
with open(source_path, 'rb') as f:
|
||||
if f.read(4) == b'\x7f\x45\x4c\x46':
|
||||
return 1
|
||||
return 0
|
||||
|
||||
@ -43,33 +42,23 @@ class ElfBinaryFileLoader(ProgramLoader):
|
||||
from elftools.elf.elffile import ELFFile
|
||||
from elftools.elf.sections import Section, SymbolTableSection
|
||||
|
||||
with open(self.source_path, "rb") as f:
|
||||
print(
|
||||
FMT_ELF
|
||||
+ "[ElfLoader] Loading elf executable from: {}".format(
|
||||
self.source_path
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
with open(self.source_path, 'rb') as f:
|
||||
print(FMT_ELF + "[ElfLoader] Loading elf executable from: {}".format(self.source_path) + FMT_NONE)
|
||||
self._read_elf(ELFFile(f))
|
||||
except ImportError as e:
|
||||
print(
|
||||
FMT_PARSE
|
||||
+ "[ElfLoader] Cannot load elf files without PyElfTools package! You can install them "
|
||||
"using pip install pyelftools!" + FMT_NONE
|
||||
)
|
||||
print(FMT_PARSE + "[ElfLoader] Cannot load elf files without PyElfTools package! You can install them "
|
||||
"using pip install pyelftools!" + FMT_NONE)
|
||||
raise e
|
||||
|
||||
return self.program
|
||||
|
||||
def _read_elf(self, elf: "ELFFile"):
|
||||
if not elf.header.e_machine == "EM_RISCV":
|
||||
def _read_elf(self, elf: 'ELFFile'):
|
||||
if not elf.header.e_machine == 'EM_RISCV':
|
||||
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!")
|
||||
|
||||
from elftools.elf.sections import SymbolTableSection
|
||||
|
||||
for sec in elf.iter_sections():
|
||||
if isinstance(sec, SymbolTableSection):
|
||||
self._parse_symtab(sec)
|
||||
@ -80,22 +69,18 @@ class ElfBinaryFileLoader(ProgramLoader):
|
||||
|
||||
self._add_sec(self._lms_from_elf_sec(sec, self.filename))
|
||||
|
||||
def _lms_from_elf_sec(self, sec: "Section", owner: str):
|
||||
is_code = sec.name in (".text",)
|
||||
def _lms_from_elf_sec(self, sec: 'Section', owner: str):
|
||||
is_code = sec.name in ('.text',)
|
||||
data = bytearray(sec.data())
|
||||
if len(data) < sec.data_size:
|
||||
data += bytearray(len(data) - sec.data_size)
|
||||
flags = MemoryFlags(is_code, is_code)
|
||||
print(
|
||||
FMT_ELF
|
||||
+ "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_ELF + "[ElfLoader] Section {} at: {:X}".format(sec.name, sec.header.sh_addr) + FMT_NONE)
|
||||
return ElfMemorySection(
|
||||
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
|
||||
|
||||
for sym in symtab.iter_symbols():
|
||||
@ -103,26 +88,18 @@ class ElfBinaryFileLoader(ProgramLoader):
|
||||
continue
|
||||
self.program.context.labels[sym.name] = sym.entry.st_value
|
||||
# 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)
|
||||
print(
|
||||
FMT_PARSE
|
||||
+ "LOADED GLOBAL SYMBOL {}: {}".format(sym.name, sym.entry.st_value)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(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:
|
||||
if sec.base < sec.end <= new_sec.base or sec.end > sec.base >= new_sec.end:
|
||||
continue
|
||||
else:
|
||||
print(
|
||||
FMT_ELF
|
||||
+ "[ElfLoader] Invalid elf layout: Two sections overlap: \n\t{}\n\t{}".format(
|
||||
sec, new_sec
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_ELF + "[ElfLoader] Invalid elf layout: Two sections overlap: \n\t{}\n\t{}".format(
|
||||
sec, new_sec
|
||||
) + FMT_NONE)
|
||||
raise RuntimeError("Cannot load elf with overlapping sections!")
|
||||
|
||||
self.program.add_section(new_sec)
|
||||
|
@ -42,12 +42,10 @@ class CpuTrap(BaseException):
|
||||
|
||||
priv: PrivModes
|
||||
"""
|
||||
The privilege level this trap targets
|
||||
The privilege level this trap targets
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self, code: int, mtval, type: CpuTrapType, priv: PrivModes = PrivModes.MACHINE
|
||||
):
|
||||
def __init__(self, code: int, mtval, type: CpuTrapType, priv: PrivModes = PrivModes.MACHINE):
|
||||
self.interrupt = 0 if type == CpuTrapType.EXCEPTION else 1
|
||||
self.code = code
|
||||
self.mtval = UInt32(mtval)
|
||||
@ -65,9 +63,7 @@ class CpuTrap(BaseException):
|
||||
name = "Reserved interrupt({}, {})".format(self.interrupt, self.code)
|
||||
|
||||
if (self.interrupt, self.code) in MCAUSE_TRANSLATION:
|
||||
name = MCAUSE_TRANSLATION[(self.interrupt, self.code)] + "({}, {})".format(
|
||||
self.interrupt, self.code
|
||||
)
|
||||
name = MCAUSE_TRANSLATION[(self.interrupt, self.code)] + "({}, {})".format(self.interrupt, self.code)
|
||||
|
||||
return "{} {{priv={}, type={}, mtval={:x}}} {}".format(
|
||||
name, self.priv.name, self.type.name, self.mtval, self.message()
|
||||
@ -78,7 +74,7 @@ class CpuTrap(BaseException):
|
||||
|
||||
|
||||
class IllegalInstructionTrap(CpuTrap):
|
||||
def __init__(self, ins: "ElfInstruction"):
|
||||
def __init__(self, ins: 'ElfInstruction'):
|
||||
super().__init__(2, ins.encoded, CpuTrapType.EXCEPTION)
|
||||
|
||||
|
||||
@ -108,9 +104,7 @@ class InvalidElfException(RiscemuBaseException):
|
||||
self.msg = msg
|
||||
|
||||
def message(self):
|
||||
return (
|
||||
FMT_PARSE + '{}("{}")'.format(self.__class__.__name__, self.msg) + FMT_NONE
|
||||
)
|
||||
return FMT_PARSE + "{}(\"{}\")".format(self.__class__.__name__, self.msg) + FMT_NONE
|
||||
|
||||
|
||||
class LoadAccessFault(CpuTrap):
|
||||
@ -123,5 +117,8 @@ class LoadAccessFault(CpuTrap):
|
||||
|
||||
def message(self):
|
||||
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):
|
||||
|
||||
@classmethod
|
||||
def can_parse(cls, source_path: str) -> float:
|
||||
if source_path.split(".")[-1] == "img":
|
||||
if source_path.split('.')[-1] == 'img':
|
||||
return 1
|
||||
return 0
|
||||
|
||||
@ -25,14 +26,14 @@ class MemoryImageLoader(ProgramLoader):
|
||||
return argv, {}
|
||||
|
||||
def parse(self) -> Iterable[Program]:
|
||||
if "debug" not in self.options:
|
||||
if 'debug' not in self.options:
|
||||
yield self.parse_no_debug()
|
||||
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())
|
||||
|
||||
with open(self.source_path, "rb") as source_file:
|
||||
with open(self.source_path, 'rb') as source_file:
|
||||
data: bytearray = bytearray(source_file.read())
|
||||
|
||||
for name, sections in debug_info.sections.items():
|
||||
@ -42,15 +43,11 @@ class MemoryImageLoader(ProgramLoader):
|
||||
if program.base is None:
|
||||
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(
|
||||
ElfMemorySection(
|
||||
data[start : start + size],
|
||||
sec_name,
|
||||
program.context,
|
||||
name,
|
||||
start,
|
||||
MemoryFlags(False, True),
|
||||
data[start:start+size], sec_name, program.context,
|
||||
name, start, MemoryFlags(False, True)
|
||||
)
|
||||
)
|
||||
|
||||
@ -60,27 +57,19 @@ class MemoryImageLoader(ProgramLoader):
|
||||
yield program
|
||||
|
||||
def parse_no_debug(self) -> Program:
|
||||
print(
|
||||
FMT_PARSE
|
||||
+ "[MemoryImageLoader] Warning: loading memory image without debug information!"
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(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()
|
||||
|
||||
p = Program(self.filename)
|
||||
p.add_section(
|
||||
ElfMemorySection(
|
||||
bytearray(data), ".text", p.context, p.name, 0, MemoryFlags(False, True)
|
||||
)
|
||||
)
|
||||
p.add_section(ElfMemorySection(
|
||||
bytearray(data), '.text', p.context, p.name, 0, MemoryFlags(False, True)
|
||||
))
|
||||
return p
|
||||
|
||||
@classmethod
|
||||
def instantiate(cls, source_path: str, options: T_ParserOpts) -> "ProgramLoader":
|
||||
if os.path.isfile(source_path + ".dbg"):
|
||||
return MemoryImageLoader(
|
||||
source_path, dict(**options, debug=source_path + ".dbg")
|
||||
)
|
||||
def instantiate(cls, source_path: str, options: T_ParserOpts) -> 'ProgramLoader':
|
||||
if os.path.isfile(source_path + '.dbg'):
|
||||
return MemoryImageLoader(source_path, dict(**options, debug=source_path + '.dbg'))
|
||||
return MemoryImageLoader(source_path, options)
|
||||
|
@ -87,11 +87,7 @@ class PrivCPU(CPU):
|
||||
|
||||
if self.halted:
|
||||
print()
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "[CPU] System halted with code {}".format(self.exit_code)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_CPU + "[CPU] System halted with code {}".format(self.exit_code) + FMT_NONE)
|
||||
sys.exit(self.exit_code)
|
||||
|
||||
elif launch_debug:
|
||||
@ -100,68 +96,55 @@ class PrivCPU(CPU):
|
||||
self.run(verbose)
|
||||
else:
|
||||
print()
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "[CPU] System stopped without halting - perhaps you stopped the debugger?"
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_CPU + "[CPU] System stopped without halting - perhaps you stopped the debugger?" + FMT_NONE)
|
||||
|
||||
def launch(self, program: Optional[Program] = None, verbose: bool = False):
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "[CPU] Started running from 0x{:08X} ({})".format(self.pc, "kernel")
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(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.run(self.conf.verbosity > 1 or verbose)
|
||||
|
||||
def load_program(self, program: Program):
|
||||
if program.name == "kernel":
|
||||
if program.name == 'kernel':
|
||||
self.pc = program.entrypoint
|
||||
super().load_program(program)
|
||||
|
||||
def _init_csr(self):
|
||||
# set up CSR
|
||||
self.csr = CSR()
|
||||
self.csr.set("mhartid", UInt32(0)) # core id
|
||||
self.csr.set('mhartid', UInt32(0)) # core id
|
||||
# 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
|
||||
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:
|
||||
|
||||
@self.csr.callback("halt")
|
||||
@self.csr.callback('halt')
|
||||
def halt(old: UInt32, new: UInt32):
|
||||
if new != 0:
|
||||
self.halted = True
|
||||
self.exit_code = new.value
|
||||
|
||||
@self.csr.callback("mtimecmp")
|
||||
@self.csr.callback('mtimecmp')
|
||||
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.csr.callback("mtimecmph")
|
||||
@self.csr.callback('mtimecmph')
|
||||
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
|
||||
|
||||
# virtual CSR registers:
|
||||
|
||||
@self.csr.virtual_register("time")
|
||||
@self.csr.virtual_register('time')
|
||||
def get_time():
|
||||
return UInt32(
|
||||
time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start
|
||||
)
|
||||
return UInt32(time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start)
|
||||
|
||||
@self.csr.virtual_register("timeh")
|
||||
@self.csr.virtual_register('timeh')
|
||||
def get_timeh():
|
||||
return UInt32(
|
||||
(time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start)
|
||||
>> 32
|
||||
)
|
||||
return UInt32((time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) >> 32)
|
||||
|
||||
# add minstret and mcycle counters
|
||||
|
||||
@ -177,21 +160,17 @@ class PrivCPU(CPU):
|
||||
self._check_interrupt()
|
||||
ins = self.mmu.read_ins(self.pc)
|
||||
if verbose and (self.mode == PrivModes.USER or self.conf.verbosity > 4):
|
||||
print(
|
||||
FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)
|
||||
)
|
||||
print(FMT_CPU + " Running 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins))
|
||||
self.run_instruction(ins)
|
||||
self.pc += self.INS_XLEN
|
||||
except CpuTrap as trap:
|
||||
self._handle_trap(trap)
|
||||
if trap.interrupt == 0 and not isinstance(trap, EcallTrap):
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "[CPU] Trap {} encountered at {} (0x{:x})".format(
|
||||
trap, self.mmu.translate_address(self.pc), self.pc
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_CPU + "[CPU] Trap {} encountered at {} (0x{:x})".format(
|
||||
trap,
|
||||
self.mmu.translate_address(self.pc),
|
||||
self.pc
|
||||
) + FMT_NONE)
|
||||
breakpoint()
|
||||
if self.conf.debug_on_exception:
|
||||
raise LaunchDebuggerException()
|
||||
@ -200,15 +179,12 @@ class PrivCPU(CPU):
|
||||
def _timer_step(self):
|
||||
if not self._time_interrupt_enabled:
|
||||
return
|
||||
if (
|
||||
self._time_timecmp
|
||||
<= (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start
|
||||
):
|
||||
if self._time_timecmp <= (time.perf_counter_ns() // self.TIME_RESOLUTION_NS) - self._time_start:
|
||||
self.pending_traps.append(TimerInterrupt())
|
||||
self._time_interrupt_enabled = False
|
||||
|
||||
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
|
||||
# select best interrupt
|
||||
# FIXME: actually select based on the official ranking
|
||||
@ -218,30 +194,24 @@ class PrivCPU(CPU):
|
||||
self.regs.dump_reg_a()
|
||||
|
||||
if trap.priv != PrivModes.MACHINE:
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!"
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_CPU + "[CPU] Trap not targeting machine mode encountered! - undefined behaviour!" + FMT_NONE)
|
||||
raise Exception("Undefined behaviour!")
|
||||
|
||||
if self.mode != PrivModes.USER:
|
||||
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("mpp", self.mode.value)
|
||||
self.csr.set_mstatus("mie", UInt32(0))
|
||||
self.csr.set("mcause", trap.mcause)
|
||||
self.csr.set("mepc", self.pc - self.INS_XLEN)
|
||||
self.csr.set("mtval", trap.mtval)
|
||||
self.csr.set_mstatus('mpie', self.csr.get_mstatus('mie'))
|
||||
self.csr.set_mstatus('mpp', self.mode.value)
|
||||
self.csr.set_mstatus('mie', UInt32(0))
|
||||
self.csr.set('mcause', trap.mcause)
|
||||
self.csr.set('mepc', self.pc - self.INS_XLEN)
|
||||
self.csr.set('mtval', trap.mtval)
|
||||
self.mode = trap.priv
|
||||
mtvec = self.csr.get("mtvec")
|
||||
mtvec = self.csr.get('mtvec')
|
||||
if mtvec & 0b11 == 0:
|
||||
self.pc = mtvec.value
|
||||
if mtvec & 0b11 == 1:
|
||||
self.pc = (
|
||||
(mtvec & 0b11111111111111111111111111111100) + (trap.code * 4)
|
||||
).value
|
||||
self.pc = ((mtvec & 0b11111111111111111111111111111100) + (trap.code * 4)).value
|
||||
self.record_perf_profile()
|
||||
if len(self._perf_counters) > 100:
|
||||
self.show_perf()
|
||||
@ -262,10 +232,7 @@ class PrivCPU(CPU):
|
||||
cycled = cycle
|
||||
timed = time_ns
|
||||
cps_list.append(cps)
|
||||
print(
|
||||
" on average {:.0f} instructions/s".format(sum(cps_list) / len(cps_list))
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(" on average {:.0f} instructions/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE)
|
||||
self._perf_counters = list()
|
||||
|
||||
def record_perf_profile(self):
|
||||
@ -273,4 +240,6 @@ class PrivCPU(CPU):
|
||||
|
||||
@classmethod
|
||||
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):
|
||||
|
||||
def get_sec_containing(self, addr: T_AbsoluteAddress) -> MemorySection:
|
||||
# try to get an existing section
|
||||
existing_sec = super().get_sec_containing(addr)
|
||||
@ -17,9 +18,7 @@ class PrivMMU(MMU):
|
||||
return existing_sec
|
||||
|
||||
# get section preceding empty space at addr
|
||||
sec_before = next(
|
||||
(sec for sec in reversed(self.sections) if sec.end < addr), None
|
||||
)
|
||||
sec_before = next((sec for sec in reversed(self.sections) if sec.end < addr), None)
|
||||
# get sec succeeding empty space at addr
|
||||
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 = min(next_sec_start, align_addr(addr + 0xFFFF, 16))
|
||||
|
||||
sec = ElfMemorySection(
|
||||
bytearray(end - start),
|
||||
".empty",
|
||||
self.global_instruction_context(),
|
||||
"",
|
||||
start,
|
||||
MemoryFlags(False, True),
|
||||
)
|
||||
sec = ElfMemorySection(bytearray(end - start), '.empty', self.global_instruction_context(), '', start, MemoryFlags(False, True))
|
||||
self.sections.append(sec)
|
||||
self._update_state()
|
||||
|
||||
@ -48,4 +40,4 @@ class PrivMMU(MMU):
|
||||
def global_instruction_context(self) -> InstructionContext:
|
||||
context = InstructionContext()
|
||||
context.global_symbol_dict = self.global_symbols
|
||||
return context
|
||||
return context
|
@ -16,159 +16,155 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
|
||||
class PrivRV32I(RV32I):
|
||||
cpu: "PrivCPU"
|
||||
cpu: 'PrivCPU'
|
||||
"""
|
||||
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)
|
||||
old_val = None
|
||||
if rd != "zero":
|
||||
if rd != 'zero':
|
||||
self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr)
|
||||
old_val = self.cpu.csr.get(csr_addr)
|
||||
if rs != "zero":
|
||||
if rs != 'zero':
|
||||
new_val = self.regs.get(rs)
|
||||
self.cpu.csr.assert_can_write(self.cpu.mode, csr_addr)
|
||||
self.cpu.csr.set(csr_addr, new_val)
|
||||
if old_val is not None:
|
||||
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)
|
||||
if rs != "zero":
|
||||
if rs != 'zero':
|
||||
# oh no, this should not happen!
|
||||
INS_NOT_IMPLEMENTED(ins)
|
||||
if rd != "zero":
|
||||
if rd != 'zero':
|
||||
self.cpu.csr.assert_can_read(self.cpu.mode, csr_addr)
|
||||
old_val = self.cpu.csr.get(csr_addr)
|
||||
self.regs.set(rd, old_val)
|
||||
|
||||
def instruction_csrrc(self, ins: "Instruction"):
|
||||
def instruction_csrrc(self, ins: 'Instruction'):
|
||||
INS_NOT_IMPLEMENTED(ins)
|
||||
|
||||
def instruction_csrrsi(self, ins: "Instruction"):
|
||||
def instruction_csrrsi(self, ins: 'Instruction'):
|
||||
INS_NOT_IMPLEMENTED(ins)
|
||||
|
||||
def instruction_csrrwi(self, ins: "Instruction"):
|
||||
def instruction_csrrwi(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
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)
|
||||
old_val = self.cpu.csr.get(addr)
|
||||
self.regs.set(rd, old_val)
|
||||
self.cpu.csr.assert_can_write(self.cpu.mode, addr)
|
||||
self.cpu.csr.set(addr, imm)
|
||||
|
||||
def instruction_csrrci(self, ins: "Instruction"):
|
||||
def instruction_csrrci(self, ins: 'Instruction'):
|
||||
INS_NOT_IMPLEMENTED(ins)
|
||||
|
||||
def instruction_mret(self, ins: "Instruction"):
|
||||
def instruction_mret(self, ins: 'Instruction'):
|
||||
if self.cpu.mode != PrivModes.MACHINE:
|
||||
print("MRET not inside machine level code!")
|
||||
raise IllegalInstructionTrap(ins)
|
||||
# retore mie
|
||||
mpie = self.cpu.csr.get_mstatus("mpie")
|
||||
self.cpu.csr.set_mstatus("mie", mpie)
|
||||
mpie = self.cpu.csr.get_mstatus('mpie')
|
||||
self.cpu.csr.set_mstatus('mie', mpie)
|
||||
# restore priv
|
||||
mpp = self.cpu.csr.get_mstatus("mpp")
|
||||
mpp = self.cpu.csr.get_mstatus('mpp')
|
||||
self.cpu.mode = PrivModes(mpp)
|
||||
# restore pc
|
||||
mepc = self.cpu.csr.get("mepc")
|
||||
mepc = self.cpu.csr.get('mepc')
|
||||
self.cpu.pc = (mepc - self.cpu.INS_XLEN).value
|
||||
|
||||
if self.cpu.conf.verbosity > 0:
|
||||
sec = self.mmu.get_sec_containing(mepc.value)
|
||||
if sec is not None:
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "[CPU] returning to mode {} in {} (0x{:x})".format(
|
||||
PrivModes(mpp).name, self.mmu.translate_address(mepc), mepc
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_CPU + "[CPU] returning to mode {} in {} (0x{:x})".format(
|
||||
PrivModes(mpp).name,
|
||||
self.mmu.translate_address(mepc),
|
||||
mepc
|
||||
) + FMT_NONE)
|
||||
if self.cpu.conf.verbosity > 1:
|
||||
self.regs.dump_reg_a()
|
||||
|
||||
def instruction_uret(self, ins: "Instruction"):
|
||||
def instruction_uret(self, ins: 'Instruction'):
|
||||
raise IllegalInstructionTrap(ins)
|
||||
|
||||
def instruction_sret(self, ins: "Instruction"):
|
||||
def instruction_sret(self, ins: 'Instruction'):
|
||||
raise IllegalInstructionTrap(ins)
|
||||
|
||||
def instruction_scall(self, ins: "Instruction"):
|
||||
def instruction_scall(self, ins: 'Instruction'):
|
||||
"""
|
||||
Overwrite the scall from userspace RV32I
|
||||
"""
|
||||
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)
|
||||
if rs1 == rs2:
|
||||
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)
|
||||
if rs1 != rs2:
|
||||
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)
|
||||
if rs1 < rs2:
|
||||
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)
|
||||
if rs1 >= rs2:
|
||||
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)
|
||||
if rs1 < rs2:
|
||||
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)
|
||||
if rs1 >= rs2:
|
||||
self.pc += dst.value - 4
|
||||
|
||||
# technically deprecated
|
||||
def instruction_j(self, ins: "Instruction"):
|
||||
def instruction_j(self, ins: 'Instruction'):
|
||||
raise NotImplementedError("Should never be reached!")
|
||||
|
||||
def instruction_jal(self, ins: "Instruction"):
|
||||
def instruction_jal(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 2)
|
||||
reg = ins.get_reg(0)
|
||||
addr = ins.get_imm(1)
|
||||
if reg == "ra" and (
|
||||
(self.cpu.mode == PrivModes.USER and self.cpu.conf.verbosity > 1)
|
||||
or (self.cpu.conf.verbosity > 3)
|
||||
if reg == 'ra' and (
|
||||
(self.cpu.mode == PrivModes.USER and self.cpu.conf.verbosity > 1) or
|
||||
(self.cpu.conf.verbosity > 3)
|
||||
):
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "Jumping from 0x{:x} to {} (0x{:x})".format(
|
||||
self.pc, self.mmu.translate_address(self.pc + addr), self.pc + addr
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_CPU + 'Jumping from 0x{:x} to {} (0x{:x})'.format(
|
||||
self.pc,
|
||||
self.mmu.translate_address(self.pc + addr),
|
||||
self.pc + addr
|
||||
) + FMT_NONE)
|
||||
self.regs.dump_reg_a()
|
||||
self.regs.set(reg, Int32(self.pc))
|
||||
self.pc += addr - 4
|
||||
|
||||
def instruction_jalr(self, ins: "Instruction"):
|
||||
def instruction_jalr(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
rd, rs, imm = self.parse_rd_rs_imm(ins)
|
||||
self.regs.set(rd, Int32(self.pc))
|
||||
self.pc = rs.value + imm.value - 4
|
||||
|
||||
def instruction_sbreak(self, ins: "Instruction"):
|
||||
def instruction_sbreak(self, ins: 'Instruction'):
|
||||
raise LaunchDebuggerException()
|
||||
|
||||
def parse_crs_ins(self, ins: "Instruction"):
|
||||
def parse_crs_ins(self, ins: 'Instruction'):
|
||||
ASSERT_LEN(ins.args, 3)
|
||||
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)
|
||||
addr = self.get_reg_content(ins, 1) + ins.get_imm(2)
|
||||
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.
|
||||
"""
|
||||
"""
|
@ -4,55 +4,27 @@ from .PrivCPU import PrivCPU
|
||||
|
||||
import sys
|
||||
|
||||
if __name__ == "__main__":
|
||||
if __name__ == '__main__':
|
||||
import argparse
|
||||
|
||||
parser = argparse.ArgumentParser(
|
||||
description="RISC-V privileged architecture emulator", prog="riscemu"
|
||||
)
|
||||
parser = argparse.ArgumentParser(description='RISC-V privileged architecture emulator', prog='riscemu')
|
||||
|
||||
parser.add_argument(
|
||||
"source",
|
||||
type=str,
|
||||
help="Compiled RISC-V ELF file or memory image containing compiled RISC-V ELF files",
|
||||
nargs="+",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--debug-exceptions",
|
||||
help="Launch the interactive debugger when an exception is generated",
|
||||
action="store_true",
|
||||
)
|
||||
parser.add_argument('source', type=str,
|
||||
help='Compiled RISC-V ELF file or memory image containing compiled RISC-V ELF files', nargs='+')
|
||||
parser.add_argument('--debug-exceptions', help='Launch the interactive debugger when an exception is generated',
|
||||
action='store_true')
|
||||
|
||||
parser.add_argument(
|
||||
"-v",
|
||||
"--verbose",
|
||||
help="Verbosity level (can be used multiple times)",
|
||||
action="count",
|
||||
default=0,
|
||||
)
|
||||
parser.add_argument('-v', '--verbose', help="Verbosity level (can be used multiple times)", action='count',
|
||||
default=0)
|
||||
|
||||
parser.add_argument(
|
||||
"--slowdown",
|
||||
help="Slow down the emulated CPU clock by a factor",
|
||||
type=float,
|
||||
default=1,
|
||||
)
|
||||
parser.add_argument('--slowdown', help="Slow down the emulated CPU clock by a factor", type=float, default=1)
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
cpu = PrivCPU(
|
||||
RunConfig(
|
||||
verbosity=args.verbose,
|
||||
debug_on_exception=args.debug_exceptions,
|
||||
slowdown=args.slowdown,
|
||||
)
|
||||
)
|
||||
cpu = PrivCPU(RunConfig(verbosity=args.verbose, debug_on_exception=args.debug_exceptions, slowdown=args.slowdown))
|
||||
|
||||
for source_path in args.source:
|
||||
loader = max(
|
||||
(loader for loader in cpu.get_loaders()),
|
||||
key=lambda l: l.can_parse(source_path),
|
||||
)
|
||||
loader = max((loader for loader in cpu.get_loaders()), key=lambda l: l.can_parse(source_path))
|
||||
argv, opts = loader.get_options(sys.argv)
|
||||
program = loader.instantiate(source_path, opts).parse()
|
||||
if isinstance(program, Program):
|
||||
|
@ -6,19 +6,9 @@ from typing import Tuple, Dict, Set
|
||||
|
||||
from riscemu.colors import FMT_NONE, FMT_PARSE
|
||||
from riscemu.decoder import format_ins, RISCV_REGS, decode
|
||||
from riscemu.priv.Exceptions import (
|
||||
InstructionAccessFault,
|
||||
InstructionAddressMisalignedTrap,
|
||||
LoadAccessFault,
|
||||
)
|
||||
from riscemu.types import (
|
||||
Instruction,
|
||||
InstructionContext,
|
||||
T_RelativeAddress,
|
||||
MemoryFlags,
|
||||
T_AbsoluteAddress,
|
||||
BinaryDataMemorySection,
|
||||
)
|
||||
from riscemu.priv.Exceptions import InstructionAccessFault, InstructionAddressMisalignedTrap, LoadAccessFault
|
||||
from riscemu.types import Instruction, InstructionContext, T_RelativeAddress, MemoryFlags, T_AbsoluteAddress, \
|
||||
BinaryDataMemorySection
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
@ -37,45 +27,34 @@ class ElfInstruction(Instruction):
|
||||
return RISCV_REGS[self.args[num]]
|
||||
|
||||
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])
|
||||
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))
|
||||
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])
|
||||
if self.name == "ret" and len(self.args) == 0:
|
||||
if self.name == 'ret' and len(self.args) == 0:
|
||||
return "ret"
|
||||
return format_ins(self.encoded, self.name)
|
||||
|
||||
|
||||
class ElfMemorySection(BinaryDataMemorySection):
|
||||
def __init__(
|
||||
self,
|
||||
data: bytearray,
|
||||
name: str,
|
||||
context: InstructionContext,
|
||||
owner: str,
|
||||
base: int,
|
||||
flags: MemoryFlags,
|
||||
):
|
||||
def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int,
|
||||
flags: MemoryFlags):
|
||||
super().__init__(data, name, context, owner, base=base, flags=flags)
|
||||
self.read_ins = lru_cache(maxsize=self.size // 4)(self.read_ins)
|
||||
|
||||
def read_ins(self, offset):
|
||||
if not self.flags.executable:
|
||||
print(
|
||||
FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE
|
||||
)
|
||||
print(FMT_PARSE + "Reading instruction from non-executable memory!" + FMT_NONE)
|
||||
raise InstructionAccessFault(offset + self.base)
|
||||
if offset % 4 != 0:
|
||||
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):
|
||||
if self.flags.read_only:
|
||||
raise LoadAccessFault(
|
||||
"read-only section", offset + self.base, size, "write"
|
||||
)
|
||||
raise LoadAccessFault('read-only section', offset + self.base, size, 'write')
|
||||
self.read_ins.cache_clear()
|
||||
return super(ElfMemorySection, self).write(offset, size, data)
|
||||
|
||||
@ -85,7 +64,7 @@ class ElfMemorySection(BinaryDataMemorySection):
|
||||
|
||||
|
||||
class MemoryImageDebugInfos:
|
||||
VERSION = "1.0.0"
|
||||
VERSION = '1.0.0'
|
||||
"""
|
||||
Schema version
|
||||
"""
|
||||
@ -110,13 +89,12 @@ class MemoryImageDebugInfos:
|
||||
This dictionary contains the list of all global symbols of a given program
|
||||
"""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
sections: Dict[str, Dict[str, Tuple[int, int]]],
|
||||
symbols: Dict[str, Dict[str, int]],
|
||||
globals: Dict[str, Set[str]],
|
||||
base: int = 0,
|
||||
):
|
||||
def __init__(self,
|
||||
sections: Dict[str, Dict[str, Tuple[int, int]]],
|
||||
symbols: Dict[str, Dict[str, int]],
|
||||
globals: Dict[str, Set[str]],
|
||||
base: int = 0
|
||||
):
|
||||
self.sections = sections
|
||||
self.symbols = symbols
|
||||
self.globals = globals
|
||||
@ -130,9 +108,7 @@ class MemoryImageDebugInfos:
|
||||
return json.dumps(dict(obj), default=serialize)
|
||||
if isinstance(obj, (set, tuple)):
|
||||
return json.dumps(list(obj), default=serialize)
|
||||
return "<<unserializable {}>>".format(
|
||||
getattr(obj, "__qualname__", "{unknown}")
|
||||
)
|
||||
return "<<unserializable {}>>".format(getattr(obj, '__qualname__', '{unknown}'))
|
||||
|
||||
return json.dumps(
|
||||
dict(
|
||||
@ -140,25 +116,22 @@ class MemoryImageDebugInfos:
|
||||
symbols=self.symbols,
|
||||
globals=self.globals,
|
||||
base=self.base,
|
||||
VERSION=self.VERSION,
|
||||
VERSION=self.VERSION
|
||||
),
|
||||
default=serialize,
|
||||
default=serialize
|
||||
)
|
||||
|
||||
@classmethod
|
||||
def load(cls, serialized_str: str) -> "MemoryImageDebugInfos":
|
||||
def load(cls, serialized_str: str) -> 'MemoryImageDebugInfos':
|
||||
json_obj: dict = json.loads(serialized_str)
|
||||
|
||||
if "VERSION" not in json_obj:
|
||||
if 'VERSION' not in json_obj:
|
||||
raise RuntimeError("Unknown MemoryImageDebugInfo version!")
|
||||
|
||||
version: str = json_obj.pop("VERSION")
|
||||
version: str = json_obj.pop('VERSION')
|
||||
|
||||
# compare major version
|
||||
if (
|
||||
version != cls.VERSION
|
||||
and version.split(".")[0] != cls.VERSION.split(".")[0]
|
||||
):
|
||||
if version != cls.VERSION and version.split('.')[0] != cls.VERSION.split('.')[0]:
|
||||
raise RuntimeError(
|
||||
"Unknown MemoryImageDebugInfo version! This emulator expects version {}, debug info version {}".format(
|
||||
cls.VERSION, version
|
||||
@ -168,7 +141,7 @@ class MemoryImageDebugInfos:
|
||||
return MemoryImageDebugInfos(**json_obj)
|
||||
|
||||
@classmethod
|
||||
def builder(cls) -> "MemoryImageDebugInfos":
|
||||
def builder(cls) -> 'MemoryImageDebugInfos':
|
||||
return MemoryImageDebugInfos(
|
||||
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
|
||||
"""
|
||||
|
||||
from collections import defaultdict
|
||||
from typing import Union
|
||||
|
||||
from .helpers import *
|
||||
|
||||
from .types import Int32, Float32
|
||||
if typing.TYPE_CHECKING:
|
||||
from .types import Int32
|
||||
|
||||
|
||||
class Registers:
|
||||
@ -17,115 +17,43 @@ class Registers:
|
||||
Represents a bunch of registers
|
||||
"""
|
||||
|
||||
valid_regs = {
|
||||
"zero",
|
||||
"ra",
|
||||
"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)
|
||||
|
||||
def __init__(self):
|
||||
from .types import Int32
|
||||
self.vals = defaultdict(lambda: Int32(0))
|
||||
self.last_set = None
|
||||
self.last_read = None
|
||||
|
||||
self.infinite_regs = infinite_regs
|
||||
|
||||
def dump(self, full: bool = False):
|
||||
def dump(self, full=False):
|
||||
"""
|
||||
Dump all registers to stdout
|
||||
:param full: If True, floating point registers are dumped too
|
||||
"""
|
||||
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:
|
||||
regs = [("a", 8), ("s", 12), ("t", 7)]
|
||||
regs = [('a', 8), ('s', 12), ('t', 7)]
|
||||
else:
|
||||
regs = [
|
||||
("a", 8),
|
||||
("s", 12),
|
||||
("t", 7),
|
||||
("ft", 8),
|
||||
("fa", 8),
|
||||
("fs", 12),
|
||||
('a', 8),
|
||||
('s', 12),
|
||||
('t', 7),
|
||||
('ft', 8),
|
||||
('fa', 8),
|
||||
('fs', 12),
|
||||
]
|
||||
for name, s in regs:
|
||||
for i in range(12):
|
||||
if i >= s:
|
||||
lines[i].append(" " * 15)
|
||||
else:
|
||||
reg = "{}{}".format(name, i)
|
||||
reg = '{}{}'.format(name, i)
|
||||
lines[i].append(self._reg_repr(reg))
|
||||
|
||||
print(
|
||||
"Registers[{},{}](".format(
|
||||
FMT_ORANGE + FMT_UNDERLINE + "read" + FMT_NONE,
|
||||
FMT_ORANGE + FMT_BOLD + "written" + FMT_NONE,
|
||||
)
|
||||
)
|
||||
print("Registers[{},{}](".format(
|
||||
FMT_ORANGE + FMT_UNDERLINE + 'read' + FMT_NONE,
|
||||
FMT_ORANGE + FMT_BOLD + 'written' + FMT_NONE
|
||||
))
|
||||
if not full:
|
||||
print("\t" + " ".join(named_regs[0:3]))
|
||||
print("\t" + " ".join(named_regs[3:]))
|
||||
@ -141,35 +69,23 @@ class Registers:
|
||||
"""
|
||||
Dump the a registers
|
||||
"""
|
||||
print(
|
||||
"Registers[a]:"
|
||||
+ " ".join(self._reg_repr("a{}".format(i)) for i in range(8))
|
||||
)
|
||||
print("Registers[a]:" + " ".join(self._reg_repr('a{}'.format(i)) for i in range(8)))
|
||||
|
||||
def _reg_repr(self, reg: str, name_len=4, fmt="08X"):
|
||||
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"
|
||||
def _reg_repr(self, reg):
|
||||
txt = '{:4}=0x{:08X}'.format(reg, self.get(reg, False))
|
||||
if reg == 'fp':
|
||||
reg = 's0'
|
||||
if reg == self.last_set:
|
||||
return FMT_ORANGE + FMT_BOLD + txt + FMT_NONE
|
||||
if reg == self.last_read:
|
||||
return FMT_ORANGE + FMT_UNDERLINE + txt + FMT_NONE
|
||||
if reg == "zero":
|
||||
if reg == 'zero':
|
||||
return txt
|
||||
should_grayscale_int = (
|
||||
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:
|
||||
if self.get(reg, False) == 0 and reg not in Registers.named_registers():
|
||||
return FMT_GRAY + txt + FMT_NONE
|
||||
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
|
||||
:param reg: The register to set
|
||||
@ -178,29 +94,25 @@ class Registers:
|
||||
:return: If the operation was successful
|
||||
"""
|
||||
|
||||
from .types import Int32
|
||||
# remove after refactoring is complete
|
||||
if not isinstance(val, Int32):
|
||||
raise RuntimeError(
|
||||
"Setting register to non-Int32 value! Please refactor your code!"
|
||||
)
|
||||
raise RuntimeError("Setting register to non-Int32 value! Please refactor your code!")
|
||||
|
||||
if reg == "zero":
|
||||
if reg == 'zero':
|
||||
return False
|
||||
# if reg not in Registers.all_registers():
|
||||
# raise InvalidRegisterException(reg)
|
||||
# replace fp register with s1, as these are the same register
|
||||
if reg == "fp":
|
||||
reg = "s1"
|
||||
if reg == 'fp':
|
||||
reg = 's1'
|
||||
if mark_set:
|
||||
self.last_set = reg
|
||||
|
||||
if not self.infinite_regs and reg not in self.valid_regs:
|
||||
raise RuntimeError("Invalid register: {}".format(reg))
|
||||
|
||||
# check 32 bit signed bounds
|
||||
self.vals[reg] = val.unsigned()
|
||||
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
|
||||
:param reg: The register name
|
||||
@ -209,27 +121,25 @@ class Registers:
|
||||
"""
|
||||
# if reg not in Registers.all_registers():
|
||||
# raise InvalidRegisterException(reg)
|
||||
if reg == "fp":
|
||||
reg = "s0"
|
||||
|
||||
if not self.infinite_regs and reg not in self.valid_regs:
|
||||
raise RuntimeError("Invalid register: {}".format(reg))
|
||||
|
||||
if reg == 'fp':
|
||||
reg = 's0'
|
||||
if mark_read:
|
||||
self.last_read = reg
|
||||
return self.vals[reg]
|
||||
|
||||
def get_f(self, reg: str, mark_read: bool = True) -> "Float32":
|
||||
if not self.infinite_regs and reg not in self.float_regs:
|
||||
raise RuntimeError("Invalid float register: {}".format(reg))
|
||||
if mark_read:
|
||||
self.last_read = reg
|
||||
return self.float_vals[reg]
|
||||
|
||||
def set_f(self, reg: str, val: Union[float, "Float32"]):
|
||||
if not self.infinite_regs and reg not in self.float_regs:
|
||||
raise RuntimeError("Invalid float register: {}".format(reg))
|
||||
self.float_vals[reg] = Float32(val)
|
||||
@staticmethod
|
||||
def all_registers():
|
||||
"""
|
||||
Return a list of all valid registers
|
||||
:return: The list
|
||||
"""
|
||||
return ['zero', 'ra', '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',
|
||||
'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
|
||||
def named_registers():
|
||||
@ -237,12 +147,4 @@ class Registers:
|
||||
Return all named registers
|
||||
:return: The list
|
||||
"""
|
||||
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))
|
||||
+ "}",
|
||||
)
|
||||
return ['zero', 'ra', 'sp', 'gp', 'tp', 'fp']
|
||||
|
@ -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
|
||||
from dataclasses import dataclass
|
||||
from math import log2, ceil
|
||||
from typing import Dict, IO, Union
|
||||
from typing import Dict, IO
|
||||
|
||||
from .types import BinaryDataMemorySection, MemoryFlags
|
||||
from .colors import FMT_SYSCALL, FMT_NONE
|
||||
from .types import Int32, CPU
|
||||
from .types.exceptions import InvalidSyscallException
|
||||
|
||||
SYSCALLS = {
|
||||
63: "read",
|
||||
64: "write",
|
||||
93: "exit",
|
||||
192: "mmap2",
|
||||
1024: "open",
|
||||
1025: "close",
|
||||
63: 'read',
|
||||
64: 'write',
|
||||
93: 'exit',
|
||||
1024: 'open',
|
||||
1025: 'close',
|
||||
}
|
||||
"""
|
||||
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
|
||||
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 = {
|
||||
0: "rb",
|
||||
1: "wb",
|
||||
2: "r+b",
|
||||
3: "x",
|
||||
4: "ab",
|
||||
0: 'rb',
|
||||
1: 'wb',
|
||||
2: 'r+b',
|
||||
3: 'x',
|
||||
4: 'ab',
|
||||
}
|
||||
"""All available file open modes"""
|
||||
|
||||
@ -57,7 +42,6 @@ class Syscall:
|
||||
"""
|
||||
Represents a syscall
|
||||
"""
|
||||
|
||||
id: int
|
||||
"""The syscall number (e.g. 64 - write)"""
|
||||
cpu: CPU
|
||||
@ -68,10 +52,12 @@ class Syscall:
|
||||
return SYSCALLS.get(self.id, "unknown")
|
||||
|
||||
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]):
|
||||
self.cpu.regs.set("a0", Int32(code))
|
||||
def ret(self, code):
|
||||
self.cpu.regs.set('a0', Int32(code))
|
||||
|
||||
|
||||
def get_syscall_symbols():
|
||||
@ -80,26 +66,25 @@ def get_syscall_symbols():
|
||||
|
||||
:return: dictionary of all syscall symbols (SCALL_<name> -> id)
|
||||
"""
|
||||
items: Dict[str, int] = {
|
||||
("SCALL_" + name.upper()): num for num, name in SYSCALLS.items()
|
||||
return {
|
||||
('SCALL_' + name.upper()): num for num, name in SYSCALLS.items()
|
||||
}
|
||||
|
||||
items.update(ADDITIONAL_SYMBOLS)
|
||||
|
||||
return items
|
||||
|
||||
|
||||
class SyscallInterface:
|
||||
"""
|
||||
Handles syscalls
|
||||
"""
|
||||
|
||||
open_files: Dict[int, IO]
|
||||
next_open_handle: int
|
||||
|
||||
def handle_syscall(self, scall: Syscall):
|
||||
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):
|
||||
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
|
||||
on return a0 will be the number of read bytes or -1 if an error occured
|
||||
"""
|
||||
fileno = scall.cpu.regs.get("a0").unsigned_value
|
||||
addr = scall.cpu.regs.get("a1").unsigned_value
|
||||
size = scall.cpu.regs.get("a2").unsigned_value
|
||||
fileno = scall.cpu.regs.get('a0').unsigned_value
|
||||
addr = scall.cpu.regs.get('a1').unsigned_value
|
||||
size = scall.cpu.regs.get('a2').unsigned_value
|
||||
if fileno not in self.open_files:
|
||||
scall.ret(-1)
|
||||
return
|
||||
|
||||
chars = self.open_files[fileno].readline(size)
|
||||
try:
|
||||
data = bytearray(chars, "ascii")
|
||||
data = bytearray(chars, 'ascii')
|
||||
scall.cpu.mmu.write(addr, len(data), data)
|
||||
return scall.ret(len(data))
|
||||
|
||||
except UnicodeEncodeError:
|
||||
print(
|
||||
FMT_SYSCALL
|
||||
+ '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_SYSCALL + '[Syscall] read: UnicodeError - invalid input "{}"'.format(chars) + FMT_NONE)
|
||||
return scall.ret(-1)
|
||||
|
||||
def write(self, scall: Syscall):
|
||||
@ -137,23 +118,19 @@ class SyscallInterface:
|
||||
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
|
||||
"""
|
||||
fileno = scall.cpu.regs.get("a0").unsigned_value
|
||||
addr = scall.cpu.regs.get("a1").unsigned_value
|
||||
size = scall.cpu.regs.get("a2").unsigned_value
|
||||
fileno = scall.cpu.regs.get('a0').unsigned_value
|
||||
addr = scall.cpu.regs.get('a1').unsigned_value
|
||||
size = scall.cpu.regs.get('a2').unsigned_value
|
||||
if fileno not in self.open_files:
|
||||
return scall.ret(-1)
|
||||
|
||||
data = scall.cpu.mmu.read(addr, size)
|
||||
|
||||
if not isinstance(data, bytearray):
|
||||
print(
|
||||
FMT_SYSCALL
|
||||
+ "[Syscall] write: writing from .text region not supported."
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_SYSCALL + '[Syscall] write: writing from .text region not supported.' + FMT_NONE)
|
||||
return scall.ret(-1)
|
||||
|
||||
self.open_files[fileno].write(data.decode("ascii"))
|
||||
self.open_files[fileno].write(data.decode('ascii'))
|
||||
return scall.ret(size)
|
||||
|
||||
def open(self, scall: Syscall):
|
||||
@ -172,29 +149,19 @@ class SyscallInterface:
|
||||
"""
|
||||
# FIXME: this should be toggleable in a global setting or something
|
||||
if True:
|
||||
print(
|
||||
FMT_SYSCALL
|
||||
+ "[Syscall] open: opening files not supported without scall-fs flag!"
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_SYSCALL + '[Syscall] open: opening files not supported without scall-fs flag!' + FMT_NONE)
|
||||
return scall.ret(-1)
|
||||
|
||||
mode = scall.cpu.regs.get("a0").unsigned_value
|
||||
addr = scall.cpu.regs.get("a1").unsigned_value
|
||||
size = scall.cpu.regs.get("a2").unsigned_value
|
||||
mode = scall.cpu.regs.get('a0').unsigned_value
|
||||
addr = scall.cpu.regs.get('a1').unsigned_value
|
||||
size = scall.cpu.regs.get('a2').unsigned_value
|
||||
|
||||
mode_st = OPEN_MODES.get(
|
||||
mode,
|
||||
)
|
||||
mode_st = OPEN_MODES.get(mode, )
|
||||
if mode_st == -1:
|
||||
print(
|
||||
FMT_SYSCALL
|
||||
+ "[Syscall] open: unknown opening mode {}!".format(mode)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_SYSCALL + '[Syscall] open: unknown opening mode {}!'.format(mode) + FMT_NONE)
|
||||
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
|
||||
self.next_open_handle += 1
|
||||
@ -202,18 +169,10 @@ class SyscallInterface:
|
||||
try:
|
||||
self.open_files[fileno] = open(path, mode_st)
|
||||
except OSError as err:
|
||||
print(
|
||||
FMT_SYSCALL
|
||||
+ "[Syscall] open: encountered error during {}!".format(err.strerror)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_SYSCALL + '[Syscall] open: encountered error during {}!'.format(err.strerror) + FMT_NONE)
|
||||
return scall.ret(-1)
|
||||
|
||||
print(
|
||||
FMT_SYSCALL
|
||||
+ "[Syscall] open: opened fd {} to {}!".format(fileno, path)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_SYSCALL + '[Syscall] open: opened fd {} to {}!'.format(fileno, path) + FMT_NONE)
|
||||
return scall.ret(fileno)
|
||||
|
||||
def close(self, scall: Syscall):
|
||||
@ -222,17 +181,13 @@ class SyscallInterface:
|
||||
|
||||
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:
|
||||
print(
|
||||
FMT_SYSCALL
|
||||
+ "[Syscall] close: unknown fileno {}!".format(fileno)
|
||||
+ FMT_NONE
|
||||
)
|
||||
print(FMT_SYSCALL + '[Syscall] close: unknown fileno {}!'.format(fileno) + FMT_NONE)
|
||||
return scall.ret(-1)
|
||||
|
||||
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]
|
||||
return scall.ret(0)
|
||||
|
||||
@ -241,55 +196,10 @@ class SyscallInterface:
|
||||
Exit syscall. Exits the system with status code a0
|
||||
"""
|
||||
scall.cpu.halted = True
|
||||
scall.cpu.exit_code = scall.cpu.regs.get("a0").signed().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)
|
||||
scall.cpu.exit_code = scall.cpu.regs.get('a0').value
|
||||
|
||||
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.types.exceptions import ParseException
|
||||
|
||||
LINE_COMMENT_STARTERS = ("#", ";", "//")
|
||||
WHITESPACE_PATTERN = re.compile(r"\s+")
|
||||
MEMORY_ADDRESS_PATTERN = re.compile(
|
||||
r"^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]*)\)$"
|
||||
)
|
||||
LINE_COMMENT_STARTERS = ('#', ';', '//')
|
||||
WHITESPACE_PATTERN = re.compile(r'\s+')
|
||||
MEMORY_ADDRESS_PATTERN = re.compile(r'^(0[xX][A-f0-9]+|\d+|0b[0-1]+|[A-z0-9_-]+)\(([A-z]+[0-9]{0,2})\)$')
|
||||
REGISTER_NAMES = RISCV_REGS
|
||||
|
||||
|
||||
@ -36,22 +34,22 @@ class Token:
|
||||
|
||||
def __str__(self):
|
||||
if self.type == TokenType.NEWLINE:
|
||||
return "\\n"
|
||||
return '\\n'
|
||||
if self.type == TokenType.COMMA:
|
||||
return ", "
|
||||
return "{}({})".format(self.type.name[0:3], self.value)
|
||||
return ', '
|
||||
return '{}({})'.format(self.type.name[0:3], self.value)
|
||||
|
||||
|
||||
NEWLINE = Token(TokenType.NEWLINE, "\n")
|
||||
COMMA = Token(TokenType.COMMA, ",")
|
||||
NEWLINE = Token(TokenType.NEWLINE, '\n')
|
||||
COMMA = Token(TokenType.COMMA, ',')
|
||||
|
||||
|
||||
def tokenize(input: Iterable[str]) -> Iterable[Token]:
|
||||
for line in input:
|
||||
for line_comment_start in LINE_COMMENT_STARTERS:
|
||||
if line_comment_start in line:
|
||||
line = line[: line.index(line_comment_start)]
|
||||
line.strip(" \t\n")
|
||||
line = line[:line.index(line_comment_start)]
|
||||
line.strip(' \t\n')
|
||||
if not line:
|
||||
continue
|
||||
|
||||
@ -66,9 +64,9 @@ def parse_line(parts: List[str]) -> Iterable[Token]:
|
||||
return ()
|
||||
first_token = parts[0]
|
||||
|
||||
if first_token[0] == ".":
|
||||
if first_token[0] == '.':
|
||||
yield Token(TokenType.PSEUDO_OP, first_token)
|
||||
elif first_token[-1] == ":":
|
||||
elif first_token[-1] == ':':
|
||||
yield Token(TokenType.LABEL, first_token)
|
||||
yield from parse_line(parts[1:])
|
||||
return
|
||||
@ -76,21 +74,22 @@ def parse_line(parts: List[str]) -> Iterable[Token]:
|
||||
yield Token(TokenType.INSTRUCTION_NAME, first_token)
|
||||
|
||||
for part in parts[1:]:
|
||||
if part == ",":
|
||||
if part == ',':
|
||||
yield COMMA
|
||||
continue
|
||||
yield from parse_arg(part)
|
||||
|
||||
|
||||
def parse_arg(arg: str) -> Iterable[Token]:
|
||||
comma = arg[-1] == ","
|
||||
comma = arg[-1] == ','
|
||||
arg = arg[:-1] if comma else arg
|
||||
mem_match_resul = re.match(MEMORY_ADDRESS_PATTERN, arg)
|
||||
if mem_match_resul:
|
||||
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, immediate)
|
||||
yield Token(TokenType.ARGUMENT, mem_match_resul.group(1))
|
||||
else:
|
||||
yield Token(TokenType.ARGUMENT, arg)
|
||||
if comma:
|
||||
@ -99,7 +98,7 @@ def parse_arg(arg: str) -> Iterable[Token]:
|
||||
|
||||
def print_tokens(tokens: Iterable[Token]):
|
||||
for token in tokens:
|
||||
print(token, end="\n" if token == NEWLINE else "")
|
||||
print(token, end='\n' if token == NEWLINE else '')
|
||||
print("", flush=True, end="")
|
||||
|
||||
|
||||
@ -124,7 +123,7 @@ def split_whitespace_respecting_quotes(line: str) -> Iterable[str]:
|
||||
part = ""
|
||||
continue
|
||||
|
||||
if c in " \t\n":
|
||||
if c in ' \t\n':
|
||||
if part:
|
||||
yield 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
|
||||
|
||||
# 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
|
||||
|
||||
# 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
|
||||
from .flags import MemoryFlags
|
||||
from .int32 import UInt32, Int32
|
||||
from .float32 import Float32
|
||||
from .instruction import Instruction
|
||||
from .instruction_context import InstructionContext
|
||||
from .memory_section import MemorySection
|
||||
@ -25,17 +24,6 @@ from .instruction_memory_section import InstructionMemorySection
|
||||
from .binary_data_memory_section import BinaryDataMemorySection
|
||||
|
||||
# exceptions
|
||||
from .exceptions import (
|
||||
ParseException,
|
||||
NumberFormatException,
|
||||
MemoryAccessException,
|
||||
OutOfMemoryException,
|
||||
LinkerException,
|
||||
LaunchDebuggerException,
|
||||
RiscemuBaseException,
|
||||
InvalidRegisterException,
|
||||
InvalidAllocationException,
|
||||
InvalidSyscallException,
|
||||
UnimplementedInstruction,
|
||||
INS_NOT_IMPLEMENTED,
|
||||
)
|
||||
from .exceptions import ParseException, NumberFormatException, MemoryAccessException, OutOfMemoryException, \
|
||||
LinkerException, LaunchDebuggerException, RiscemuBaseException, InvalidRegisterException, \
|
||||
InvalidAllocationException, InvalidSyscallException, UnimplementedInstruction
|
||||
|
@ -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
|
||||
|
||||
|
||||
class BinaryDataMemorySection(MemorySection):
|
||||
def __init__(
|
||||
self,
|
||||
data: bytearray,
|
||||
name: str,
|
||||
context: InstructionContext,
|
||||
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,
|
||||
)
|
||||
def __init__(self, data: bytearray, name: str, context: InstructionContext, owner: str, base: int = 0, flags: MemoryFlags = None):
|
||||
self.name = name
|
||||
self.base = base
|
||||
self.context = context
|
||||
self.size = len(data)
|
||||
self.flags = flags if flags is not None else MemoryFlags(False, False)
|
||||
self.data = data
|
||||
self.owner = owner
|
||||
|
||||
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
|
||||
if offset + size > self.size:
|
||||
raise MemoryAccessException(
|
||||
"Out of bounds access in {}".format(self), offset, size, "read"
|
||||
)
|
||||
return self.data[offset : offset + size]
|
||||
raise MemoryAccessException("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):
|
||||
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:
|
||||
raise MemoryAccessException(
|
||||
"Invalid write parameter sizing", offset, size, "write"
|
||||
)
|
||||
self.data[offset : offset + size] = data[0:size]
|
||||
raise MemoryAccessException("Invalid write parameter sizing", offset, size, 'write')
|
||||
self.data[offset:offset + size] = data[0:size]
|
||||
|
||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
||||
raise MemoryAccessException(
|
||||
"Tried reading instruction on non-executable section {}".format(self),
|
||||
offset,
|
||||
4,
|
||||
"instruction fetch",
|
||||
)
|
||||
raise MemoryAccessException("Tried reading instruction on non-executable section {}".format(self),
|
||||
offset, 4, 'instruction fetch')
|
||||
|
@ -1,15 +1,12 @@
|
||||
import typing
|
||||
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 ..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
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from ..MMU import MMU
|
||||
from ..instructions import InstructionSet
|
||||
|
||||
|
||||
class CPU(ABC):
|
||||
# static cpu configuration
|
||||
@ -17,7 +14,7 @@ class CPU(ABC):
|
||||
|
||||
# housekeeping variables
|
||||
regs: Registers
|
||||
mmu: "MMU"
|
||||
mmu: 'MMU'
|
||||
pc: T_AbsoluteAddress
|
||||
cycle: int
|
||||
halted: bool
|
||||
@ -27,19 +24,14 @@ class CPU(ABC):
|
||||
|
||||
# instruction information
|
||||
instructions: Dict[str, Callable[[Instruction], None]]
|
||||
instruction_sets: Set["InstructionSet"]
|
||||
instruction_sets: Set['InstructionSet']
|
||||
|
||||
# configuration
|
||||
conf: RunConfig
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
mmu: "MMU",
|
||||
instruction_sets: List[Type["InstructionSet"]],
|
||||
conf: RunConfig,
|
||||
):
|
||||
def __init__(self, mmu: 'MMU', instruction_sets: List[Type['InstructionSet']], conf: RunConfig):
|
||||
self.mmu = mmu
|
||||
self.regs = Registers(conf.unlimited_registers)
|
||||
self.regs = Registers()
|
||||
self.conf = conf
|
||||
|
||||
self.instruction_sets = set()
|
||||
@ -79,37 +71,32 @@ class CPU(ABC):
|
||||
self.pc,
|
||||
self.cycle,
|
||||
self.halted,
|
||||
" ".join(s.name for s in self.instruction_sets),
|
||||
" ".join(s.name for s in self.instruction_sets)
|
||||
)
|
||||
|
||||
@abstractmethod
|
||||
def step(self, verbose: bool = False):
|
||||
def step(self, verbose=False):
|
||||
pass
|
||||
|
||||
@abstractmethod
|
||||
def run(self, verbose: bool = False):
|
||||
def run(self, verbose=False):
|
||||
pass
|
||||
|
||||
def launch(self, verbose: bool = False):
|
||||
entrypoint = self.mmu.find_entrypoint()
|
||||
|
||||
if entrypoint is None:
|
||||
entrypoint = self.mmu.programs[0].entrypoint
|
||||
|
||||
def launch(self, program: Program, verbose: bool = False):
|
||||
if program not in self.mmu.programs:
|
||||
print(FMT_ERROR + '[CPU] Cannot launch program that\'s not loaded!' + FMT_NONE)
|
||||
return
|
||||
if self.conf.verbosity > 0:
|
||||
print(
|
||||
FMT_CPU
|
||||
+ "[CPU] Started running from {}".format(
|
||||
self.mmu.translate_address(entrypoint)
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
self.pc = entrypoint
|
||||
print(FMT_CPU + "[CPU] Started running from {}".format(
|
||||
self.mmu.translate_address(program.entrypoint)
|
||||
) + FMT_NONE)
|
||||
print(program)
|
||||
self.pc = program.entrypoint
|
||||
self.run(verbose)
|
||||
|
||||
@classmethod
|
||||
@abstractmethod
|
||||
def get_loaders(cls) -> Iterable[Type[ProgramLoader]]:
|
||||
def get_loaders(cls) -> typing.Iterable[Type[ProgramLoader]]:
|
||||
pass
|
||||
|
||||
def get_best_loader_for(self, file_name: str) -> Type[ProgramLoader]:
|
||||
|
@ -14,86 +14,63 @@ if typing.TYPE_CHECKING:
|
||||
|
||||
class RiscemuBaseException(BaseException):
|
||||
@abstractmethod
|
||||
def message(self) -> str:
|
||||
raise NotImplemented
|
||||
def message(self):
|
||||
pass
|
||||
|
||||
def print_stacktrace(self):
|
||||
import traceback
|
||||
|
||||
traceback.print_exception(type(self), self, self.__traceback__)
|
||||
|
||||
|
||||
# Parsing exceptions:
|
||||
|
||||
|
||||
class ParseException(RiscemuBaseException):
|
||||
def __init__(self, msg: str, data=None):
|
||||
def __init__(self, msg, data=None):
|
||||
super().__init__(msg, data)
|
||||
self.msg = msg
|
||||
self.data = data
|
||||
|
||||
def message(self):
|
||||
return (
|
||||
FMT_PARSE
|
||||
+ '{}("{}", data={})'.format(self.__class__.__name__, self.msg, self.data)
|
||||
+ FMT_NONE
|
||||
)
|
||||
return FMT_PARSE + "{}(\"{}\", data={})".format(self.__class__.__name__, self.msg, self.data) + FMT_NONE
|
||||
|
||||
|
||||
def ASSERT_EQ(a1, a2):
|
||||
if a1 != a2:
|
||||
raise ParseException(
|
||||
"ASSERTION_FAILED: Expected elements to be equal!", (a1, a2)
|
||||
)
|
||||
raise ParseException("ASSERTION_FAILED: Expected elements to be equal!", (a1, a2))
|
||||
|
||||
|
||||
def ASSERT_LEN(a1, size):
|
||||
if len(a1) != size:
|
||||
raise ParseException(
|
||||
"ASSERTION_FAILED: Expected {} to be of length {}".format(a1, size),
|
||||
(a1, size),
|
||||
)
|
||||
raise ParseException("ASSERTION_FAILED: Expected {} to be of length {}".format(a1, size), (a1, size))
|
||||
|
||||
|
||||
def ASSERT_NOT_NULL(a1):
|
||||
if a1 is None:
|
||||
raise ParseException(
|
||||
"ASSERTION_FAILED: Expected {} to be non null".format(a1), (a1,)
|
||||
)
|
||||
raise ParseException("ASSERTION_FAILED: Expected {} to be non null".format(a1), (a1,))
|
||||
|
||||
|
||||
def ASSERT_NOT_IN(a1, a2):
|
||||
if a1 in a2:
|
||||
raise ParseException(
|
||||
"ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2)
|
||||
)
|
||||
raise ParseException("ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2))
|
||||
|
||||
|
||||
def ASSERT_IN(a1, a2):
|
||||
if a1 not in a2:
|
||||
raise ParseException(
|
||||
"ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2)
|
||||
)
|
||||
raise ParseException("ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2))
|
||||
|
||||
|
||||
class LinkerException(RiscemuBaseException):
|
||||
def __init__(self, msg: str, data):
|
||||
def __init__(self, msg, data):
|
||||
self.msg = msg
|
||||
self.data = data
|
||||
|
||||
def message(self):
|
||||
return (
|
||||
FMT_PARSE
|
||||
+ '{}("{}", data={})'.format(self.__class__.__name__, self.msg, self.data)
|
||||
+ FMT_NONE
|
||||
)
|
||||
return FMT_PARSE + "{}(\"{}\", data={})".format(self.__class__.__name__, self.msg, self.data) + FMT_NONE
|
||||
|
||||
|
||||
# MMU Exceptions
|
||||
|
||||
|
||||
class MemoryAccessException(RiscemuBaseException):
|
||||
def __init__(self, msg: str, addr, size, op):
|
||||
def __init__(self, msg, addr, size, op):
|
||||
super(MemoryAccessException, self).__init__()
|
||||
self.msg = msg
|
||||
self.addr = addr
|
||||
@ -101,13 +78,13 @@ class MemoryAccessException(RiscemuBaseException):
|
||||
self.op = op
|
||||
|
||||
def message(self):
|
||||
return (
|
||||
FMT_MEM
|
||||
+ "{}(During {} at 0x{:08x} of size {}: {})".format(
|
||||
self.__class__.__name__, self.op, self.addr, self.size, self.msg
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
return FMT_MEM + "{}(During {} at 0x{:08x} of size {}: {})".format(
|
||||
self.__class__.__name__,
|
||||
self.op,
|
||||
self.addr,
|
||||
self.size,
|
||||
self.msg
|
||||
) + FMT_NONE
|
||||
|
||||
|
||||
class OutOfMemoryException(RiscemuBaseException):
|
||||
@ -115,13 +92,10 @@ class OutOfMemoryException(RiscemuBaseException):
|
||||
self.action = action
|
||||
|
||||
def message(self):
|
||||
return (
|
||||
FMT_MEM
|
||||
+ "{}(Ran out of memory during {})".format(
|
||||
self.__class__.__name__, self.action
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
return FMT_MEM + '{}(Ran out of memory during {})'.format(
|
||||
self.__class__.__name__,
|
||||
self.action
|
||||
) + FMT_NONE
|
||||
|
||||
|
||||
class InvalidAllocationException(RiscemuBaseException):
|
||||
@ -132,29 +106,28 @@ class InvalidAllocationException(RiscemuBaseException):
|
||||
self.flags = flags
|
||||
|
||||
def message(self):
|
||||
return FMT_MEM + "{}[{}](name={}, size={}, flags={})".format(
|
||||
self.__class__.__name__, self.msg, self.name, self.size, self.flags
|
||||
return FMT_MEM + '{}[{}](name={}, size={}, flags={})'.format(
|
||||
self.__class__.__name__,
|
||||
self.msg,
|
||||
self.name,
|
||||
self.size,
|
||||
self.flags
|
||||
)
|
||||
|
||||
|
||||
# CPU Exceptions
|
||||
|
||||
|
||||
class UnimplementedInstruction(RiscemuBaseException):
|
||||
def __init__(self, ins: "Instruction", context=None):
|
||||
def __init__(self, ins: 'Instruction', context = None):
|
||||
self.ins = ins
|
||||
self.context = context
|
||||
|
||||
def message(self):
|
||||
return (
|
||||
FMT_CPU
|
||||
+ "{}({}{})".format(
|
||||
self.__class__.__name__,
|
||||
repr(self.ins),
|
||||
", context={}".format(self.context) if self.context is not None else "",
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
return FMT_CPU + "{}({}{})".format(
|
||||
self.__class__.__name__,
|
||||
repr(self.ins),
|
||||
', context={}'.format(self.context) if self.context is not None else ''
|
||||
) + FMT_NONE
|
||||
|
||||
|
||||
class InvalidRegisterException(RiscemuBaseException):
|
||||
@ -162,11 +135,10 @@ class InvalidRegisterException(RiscemuBaseException):
|
||||
self.reg = reg
|
||||
|
||||
def message(self):
|
||||
return (
|
||||
FMT_CPU
|
||||
+ "{}(Invalid register {})".format(self.__class__.__name__, self.reg)
|
||||
+ FMT_NONE
|
||||
)
|
||||
return FMT_CPU + "{}(Invalid register {})".format(
|
||||
self.__class__.__name__,
|
||||
self.reg
|
||||
) + FMT_NONE
|
||||
|
||||
|
||||
class InvalidSyscallException(RiscemuBaseException):
|
||||
@ -174,11 +146,10 @@ class InvalidSyscallException(RiscemuBaseException):
|
||||
self.scall = scall
|
||||
|
||||
def message(self):
|
||||
return (
|
||||
FMT_SYSCALL
|
||||
+ "{}(Invalid syscall: {})".format(self.__class__.__name__, self.scall)
|
||||
+ FMT_NONE
|
||||
)
|
||||
return FMT_SYSCALL + "{}(Invalid syscall: {})".format(
|
||||
self.__class__.__name__,
|
||||
self.scall
|
||||
) + FMT_NONE
|
||||
|
||||
|
||||
def INS_NOT_IMPLEMENTED(ins):
|
||||
@ -191,10 +162,13 @@ class NumberFormatException(RiscemuBaseException):
|
||||
self.msg = msg
|
||||
|
||||
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
|
||||
class LaunchDebuggerException(RiscemuBaseException):
|
||||
def message(self) -> str:
|
||||
def message(self):
|
||||
return ""
|
||||
|
@ -8,5 +8,6 @@ class MemoryFlags:
|
||||
|
||||
def __repr__(self):
|
||||
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]]
|
||||
"""
|
||||
This dictionary maps numbered labels (which can occur multiple times) to a list of (block-relative) addresses where
|
||||
the label was placed
|
||||
This dictionary maps numbered labels (which can occur multiple times) to a list of (block-relative) addresses where
|
||||
the label was placed
|
||||
"""
|
||||
|
||||
global_symbol_dict: Dict[str, T_AbsoluteAddress]
|
||||
@ -33,32 +33,21 @@ class InstructionContext:
|
||||
self.base_address = 0
|
||||
self.global_symbol_dict = dict()
|
||||
|
||||
def resolve_label(
|
||||
self, symbol: str, address_at: Optional[T_RelativeAddress] = None
|
||||
) -> Optional[T_AbsoluteAddress]:
|
||||
def resolve_label(self, symbol: str, address_at: Optional[T_RelativeAddress] = None) -> Optional[T_AbsoluteAddress]:
|
||||
if NUMBER_SYMBOL_PATTERN.match(symbol):
|
||||
if address_at is None:
|
||||
raise ParseException(
|
||||
"Cannot resolve relative symbol {} without an address!".format(
|
||||
symbol
|
||||
)
|
||||
)
|
||||
raise ParseException("Cannot resolve relative symbol {} without an address!".format(symbol))
|
||||
|
||||
direction = symbol[-1]
|
||||
values = self.numbered_labels.get(symbol[:-1], [])
|
||||
if direction == "b":
|
||||
return max(
|
||||
(addr + self.base_address for addr in values if addr < address_at),
|
||||
default=None,
|
||||
)
|
||||
if direction == 'b':
|
||||
return max((addr + self.base_address for addr in values if addr < address_at), default=None)
|
||||
else:
|
||||
return min(
|
||||
(addr + self.base_address for addr in values if addr > address_at),
|
||||
default=None,
|
||||
)
|
||||
return min((addr + self.base_address for addr in values if addr > address_at), default=None)
|
||||
else:
|
||||
# if it's not a local symbol, try the globals
|
||||
if symbol not in self.labels:
|
||||
return self.global_symbol_dict.get(symbol, None)
|
||||
# otherwise return the local symbol
|
||||
return self.labels.get(symbol, None)
|
||||
|
||||
|
@ -1,24 +1,11 @@
|
||||
from typing import List
|
||||
|
||||
from . import (
|
||||
MemorySection,
|
||||
Instruction,
|
||||
InstructionContext,
|
||||
MemoryFlags,
|
||||
T_RelativeAddress,
|
||||
)
|
||||
from . import MemorySection, Instruction, InstructionContext, MemoryFlags, T_RelativeAddress
|
||||
from .exceptions import MemoryAccessException
|
||||
|
||||
|
||||
class InstructionMemorySection(MemorySection):
|
||||
def __init__(
|
||||
self,
|
||||
instructions: List[Instruction],
|
||||
name: str,
|
||||
context: InstructionContext,
|
||||
owner: str,
|
||||
base: int = 0,
|
||||
):
|
||||
def __init__(self, instructions: List[Instruction], name: str, context: InstructionContext, owner: str, base: int = 0):
|
||||
self.name = name
|
||||
self.base = base
|
||||
self.context = context
|
||||
@ -28,27 +15,13 @@ class InstructionMemorySection(MemorySection):
|
||||
self.owner = owner
|
||||
|
||||
def read(self, offset: T_RelativeAddress, size: int) -> bytearray:
|
||||
raise MemoryAccessException(
|
||||
"Cannot read raw bytes from instruction section",
|
||||
self.base + offset,
|
||||
size,
|
||||
"read",
|
||||
)
|
||||
raise MemoryAccessException("Cannot read raw bytes from instruction section", self.base + offset, size, 'read')
|
||||
|
||||
def write(self, offset: T_RelativeAddress, size: int, data: bytearray):
|
||||
raise MemoryAccessException(
|
||||
"Cannot write raw bytes to instruction section",
|
||||
self.base + offset,
|
||||
size,
|
||||
"write",
|
||||
)
|
||||
raise MemoryAccessException("Cannot write raw bytes to instruction section", self.base + offset, size, 'write')
|
||||
|
||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
||||
if offset % 4 != 0:
|
||||
raise MemoryAccessException(
|
||||
"Unaligned instruction fetch!",
|
||||
self.base + offset,
|
||||
4,
|
||||
"instruction fetch",
|
||||
)
|
||||
raise MemoryAccessException("Unaligned instruction fetch!", self.base + offset, 4, 'instruction fetch')
|
||||
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
|
||||
|
||||
|
||||
@ -11,91 +11,82 @@ class Int32:
|
||||
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.
|
||||
"""
|
||||
|
||||
_type = c_int32
|
||||
__slots__ = ("_val",)
|
||||
__slots__ = ('_val',)
|
||||
|
||||
def __init__(
|
||||
self, val: Union[int, c_int32, c_uint32, "Int32", bytes, bytearray, bool] = 0
|
||||
):
|
||||
def __init__(self, val: Union[int, c_int32, c_uint32, 'Int32', bytes, bytearray] = 0):
|
||||
if isinstance(val, (bytes, bytearray)):
|
||||
signed = len(val) == 4 and self._type == c_int32
|
||||
self._val = self.__class__._type(
|
||||
int.from_bytes(val, "little", signed=signed)
|
||||
)
|
||||
self._val = self.__class__._type(int.from_bytes(val, 'little', signed=signed))
|
||||
elif isinstance(val, self.__class__._type):
|
||||
self._val = val
|
||||
elif isinstance(val, (c_uint32, c_int32, Int32)):
|
||||
self._val = self.__class__._type(val.value)
|
||||
elif isinstance(val, (int, bool)):
|
||||
elif isinstance(val, int):
|
||||
self._val = self.__class__._type(val)
|
||||
else:
|
||||
raise RuntimeError(
|
||||
"Unknonw {} input type: {} ({})".format(
|
||||
self.__class__.__name__, type(val), val
|
||||
)
|
||||
"Unknonw {} input type: {} ({})".format(self.__class__.__name__, type(val), val)
|
||||
)
|
||||
|
||||
def __add__(self, other: Union["Int32", int]):
|
||||
def __add__(self, other: Union['Int32', int]):
|
||||
if isinstance(other, Int32):
|
||||
other = other.value
|
||||
|
||||
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):
|
||||
other = other.value
|
||||
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):
|
||||
other = other.value
|
||||
return self.__class__(self._val.value * other)
|
||||
|
||||
def __truediv__(self, other: Any):
|
||||
def __truediv__(self, other):
|
||||
return self // other
|
||||
|
||||
def __floordiv__(self, other: Any):
|
||||
def __floordiv__(self, other):
|
||||
if isinstance(other, Int32):
|
||||
other = other.value
|
||||
return self.__class__(self.value // other)
|
||||
|
||||
def __mod__(self, other: Union["Int32", int]):
|
||||
def __mod__(self, other: Union['Int32', int]):
|
||||
if isinstance(other, Int32):
|
||||
other = other.value
|
||||
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):
|
||||
other = other.value
|
||||
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):
|
||||
other = other.value
|
||||
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):
|
||||
other = other.value
|
||||
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):
|
||||
other = other.value
|
||||
return self.__class__(self.value << other)
|
||||
|
||||
def __rshift__(self, other: Union["Int32", int]):
|
||||
def __rshift__(self, other: Union['Int32', int]):
|
||||
if isinstance(other, Int32):
|
||||
other = other.value
|
||||
return self.__class__(self.value >> other)
|
||||
|
||||
def __eq__(self, other: object) -> bool:
|
||||
if isinstance(other, int):
|
||||
return self.value == other
|
||||
elif isinstance(other, Int32):
|
||||
return self.value == other.value
|
||||
return False
|
||||
def __eq__(self, other: Union['Int32', int]):
|
||||
if isinstance(other, Int32):
|
||||
other = other.value
|
||||
return self.value == other
|
||||
|
||||
def __neg__(self):
|
||||
return self.__class__(-self._val.value)
|
||||
@ -107,33 +98,33 @@ class Int32:
|
||||
return self.to_bytes(4)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}({})".format(self.__class__.__name__, self.value)
|
||||
return '{}({})'.format(self.__class__.__name__, self.value)
|
||||
|
||||
def __str__(self):
|
||||
return str(self.value)
|
||||
|
||||
def __format__(self, format_spec: str):
|
||||
def __format__(self, format_spec):
|
||||
return self.value.__format__(format_spec)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.value)
|
||||
|
||||
def __gt__(self, other: Any):
|
||||
def __gt__(self, other):
|
||||
if isinstance(other, Int32):
|
||||
other = other.value
|
||||
return self.value > other
|
||||
|
||||
def __lt__(self, other: Any):
|
||||
def __lt__(self, other):
|
||||
if isinstance(other, Int32):
|
||||
other = other.value
|
||||
return self.value < other
|
||||
|
||||
def __le__(self, other: Any):
|
||||
def __le__(self, other):
|
||||
if isinstance(other, Int32):
|
||||
other = other.value
|
||||
return self.value <= other
|
||||
|
||||
def __ge__(self, other: Any):
|
||||
def __ge__(self, other):
|
||||
if isinstance(other, Int32):
|
||||
other = other.value
|
||||
return self.value >= other
|
||||
@ -141,38 +132,38 @@ class Int32:
|
||||
def __bool__(self):
|
||||
return bool(self.value)
|
||||
|
||||
def __cmp__(self, other: Any):
|
||||
def __cmp__(self, other):
|
||||
if isinstance(other, Int32):
|
||||
other = other.value
|
||||
return self.value.__cmp__(other)
|
||||
|
||||
# right handed binary operators
|
||||
|
||||
def __radd__(self, other: Any):
|
||||
def __radd__(self, other):
|
||||
return self + other
|
||||
|
||||
def __rsub__(self, other: Any):
|
||||
def __rsub__(self, other):
|
||||
return self.__class__(other) - self
|
||||
|
||||
def __rmul__(self, other: Any):
|
||||
def __rmul__(self, other):
|
||||
return self * other
|
||||
|
||||
def __rtruediv__(self, other: Any):
|
||||
def __rtruediv__(self, other):
|
||||
return self.__class__(other) // self
|
||||
|
||||
def __rfloordiv__(self, other: Any):
|
||||
def __rfloordiv__(self, other):
|
||||
return self.__class__(other) // self
|
||||
|
||||
def __rmod__(self, other: Any):
|
||||
def __rmod__(self, other):
|
||||
return self.__class__(other) % self
|
||||
|
||||
def __rand__(self, other: Any):
|
||||
def __rand__(self, other):
|
||||
return self.__class__(other) & self
|
||||
|
||||
def __ror__(self, other: Any):
|
||||
def __ror__(self, other):
|
||||
return self.__class__(other) | self
|
||||
|
||||
def __rxor__(self, other: Any):
|
||||
def __rxor__(self, other):
|
||||
return self.__class__(other) ^ self
|
||||
|
||||
@property
|
||||
@ -183,7 +174,7 @@ class Int32:
|
||||
"""
|
||||
return self._val.value
|
||||
|
||||
def unsigned(self) -> "UInt32":
|
||||
def unsigned(self) -> 'UInt32':
|
||||
"""
|
||||
Convert to an unsigned representation. See :class:Uint32
|
||||
:return:
|
||||
@ -197,9 +188,9 @@ class Int32:
|
||||
:param bytes: The length of the bytearray
|
||||
: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
|
||||
:return:
|
||||
@ -216,7 +207,7 @@ class Int32:
|
||||
"""
|
||||
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.
|
||||
|
||||
@ -246,12 +237,12 @@ class Int32:
|
||||
:return: An instance of Int32, holding the sign-extended value
|
||||
"""
|
||||
if isinstance(data, (bytes, bytearray)):
|
||||
data = int.from_bytes(data, "little")
|
||||
data = int.from_bytes(data, 'little')
|
||||
sign = data >> (bits - 1)
|
||||
if sign > 1:
|
||||
print("overflow in Int32.sext!")
|
||||
if sign:
|
||||
data = (data & (2 ** (bits - 1) - 1)) - 2 ** (bits - 1)
|
||||
data = (data & (2 ** (bits - 1) - 1)) - 2**(bits-1)
|
||||
return cls(data)
|
||||
|
||||
|
||||
@ -259,10 +250,9 @@ class UInt32(Int32):
|
||||
"""
|
||||
An unsigned version of :class:Int32.
|
||||
"""
|
||||
|
||||
_type = c_uint32
|
||||
|
||||
def unsigned(self) -> "UInt32":
|
||||
def unsigned(self) -> 'UInt32':
|
||||
"""
|
||||
Return a new instance representing the same bytes, but signed
|
||||
:return:
|
||||
@ -273,13 +263,11 @@ class UInt32(Int32):
|
||||
def unsigned_value(self) -> int:
|
||||
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>`
|
||||
|
||||
:param ammount: Number of positions to shift
|
||||
:return: A new Int32 object representing the shifted value (keeps the signed-ness of the source)
|
||||
"""
|
||||
if isinstance(ammount, Int32):
|
||||
ammount = ammount.value
|
||||
return UInt32(self.value >> ammount)
|
||||
return self >> ammount
|
||||
|
@ -4,14 +4,7 @@ from typing import Optional
|
||||
|
||||
from ..colors import FMT_MEM, FMT_NONE, FMT_UNDERLINE, FMT_ORANGE, FMT_ERROR
|
||||
from ..helpers import format_bytes
|
||||
from . import (
|
||||
MemoryFlags,
|
||||
T_AbsoluteAddress,
|
||||
InstructionContext,
|
||||
T_RelativeAddress,
|
||||
Instruction,
|
||||
Int32,
|
||||
)
|
||||
from . import MemoryFlags, T_AbsoluteAddress, InstructionContext, T_RelativeAddress, Instruction, Int32
|
||||
|
||||
|
||||
@dataclass
|
||||
@ -39,16 +32,8 @@ class MemorySection(ABC):
|
||||
def read_ins(self, offset: T_RelativeAddress) -> Instruction:
|
||||
pass
|
||||
|
||||
def dump(
|
||||
self,
|
||||
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,
|
||||
):
|
||||
def dump(self, start: T_RelativeAddress, end: Optional[T_RelativeAddress] = None, fmt: str = None,
|
||||
bytes_per_row: int = None, rows: int = 10, group: int = None, highlight: int = None):
|
||||
"""
|
||||
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 self.flags.executable and self.flags.read_only:
|
||||
bytes_per_row = 4
|
||||
fmt = "asm"
|
||||
fmt = 'asm'
|
||||
else:
|
||||
fmt = "hex"
|
||||
fmt = 'hex'
|
||||
|
||||
if fmt == "char":
|
||||
if fmt == 'char':
|
||||
if bytes_per_row is None:
|
||||
bytes_per_row = 4
|
||||
if group is None:
|
||||
@ -85,82 +70,50 @@ class MemorySection(ABC):
|
||||
if bytes_per_row is None:
|
||||
bytes_per_row = 4
|
||||
|
||||
if fmt not in ("asm", "hex", "int", "char"):
|
||||
print(
|
||||
FMT_ERROR
|
||||
+ "[MemorySection] Unknown format {}, known formats are {}".format(
|
||||
fmt, ", ".join(("asm", "hex", "int", "char"))
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
if fmt not in ('asm', 'hex', 'int', 'char'):
|
||||
print(FMT_ERROR + '[MemorySection] Unknown format {}, known formats are {}'.format(
|
||||
fmt, ", ".join(('asm', 'hex', 'int', 'char'))
|
||||
) + FMT_NONE)
|
||||
|
||||
if end is None:
|
||||
end = min(start + (bytes_per_row * (rows // 2)), self.size)
|
||||
highlight = start
|
||||
start = max(0, start - (bytes_per_row * (rows // 2)))
|
||||
|
||||
if fmt == "asm":
|
||||
print(
|
||||
FMT_MEM
|
||||
+ "{}, viewing {} instructions:".format(self, (end - start) // 4)
|
||||
+ FMT_NONE
|
||||
)
|
||||
if fmt == 'asm':
|
||||
print(FMT_MEM + "{}, viewing {} instructions:".format(
|
||||
self, (end - start) // 4
|
||||
) + FMT_NONE)
|
||||
|
||||
for addr in range(start, end, 4):
|
||||
if addr == highlight:
|
||||
print(FMT_UNDERLINE + FMT_ORANGE, end="")
|
||||
print(
|
||||
"0x{:04x}: {}{}".format(
|
||||
self.base + addr, self.read_ins(addr), FMT_NONE
|
||||
)
|
||||
)
|
||||
print(FMT_UNDERLINE + FMT_ORANGE, end='')
|
||||
print("0x{:04x}: {}{}".format(
|
||||
self.base + addr, self.read_ins(addr), FMT_NONE
|
||||
))
|
||||
else:
|
||||
print(
|
||||
FMT_MEM + "{}, viewing {} bytes:".format(self, (end - start)) + FMT_NONE
|
||||
)
|
||||
print(FMT_MEM + "{}, viewing {} bytes:".format(
|
||||
self, (end - start)
|
||||
) + FMT_NONE)
|
||||
|
||||
aligned_end = (
|
||||
end - (end % bytes_per_row) if end % bytes_per_row != 0 else end
|
||||
)
|
||||
aligned_end = end - (end % bytes_per_row) if end % bytes_per_row != 0 else end
|
||||
|
||||
for addr in range(start, aligned_end, bytes_per_row):
|
||||
hi_ind = (highlight - addr) // group if highlight is not None else -1
|
||||
print(
|
||||
"0x{:04x}: {}{}".format(
|
||||
self.base + addr,
|
||||
format_bytes(
|
||||
self.read(addr, bytes_per_row), fmt, group, hi_ind
|
||||
),
|
||||
FMT_NONE,
|
||||
)
|
||||
)
|
||||
print("0x{:04x}: {}{}".format(
|
||||
self.base + addr, format_bytes(self.read(addr, bytes_per_row), fmt, group, hi_ind), FMT_NONE
|
||||
))
|
||||
|
||||
if aligned_end != end:
|
||||
hi_ind = (
|
||||
(highlight - aligned_end) // group if highlight is not None else -1
|
||||
)
|
||||
print(
|
||||
"0x{:04x}: {}{}".format(
|
||||
self.base + aligned_end,
|
||||
format_bytes(
|
||||
self.read(aligned_end, end % bytes_per_row),
|
||||
fmt,
|
||||
group,
|
||||
hi_ind,
|
||||
),
|
||||
FMT_NONE,
|
||||
)
|
||||
)
|
||||
hi_ind = (highlight - aligned_end) // group if highlight is not None else -1
|
||||
print("0x{:04x}: {}{}".format(
|
||||
self.base + aligned_end, format_bytes(
|
||||
self.read(aligned_end, end % bytes_per_row), fmt, 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 dump_all(self, *args, **kwargs):
|
||||
self.dump(0, self.size, *args, **kwargs)
|
||||
|
||||
def __repr__(self):
|
||||
return "{}[{}] at 0x{:08X} (size={}bytes, flags={}, owner={})".format(
|
||||
@ -169,5 +122,5 @@ class MemorySection(ABC):
|
||||
self.base,
|
||||
self.size,
|
||||
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.
|
||||
|
||||
"""
|
||||
|
||||
name: str
|
||||
context: InstructionContext
|
||||
global_labels: Set[str]
|
||||
@ -45,13 +44,9 @@ class Program:
|
||||
if self.base is not None:
|
||||
if sec.base < self.base:
|
||||
print(
|
||||
FMT_RED
|
||||
+ FMT_BOLD
|
||||
+ "WARNING: memory section {} in {} is placed before program base (0x{:x})".format(
|
||||
FMT_RED + FMT_BOLD + "WARNING: memory section {} in {} is placed before program base (0x{:x})".format(
|
||||
sec, self.name, self.base
|
||||
)
|
||||
+ FMT_NONE
|
||||
)
|
||||
) + FMT_NONE)
|
||||
|
||||
self.sections.append(sec)
|
||||
# keep section list ordered
|
||||
@ -59,21 +54,18 @@ class Program:
|
||||
|
||||
def __repr__(self):
|
||||
return "{}(name={},sections={},base={})".format(
|
||||
self.__class__.__name__,
|
||||
self.name,
|
||||
self.global_labels,
|
||||
[s.name for s in self.sections],
|
||||
self.base,
|
||||
self.__class__.__name__, self.name, self.global_labels,
|
||||
[s.name for s in self.sections], self.base
|
||||
)
|
||||
|
||||
@property
|
||||
def entrypoint(self):
|
||||
if "_start" in self.context.labels:
|
||||
return self.context.labels.get("_start")
|
||||
if "main" in self.context.labels:
|
||||
return self.context.labels.get("main")
|
||||
if '_start' in self.context.labels:
|
||||
return self.context.labels.get('_start')
|
||||
if 'main' in self.context.labels:
|
||||
return self.context.labels.get('main')
|
||||
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
|
||||
|
||||
def loaded_trigger(self, at_addr: T_AbsoluteAddress):
|
||||
@ -89,17 +81,12 @@ class Program:
|
||||
"""
|
||||
if self.is_loaded:
|
||||
if at_addr != self.base:
|
||||
raise RuntimeError(
|
||||
"Program loaded twice at different addresses! This will probably break things!"
|
||||
)
|
||||
raise RuntimeError("Program loaded twice at different addresses! This will probably break things!")
|
||||
return
|
||||
|
||||
if self.base is not None and self.base != at_addr:
|
||||
print(
|
||||
FMT_MEM
|
||||
+ "WARNING: Program loaded at different address then expected! (loaded at {}, "
|
||||
"but expects to be loaded at {})".format(at_addr, self.base) + FMT_NONE
|
||||
)
|
||||
print(FMT_MEM + '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
|
||||
if self.base != at_addr:
|
||||
|
@ -39,7 +39,7 @@ class ProgramLoader(ABC):
|
||||
pass
|
||||
|
||||
@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
|
||||
|
||||
|
@ -5,13 +5,8 @@ from ..helpers import parse_numeric_argument
|
||||
|
||||
|
||||
class SimpleInstruction(Instruction):
|
||||
def __init__(
|
||||
self,
|
||||
name: str,
|
||||
args: Union[Tuple[()], Tuple[str], Tuple[str, str], Tuple[str, str, str]],
|
||||
context: InstructionContext,
|
||||
addr: T_RelativeAddress,
|
||||
):
|
||||
def __init__(self, name: str, args: Union[Tuple[()], Tuple[str], Tuple[str, str], Tuple[str, str, str]],
|
||||
context: InstructionContext, addr: T_RelativeAddress):
|
||||
self.context = context
|
||||
self.name = name
|
||||
self.args = args
|
||||
@ -28,3 +23,4 @@ class SimpleInstruction(Instruction):
|
||||
|
||||
def get_reg(self, num: int) -> str:
|
||||
return self.args[num]
|
||||
|
||||
|
27
setup.py
27
setup.py
@ -1,5 +1,4 @@
|
||||
import setuptools
|
||||
from glob import glob
|
||||
|
||||
import riscemu
|
||||
|
||||
@ -11,7 +10,7 @@ setuptools.setup(
|
||||
version=riscemu.__version__,
|
||||
author=riscemu.__author__,
|
||||
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_content_type="text/markdown",
|
||||
url="https://github.com/antonlydike/riscemu",
|
||||
@ -24,21 +23,9 @@ setuptools.setup(
|
||||
"Operating System :: OS Independent",
|
||||
],
|
||||
package_dir={"": "."},
|
||||
packages=[
|
||||
"riscemu",
|
||||
"riscemu.decoder",
|
||||
"riscemu.instructions",
|
||||
"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"],
|
||||
)
|
||||
packages=["riscemu", "riscemu.decoder", "riscemu.instructions", "riscemu.IO", "riscemu.priv", "riscemu.types"],
|
||||
python_requires=">=3.6",
|
||||
install_requires=[
|
||||
"pyelftools~=0.27"
|
||||
]
|
||||
)
|
2
sphinx-docs/.gitignore
vendored
2
sphinx-docs/.gitignore
vendored
@ -1,4 +1,4 @@
|
||||
build
|
||||
source/riscemu*.rst
|
||||
source/modules.rst
|
||||
source/help
|
||||
source/help
|
@ -18,34 +18,37 @@ import subprocess
|
||||
# import sys
|
||||
# sys.path.insert(0, os.path.abspath('.'))
|
||||
|
||||
if os.getenv("READTHEDOCS", False) and not os.path.exists("riscemu.rst"):
|
||||
subprocess.check_call(["../../generate-docs.sh", "generate"])
|
||||
if os.getenv('READTHEDOCS', False) and not os.path.exists('riscemu.rst'):
|
||||
subprocess.check_call(['../../generate-docs.sh', 'generate'])
|
||||
|
||||
# -- Project information -----------------------------------------------------
|
||||
|
||||
project = "RiscEmu"
|
||||
copyright = "2022, Anton Lydike"
|
||||
author = "Anton Lydike"
|
||||
project = 'RiscEmu'
|
||||
copyright = '2022, Anton Lydike'
|
||||
author = 'Anton Lydike'
|
||||
|
||||
# The full version, including alpha/beta/rc tags
|
||||
release = "2.0.0a2"
|
||||
release = '2.0.0a2'
|
||||
|
||||
# -- General configuration ---------------------------------------------------
|
||||
|
||||
# Add any Sphinx extension module names here, as strings. They can be
|
||||
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
|
||||
# ones.
|
||||
extensions = ["sphinx.ext.autodoc", "recommonmark"]
|
||||
extensions = [
|
||||
'sphinx.ext.autodoc',
|
||||
'recommonmark'
|
||||
]
|
||||
|
||||
# autodoc options
|
||||
autodoc_default_options = {
|
||||
"members": True,
|
||||
"member-order": "bysource",
|
||||
"special-members": "__init__",
|
||||
'members': True,
|
||||
'member-order': 'bysource',
|
||||
'special-members': '__init__',
|
||||
}
|
||||
|
||||
# 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
|
||||
# directories to ignore when looking for source files.
|
||||
@ -55,10 +58,10 @@ templates_path = ["_templates"]
|
||||
from recommonmark.parser import CommonMarkParser
|
||||
|
||||
# The suffix of source filenames.
|
||||
source_suffix = [".rst", ".md"]
|
||||
source_suffix = ['.rst', '.md']
|
||||
|
||||
source_parsers = {
|
||||
".md": CommonMarkParser,
|
||||
'.md': CommonMarkParser,
|
||||
}
|
||||
|
||||
# -- Options for HTML output -------------------------------------------------
|
||||
@ -66,18 +69,18 @@ source_parsers = {
|
||||
# The theme to use for HTML and HTML Help pages. See the documentation for
|
||||
# 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,
|
||||
# relative to this directory. They are copied after the builtin static files,
|
||||
# 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
|
||||
|
||||
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
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
SOFTWARE.
|
@ -1,3 +1,3 @@
|
||||
from .test_tokenizer import *
|
||||
from .test_helpers import *
|
||||
from .test_integers import *
|
||||
from .test_integers import *
|
73
test/end_to_end/end_to_end_test.py
Normal file
73
test/end_to_end/end_to_end_test.py
Normal file
@ -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
|
2
test/filecheck/.gitignore
vendored
2
test/filecheck/.gitignore
vendored
@ -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…
Reference in New Issue
Block a user