pax_global_header00006660000000000000000000000064145120055300014505gustar00rootroot0000000000000052 comment=f9e92d626e2f1ea8f3ef6deb49a95c31765984eb riscemu-2.2.5/000077500000000000000000000000001451200553000131625ustar00rootroot00000000000000riscemu-2.2.5/.git-blame-ignored-commits000066400000000000000000000001071451200553000201200ustar00rootroot00000000000000# introduces black formatting 5515c7795cfd690d346aad10ce17b30acf914648 riscemu-2.2.5/.github/000077500000000000000000000000001451200553000145225ustar00rootroot00000000000000riscemu-2.2.5/.github/workflows/000077500000000000000000000000001451200553000165575ustar00rootroot00000000000000riscemu-2.2.5/.github/workflows/ci-pytest.yml000066400000000000000000000017711451200553000212310ustar00rootroot00000000000000# 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: Install poetry run: | pip install poetry - name: Install the package locally run: poetry install - name: Test with pytest run: | poetry run pytest -W error - name: Test with lit run: | poetry run lit --timeout=5 -v test/filecheck riscemu-2.2.5/.github/workflows/code-formatting.yml000066400000000000000000000014051451200553000223640ustar00rootroot00000000000000# 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 riscemu-2.2.5/.github/workflows/pythonpublish.yml000066400000000000000000000013471451200553000222170ustar00rootroot00000000000000name: 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: # clone repo and set up python - uses: actions/checkout@v4 - name: Set up Python uses: actions/setup-python@v4 with: python-version: "3.10" # install build package and build dist - name: Build distribution run: >- python3 -m pip install poetry && poetry build # retrieve your distributions here - name: Publish package distributions to PyPI uses: pypa/gh-action-pypi-publish@release/v1 riscemu-2.2.5/.gitignore000066400000000000000000000001231451200553000151460ustar00rootroot00000000000000__pycache__ .mypy_cache .pytest_cache /venv* /dist /riscemu.egg-info /build .idea riscemu-2.2.5/.pre-commit-config.yaml000066400000000000000000000005261451200553000174460ustar00rootroot00000000000000# 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 riscemu-2.2.5/.readthedocs.yaml000066400000000000000000000007411451200553000164130ustar00rootroot00000000000000# .readthedocs.yaml # Read the Docs configuration file # See https://docs.readthedocs.io/en/stable/config-file/v2.html for details # Required version: 2 # Build documentation in the docs/ directory with Sphinx sphinx: builder: html configuration: sphinx-docs/source/conf.py # Optionally set the version of Python and requirements required to build your docs python: version: "3.7" system_packages: true install: - requirements: sphinx-docs/requirements.txt riscemu-2.2.5/CHANGELOG.md000066400000000000000000000065651451200553000150070ustar00rootroot00000000000000# Changelog ## 2.2.5 - BugFix: Fix missed import in core.simple_instruction ## 2.2.4 - BugFix: Found and added some missing floating point registers (`ft8` to `ft11`) - Feature: Add frep support to the snitch emulation - Feature: Add support for 64-bit floats to the snitch Xssr emulation ## 2.2.3 - Feature: Adding support for 64 bit floating point operations - BugFix: Fix a bug where `-o libc` would fail with packaged versions of riscemu - BugFix: Fix `__all__` to now properly work (use name strings instead of values) ## 2.2.2 - Dev: Add `__all__` to `riscemu.{core,instructions,decoder}` modules to make pyright in other projects happy - Perf: very minor fix related to not converting values twice when loaded from memory ## 2.2.1 Version bump to re-trigger CI run. ## 2.2.0 - Feature: Added Zicsr extension and with that support for CSRs - Feature: Starting to add support for Snitch architecture (Xssr) - Feature: Add support for `.p2align` assembler directive - Rework: Improve handling of immediates, so that `beq a0, a1, 1b` and `beq a0, a1, -16` can both can be handled correctly. - BugFix: Fix some more errors in the RV32F implementation - Dev: Move to poetry for project development environment - Dev: Module refactoring, core datastructures now mostly live inside riscemu.core - Perf: Improved performance by around 1.8x ## 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 - Parser: fixed error when labels where used outside of sections - Cleaned up and improved memory dumping code - Fixed a bug with hex literal recognition in instruction parse code - 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 ## 2.0.2 - Added implicit declaration of .text section when a file starts with assembly instructions without declaring a section first - Fixed a regression where the cpu's exit code would no longer be the exit code of the emulator. Now the emulator exits with the cpu's exit code - Added the changelog ## 2.0.1 - Fixed type annotations in parser code that prevented running unprivileged code ## 2.0.0 - Correct handling of 32 bit overflows and underflows - Complete revamp of internal data structures - Completely reworked how assembly is parsed riscemu-2.2.5/LICENSE000066400000000000000000000020621451200553000141670ustar00rootroot00000000000000MIT License Copyright (c) 2021-2022 Anton Lydike Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. riscemu-2.2.5/README.md000066400000000000000000000114471451200553000144500ustar00rootroot00000000000000# RiscEmu - RISC-V (userspace) emulator in python [![Documentation Status](https://readthedocs.org/projects/riscemu/badge/?version=latest)](https://riscemu.readthedocs.io/en/latest/?badge=latest) Implementing a basic RISC-V emulator, aimed at being easily extendable. Check out the docs at [readthedocs](https://riscemu.readthedocs.io/en/latest/index.html) or [riscemu.datenvorr.at](https://riscemu.datenvorr.at/index.html). This emulator contains: * RISC-V Assembly parser * 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 * A debugging environment ## Installation: ```bash $ pip install riscemu ``` ## Running simple Assembly: A couple of basic assembly programs are provided inside `examples/`, such as [`hello-world.asm`](examples/hello-world.asm). You can run it by typing `python -m riscemu examples/hello-world.asm`. It will produce output similar to: ``` [MMU] Successfully loaded: LoadedExecutable[examples/hello-world.asm](base=0x00000100, size=24bytes, sections=data text, run_ptr=0x00000110) [CPU] Started running from 0x00000110 (examples/hello-world.asm) Hello world Program exited with code 0 ``` The [`read` syscall](docs/syscalls.md) defaults to readline behaviour. Reading "true chunks" (ignoring newlines) is currently not supported. See the docs on [assembly](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. Currently, symbols (such as `main` or `loop`) are looked-up at runtime. This allows for better debugging, I believe. Basic IO should work, as open, read, write and close are supported for stdin/stdout/stderr and even arbitrary file paths (if enabled) When trying to run an assembly program, the emulator first tries to find a symbol named `_start`, then a symbol named `main`. if both symbols were not found in the file, it simply starts at the beginning of the `.text` segment. ## Using the CLI: *Current CLI is not final, options may change frequently until a stable version is reached* This is how the interface is used: ``` usage: riscemu [-h] [--options OPTIONS] [--syscall-opts SYSCALL_OPTS] [--instruction-sets INSTRUCTION_SETS] [--stack_size stack-size] file.asm [file.asm ...] OPTIONS and SYSCALL_OPTIONS is a list of comma-separated flags that will be enabled --options OPTIONS: (-o) disable_debug Disable the ebreak and sbreak instructions no_syscall_symbols Don't make syscall symbols globally available fail_on_ex Do not launch an interactive debugger when the CPU loop catches an exception add_accept_imm accept "add rd, rs, imm" instructions, even though they are not standard --syscall-opts SYSCALL_OPTS: (-so) Options to control syscall behaviour fs_access Allow access to the filesystem 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 memory, 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. ## Debugging Debugging is done using the `ebreak` (formerly `sbreak`) instruction, which will launch a debugging session if encountered. See [docs/debugging.md](docs/debugging.md) for more info. ![debugging the fibs program](docs/debug-session.png) ## The source code: 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 generate and make all doc files for you. Finally, you can open the docs locall by running `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 riscemu-2.2.5/docs/000077500000000000000000000000001451200553000141125ustar00rootroot00000000000000riscemu-2.2.5/docs/PythonDemo.ipynb000066400000000000000000000023341451200553000172450ustar00rootroot00000000000000{ "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 } riscemu-2.2.5/docs/assembly.md000066400000000000000000000031131451200553000162510ustar00rootroot00000000000000# Assembly Assembly tokenization should be working completely. It knows what instructions the CPU implementation supports and parses based on them. ## Instruction sets: * RV32I * Loads/Stores: `lb, lh, lw, lbu, lhu, sw, sh, sb` (supported arg format is either `rd, imm(reg)` or `rd, reg, imm`) * Branch statements: `beq, bne, blt, bge, bltu, bgeu` * Jumps `j, jal, jalr, ret` * Basic arithmetic: `add, addi, sub, lui, auipc` * Shifts: `sll, slli, srl, srli, sra, srai` * Syscall/Debugging:`scall, ecall, sbreak, ebreak` (both `s` and `e` version are the same instruction) * Compares: `slt, sltu, slti, sltiu` * Logical: `and, or, xor, andi, ori, xori` * Not implemented: `fence, fence.i, rdcycle, rdcycleh, rdtime, rdtimeh, rdinstret, rdinstreth` * RV32M * Multiplication: `mul, mulh`, not implemented yet are `mulhsu, mulhu` * Division: `div, divu, rem, remu` ## Pseudo-ops The following pseudo-ops are implemented as of yet: * `.space ` reverse bytes of zero * `.ascii 'text'` put text into memory * `.asciiz 'text'` put text into memory (null terminated) * `.section .` same as `.`, see sections * `.set , ` to create a const symbol with a given value * `.global ` mark symbol `` as a global symbol. It is available from all loaded programs * `.align ` currently a nop as cpu does not care about alignment as of now ## 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) riscemu-2.2.5/docs/debug-session.png000066400000000000000000006054571451200553000174100ustar00rootroot00000000000000PNG  IHDRJ4 pHYsttfx IDATxw|TGvRR+GsB"h0$606N0^'[Ͼy﷞;06N1!Pʠ%ZíВZRߖZ>|>c:UuܪS$::z``AAAAAda̵     2-0ҍ     ,l0ҍ     ,l0ҍ     ,l0ҍ     ,l0ҍ     ,l0ҍ  Cxde@AAd޲ AAAVOOo}xΙLXNQ+#kY깖/WY9lտJq_/_:1B|fAAA c޺z.sFA1Q4o_kYN}EZAAA3|[ݡ I8~z>o@H7 2oa.Oqz9DDwc̭&n8/g`LNGT;5'bLϋ>Z`ȑ/ 6_͆ pp9>Ū +-3 5Œ SHs>~PfŘ+ ?WZv7f دɯxB噷Ii>~āv|3 {?`…-#Kڼ>1V* ΃d!%ݤ|EK],-O7Qd2xNI#38U3m~!X* XaPu,@Q#xe-=jW(Dh'%  0{Q<PBs K*`\BPp ER0X5(;H]d64 !*5PD4|7Ԓea&}ԧ@z 'W,e$A<5x0u4oaȍ@@`IY_k6i\u@w)!'88 Dnߞ~)?|n&\h%}Mե9iWf"81)!2Z*uogKmYy(BV_f+&5Ew.]Hm< p [21Ju6WLQb@ N5_|JGy.^c@Swo\@$.+V&EDl+ϻr^KtZdՋ㓩 e+J#dS ,&m\nkAk2~{ujnk6R.iGuW.++o||}CIG͓_~S9$dlyϪ93p OZn/ yT\]}ꍂ)ZPmM潺<C(NS5's7QpJt+hH&8xY`kJ=9 3 3>n0.|BUeFyYeɡBWfY7=5}`tfìp0Q㇯4LS߿34SuGmEuّ.ehoAzΰ.Z@ɘ6MGk]LA!Ɉq/__@:oGFȸoןsԿ}4gvŜ1(w=s[zHlZj-b{ڪ^x} `0j 7حL&a-oF}oN޸nqh(˾ƑLMVmسg&3z_}UmĞ/v mG>nh'HeR֏JEIkHshWIQ cۑ)w EC3 [DN~><[n3Γ?uAȹtcN%th1RXb^V3Vd́.@or $)WTx'Etmy 女M^ )4d\vxh)m͏摯vkr1Q\0BıAŒ-y$6ٽ^H|y(Sx+y]0ޭu\M8kLd!(g̝oˏ_\*AB[P[`tTG)%76ʎ̬1Dx_7_! |P$)BYtukyd\jH*榩Zg3jL_٣ 2ۓ\("uR, 3Wf3G1̈ J5jJ5vyJ)%|7c3q&1KC$}~$?4R,;V c mu[KBgR(3&ʁ1+sBS0[ b΁q^ߐ$bvO2rGś}<ݶḭlj(yG,BrahP{v Btb@Yjn5X*@.P27!:jHe#S^`}8]DscT M# t[Nĩf= Hx,YJ[@_O_ͭzxFD;~ސId;Lt3Ξ.Bᗰeצp%]M2+O)";߄WDqk8bxŎBJ;s&%C-v[uROvTlI5sBSrbOxN^KuaYc^"PU~wr{ON2XԞ{>,t͢z%:oX"|VINpϿJ #:i~5\ "*jUكx2U;Wl/xigLl5sinΪ#6mPJ___jupG__Zj+Y8-}K=3KHrUjyb['-r[=^:#k!'T]3a׽1v6k ݡxʨt@x4D6Րψ*ӳŞ .:7$d _^v79Mj^91OĘዶgGBoR32ΊsO>#ݨDDlM4{efԸf*^*Zi}H%H[jk 9=7 f9\i)q-T|Ϸ+4)}{[m^YN)-ȭpYzpg7v+'MVgn֋jϿnziK#u ^}j_c;se1ҞMyՄz6F}~a!a{^mSzO<4hh'JZ}[Ol÷m!j )x"`e6x@ h[ <[z/RUGț*:Ǥ qԣT9VPO 9wT,$vK0 DӻUDΙZe!9sm$mH^9,!@@?˹BTSo+LjOuHϋڅEJrcW!(ڕV ƒE:mO>V Nĩ;=[6PzNnGbhOމ :Gce. Iκ1 -wb; +%)_py@E؄ :\ݘ8Vv/,+(C"Ą*sx"KNuۃ;'ihbUNSBh)VXۘՅ7zMNەf >0P͑:%,9.< ]u>nR_-]r@j3*- 0o$ĥ`nd(r}Dm~u ZajTgΚonΪ#Lk]W=ldVRUMUԎ'K ;.홭ypF1^} (J5ט}Sk}b$(O;fkAnKx0g0ir:B,zV U;([x]Jgj6j5aͥA2 ϺY[QqFvmϘSU^xW?m*"W!WNR aˋY`p >Od[5{,҆-ljT8Sؾrrv(Q*o2VTr.Z-3*- (-,*.oT.ͼDȷ_pB]STdWx<1CwtvQ̳LɅ u)A*Z,D`{TB@GzɡUd  =OO[fۂ*ǞnlC`b^ Yus=kJ*ő^V;.jhnmm{3劗EۙQ2S^bL}@06+9`ц 0n 갌M/lЮ'>8?M?]dAhWNzI펶&kǯغc}j1V iHlЁҌ\_<8Øm`'(Uvо~>_/xa0hN_Hl#{8hnjojuH{6f3&6680 i}}@xV r`k?=VV^زzwDlߚaΥ{=/s>-]ȸ 7cz]NUJUInV5>t΃C) r4&^hDD<@N<s !nf\m&Xqd9dGJ,?0UC)^O"*B5I,6Avnv.^Pax⇝U6BrP1UfpR1R/<@ KL]RUBL1[jt:+do)GHAѯzTb+;.s2$N5@wGlCU`!}ŗ,@JE,-ЎkD*RopD*^RSb4^fN5sXEmMTU:vJq6c&H|Wl۾}J'hlB"j4&m؂$c>Kṁ\TgjnTljgm_{g?X{9FaPMEfB5]uS>~ ? C =afA;ȧ=Uf7j@E'?iS 0'f:p֒s~1Ua4`/Kϝx#OkCSښUjyۖ`z[p>330rE 03-aa>q|Q#1@1Ӫ4̱0}͊xlɿֿ&[YPm_ЕT[Y-L,&X98 Gmc{n:@h/7@) |%TC o.l`hI @4:t0Bnk$p*$ EN=PR6 tRXw`Q 4 i| ^ȼM(4̖%bUb sꆡ3M".f7v 0 *S411WD+n9tqw hx xyB^Vo)M- p(:>8֬ |޲/><;ŞB-sܠMaQ~BB5C[5_Kgig+Z=S$%Q kқXFrFIb7ms}KYJ 7" QWWœj K!Uc4ǯDd{]n q*kkvV=Y2vq6% n4wA66޶L܌ CZLc }wX]=$H_!PtSVd('qsF6i\dsmƃQgݐ;nb#PKBJ2srWq38`^\f'TӒ,]m(`+z#(gsj=krFkN_&7jGA N5/œ1,"3Qbl7u{g;D]z3~}yۚ^4~DtsxlG?Q;Dqjm7{/^1kXS=u̡ՙcj=s҃#Tm0~ah733ܭ8Lgf 'L]Ϋ}?4vm#_, =檚>(UD_)1]"T0@*Nx(3@{*-^Ud]TfMog>JBw1 %`IaP@3O;gaDPX4ú)`djS?w}8ǚG`7+%3_B!ߠ]9׳X" br% ]/ Sx{{Ճ*J$ᛶ8Xy|i}M }U1@,bODDh뛴o_yZ)*zZ'v oW_~2v%1B%*u)u^=Rize9 lMz8x nV"#t^K}Dw:;ۢ,#vǏym6c;[U!2ZímfK-wF(m7`[V2Jqc7)*:ϥ|Wl{7+CD>WY1"aqAbB{gO0=P4A VmfjgZXf}WשApxBs=PBhw̑7M`)?7ڛ7J' .b(O\HIZNz3 ]4K%⸕Z! wOPqmׯ/-B, Sh"$Rv+r -{#Q{mW6BǧBq* NV-0 '--\ZHRP^Jfbrlb ,>\-itB]E@xL]&r dL\ͤ[@HƙrIŐ8լ/9 Q$$Ch ];x{)NϭXK)Eo~y"[ow8>HVb~߃sݵy.b̯;.DOh3Xj1ѝO.[5 {Ȣ-v^=~FN5 g3Ɛ21Kl .ؕ!o_]}V;l޳C/)9 lˍ<_\4AKG/sӿXNYJ a}U45Sׅ<7a[_Wj8`7/}ղcn?28j+o|WdUCouRΪ4I(yNZ{|陶?Jrug׏?s!ffb'$;?ofWG/0D9?G=^ ~Wi 70Q *I^m4L((=Sf7zO}x`{N3B-k 5/j٫o%yI"zQ=00>r272|Kk9k䛿Kȸ ^Թgyb,TB"n~([ |뀵/JHمһYmD^ )3σ:c90 aah733ܭM1mf8&yEkV/uͥO9+J1fdl˟rU~rv^=j҂OU5erv!p^sԕS' -.!m <,d^UsR0NRV |)H \m%YN"|Ov f0F M7).&Z`*IF VmkI-)Y#Wr{Dl!uߣƊ_y4ha9J.ԏĝ&8agiʪzVfc-Z`Qu_+t&c X"|F\uOg4/9؇qLjm̹iO': )g ڎFIx@ʡIf}y[4D,0@wKUo~qbʚ܌F@$dw4\?{+e]Qbl [D]ݡcN&s [Э* m̗AkRw8Gc' g~'uwl34]ߟ!/foc&W @^v =ߟ>`LJgi;bUK;PП f2hu2|? I&5eR? @<9(*!iM-\$ .V/Pu[\1Dlnd[K;m"eS# ?<~q TddYxPe*)!\8 RRuqzȂQDF/ڕY?uyZ->_.ε2ڞƢ[)gv0ҍ` @t L͵\KG[v?=nUmũNjSOxx YpKoB{ si]01<¶d:["_?)h<     f\@     2H7     H7     H7     H7     H7     H7     H7     H7     H7     H7     H7     ϵ b݋]=_|ikAAAAအH7c'7e5-%97 M: g~'uE/$`b7)jҸEK 4/Jˎ`ZP"G煞Y&:X7zA߾s5vEz:ZIxΦkԳZB, EĦ}VxBه$רnu|OX-{d?δ9^5'AAAǘ=݄[܃݃dݦhgf{8 zjdUb,Ld R/=CL Tx. {zMsy! "m/q0aٮ >Е{T1,(ժ bF1AAA1I"ׄ'q^i*_ϴ7i\ж{\ 3nۿC uϞSٮzEٶ%sU;oZS 0>=A@{*o-ؼ;H08wwYcܴ3~6N^+P4w-s   2j+}Ku;'&Jچ  D.p OZn/ yT\]}= >~翓G<_RZk7d&40 IDATZо^nw=K䊕 a~nrQ)n\W;<.0q OG+ h.Ivuqonia9jSss{s:Ṉf"dqAnb^St҅:jĤ@OGkԽ-eySo5O'TUv/oTyYeɡBWf@_!_W)76z 4u'\j^hc_^G&ȪmH犄~bP9ycj3bl#i>w~AAAd|ԡ HGf#opQ?ytQص> v+ޖ,K'n  ԰#g: ,Ol"s" I~6@e,~7xˡm>ߖU8"PjѻgJzn#'!R L3O 0.zO [98DƆgg';>Oϭ1E#X}OFg7^ .z NkϜj9-2B(Պc78;/Otը@fp]_f]" @G@d~k'95^?uTQ*D@]{wAolr^ku;kC<]k=rpK 0̓_q8cd (>9uSvO?8U؁AAAt8E(dTX/޽gzΜIɯP Vmݺmž4[9zXE;{`zyOjGjYm2b~m/7K?m uk_~9X /p># SZ:ΟoWviS,w ۺ)೜'\swyZ-dIVyo؆KUFjվ2v.}IgŲigɥSn+?~öB;iM8x^諽髹Uψ۞Hps[wg5Q8a)z6M0FfmꌴgN5dWJ)SjEv [w7,rǓ.BucٓeX(n.TgHc~Yz>%4)G.tT`%?%UwG.ozMNrHLc9R7q #j^؅.޹%H =gOf<<+W___w[.Hn6_޻!M:)AAAA~L{YspqdU @ rUݜm+9疫(a]Mضyգvkr [g}^?ٶ&V!y@VaFZhgŝ멩3+mj*H}NV(M+)0 {)O"swam+ȮKߜKM̟̊.qp*mu*JxnNc F)ol681OTg=sqp-(H]Whz^]S_96 g){}M7P"u;o'_XDzΞۮ3'" kI[E2;_^\a1/ AAAA?@ ~r~?]SH4n3Q= BDtqe~[9:Ez|=kJ*ő^V;.jhnmm{ԣ9y{l\.ըLKסkN=68)~TA! ax3Q8a%'= Ni\j&" )L{=My2=sHlL"g?k'% !1_r蹐%?*@oҚ1؝OHwڍkW³r߰u]][x>2AAAAL? jُl8Tc p_7<0^2ڙy{M^UuTϺzcw;q >8{o8mH3a.3`^'DuF۳5G9@,ɁR /p,u85/SAQ۞Aoѹwsw`׵?sgvջDQQ%b 7l8v$N^ʋ<%.!ƀmlW.z?$Īꋿ@gޙVgΞ\~rˇr8)(?xI׬ne6]71\W$([}t0yjR\TXPZ8-U1dC7'E>f ǺS۹zj0g~$rDW-(<@ADa ׽#f >P5#~;`UؘFӍt3X_uDmG׼@7i87i4jFu1]=Jt4]:ґ/$* 3.Mfe%~;Vp8Өםl{p8;u4:ClHS#RLNT[NC=qO׹ #HXb3]>[l˫\g/e$xv_mܤnnJFQl\Scjoo#ޮkl1[8\t|(qQC'UkDd65Z= 3BADj[tIIrXyxDvs }K"1yXn$ '3q̉Zvzm̜;.3}fd""FRTӹׇIXq8;w49ChS#sCǑwSzy)ʪZ5|m˖x1s(1G=&Ȯ&ib 42mD䣹hqb>N1qӓcwS:FJG_kZQZ;yO^AkwUvQn!ͤ%+R_2tjdW vlo?cf_!Ios=,飔{@̔<'x잩>=0pV[r9#jwxyqSa pJDD̻{OYp yv)kɶ/r*L$xzW/W¯t;dϢJT.Wt:RP&?I1[44u9w>PVS!0bWXn7_+"6֛9 aSL (A9uŲ.0G w.QtK}tAvhNgޚ'UdfFI_LڊGǫן>tzK?9 Nw?'=tΩyөLidFJ>c\gBcE̜%,\:9P32uO/;{׃[hLi1~׆ؚ'~2o]K&iRs]N;TZH=i~B?Gv! w,<ߞ橐H2xlim'6y-\-鶉[ׯRbG~i?n/񦋽n%VfGON&2D`D.lP֒m_lyd^DĜE#→Vҭ7n|y3c<䢾}[7NcD>,ֳ݊U3^xkөUxpWϞ7Uͧ6|wט39{*&ަ KW6u;7JR:uźxdYϲssv83_pfx{y4L?[mX2EXr8;w4:CȼW%XuTƏKm6.Ht_ە rl֩bSMʲ#sTI E=p58\Or qS3o/ק. 3^&KEeW~[Jۓ̠qu=#b0ٱ=jeɫOH?ZʶkmOW$E_7GHɹRWv]{OVm 633c=U2Z_Qtؾ=GZpoߴp8Ҧo8sSu=wdvq*|{Ox77V}Qۄq2sӧDŽyL.9;Hq5>Ho);Ϝ᧑I؀:wņ܏^:=7szBLu ':V~ͬϹoR83svʸOfl+`ނỗ8N 'Ñ=tΩU1)BUTzM/?bǻo2nXDWW|4g}ޠ̜ձ6$6Cs3dwR4*V^ju{'=>7p»^x+.kى93Dž+Ȭk,:slRRRL~z[znQuZ{aXnʑn"Ⱥz93?:;"d$Ͻ47MtKW*Im헷|x>t_Źh5뛫ߛB# nV7{n #=A\2ڐ׆L76d! npmtkCHHan!l6GT;M^[Gz:"?xojl6@HGz[ lFz:BꡍOkRܼM[{jaOd8Py|o:VmGzR gVA'ǹU:WoYu4{/.o_~cGKfȧkv{0<FbοMh"K^}ӂƋ_8G "ܑX~{oin"hڳx( b I5f4_ܤ`BnY̜Yr6Y,7^ZJY5'{G)[ 9cd̍>%HOWM<})ZCC ,*%[Gn# -\ILU!ѪSL1wm{nݝ_^ v׶l`޴8.ٲ=ԣKSNTIls}AKw̄@OMW[|r+g?#pQdD3uA/|o_m/#^[2m;Nqt[jj Ǝa^{ZKmؐs" Xdfve/]I~cgXo5;J ,ͼS.a+ܱ M;&m]sRۿ὿.4&GRfz~V3}(rⅠ ߾D:)cYvff;S =#,Q(Fh0`s :ũݛh䤑("t1[ԗn|zj}v`g1e^lmsuI |;gITN{ᢆvPtxO9SOٕiŤ$y $Zgk "h;sgP@;~9myk1Bg%⦆>' &WrȄvNd.+I4j=<@rv]l0q""k8NI鹛L#Av :73/c00$n9e6~?S5&EgVޔ^ϡ vXҾΞ*p:ǖ Owk[[ ͜IW  T1Μ,Vk);q~@^! c'<0v":2 Ou;ps' ͜ !y QGΠS9^ҋ/?yBgߚ9%4Z!oջyum=QXkkVsv\ƈ:dz:HPt5tfJq+f z@vkH$eYt\Cn"b 3NҺ1`p3j+ʟ6~aYO2^6лNag)np=7t n o7'1D-7֞ڵuˮk]]I׬6WҮ=}q.v48ݗ伳 :t 9͞DݺeW^CSuk/?_Xk#3MFTQKԚ9tʉBޣ4+Dĭ֞]4n=^T(lS?kZg7bět a?w$=g IDATtCV[Y#(嘄8Saziܘ39m+:z.-#΃_nX)F>&iwai'tsc]m8;:G(:joL7#l߄ҮFL31iX[U$%~CÝvh8u{bcLJ %ŵ#T}eD`ƚSD] Ϗ9Go5[j_ebMsqhkɶ/r>!LśǬVj!*%OʈIRR>gI0o:u҂豚?q\gĤݱ"V+ѶҼySmVgX¼%$ 9o +WFDeD SVN NtGD}֏7]4o3n|y3c<䢾}[7NcDhvNꎀv xvg:*Gϥ6$o:W ݣ%۾ȼ9>73&0F %d`K7kL~w6I$#\waWNb>{OEs ͩM3GùmGνί{ƅi}6}SŹܝwFXgݎħ,z0 g)3{xQ%3pm,%%dFI+\$𖧞[)m^`AHoy7#xKo;ybTY/عP޹J3Hu䴹zRw>?;pS뫦 2'7o#+BFb. )7t;ع1SYag/4ll=2ݎƯ|qU6/olK0wep΋K˘  sk@wnL8f}su{s_ht17۽/%rn"_96&npmtkC\2ڐ׆L76d! n&IGz$9c%v?}#|.F`%՗|F,fNFe9J6tM-ey?pi^ 0D[U}idQ)8R<7>Nݑ O]4LRbB2ePZOfh-=?gcyL t'm|b̃/=,~;꺪ť*ImG{3}6O`ʠia*[[jK߹nc_ڊ7zuwP_gok _6\0oZG UlٞWm2ulNDKඦ{|0*gu&h}&V1RY?as{t8ffb~Jjo6;Ѵ~Ks]l٤~f_ʯ/;G=Ly/v}3ܹ/!3o&.TdVDp|Y;u@ UO5py:[EgZ!qiz! &ҝXv|;Sn;.+WN _IJ5afOIw>k_4?jfq.J4AqVDyGfi-vfʯ$WhǧiRwx$sSL[[K2C8+C|ӯsҠ(eI/l6\Ma lBqe2nok#Qd$8)cDlJYԀD=9QNyCʕ ϓɵd1sIy0YMSQĜ)l׷;oh O rzHDLvcԚ_ږ]K}εefSg5u%+Tz7.\g|rGn)MLTiwl7~q +#2 FsHA@d+%\/֟˼d^fiqVX./9ӨYb-c'yDo4\.j;"jd=@D$7]c`O?wp+z;L$ ^4Aˆʹ:/<*a~S}gomn,d3_5Dٴ{"~1].a9ch0A``4N[Y=6rK}IKBͅ5Dy6Xf T1Ϝ,؏j);q>#GcVHDL&!\:vfS~"KRߏQ4OCwPLKE"#mZSpzS"D&45:7j-땪k-6J=:ˇExc\>2AArhl)(H&!G&溞;ě-ghibDLPʉFM[:XS{fsN1nnmz+ >ntJHgyK}f'_8A-_kdT""..9yj8WkmE_as\:<5).*,([-IN[s~qp5sv =ڮ>ֽd=ח%dW*0\m1W5p"Pb&FXoXr͎ssBF/Ǻ5˼w:!=TN]ZYj;uopAn49hWZUw4j6R_""Kӥ#[/B 0sMlVVbs8Ρ엡Sl{Yl _FOsZ ,e"$2<ԗ L 6KU=&)2OF-.}\J$VY$U#=> {ZD&57-f '7TU8 >" "OTg34\>Ǜ/aA|'vpiNM"byKMR}GZ虿DD v $`1rb2w<@kkZ(IUDV`kDڄp6Ŏ4\-UJj[c}'LCU6.LHvj+kDΔcܺ'^رcF]Mي?UY rD?{"j߹<{9=BǽDϼh9 Z#;2Af3Ts΄IMyȼƩ]MILIwԷ(zCTZÉ S4Kdʩe[yʤ믿=@[^$b75xmd2F$Z1m:AߑbCHD$}{#Tۖ?2{̡!sǏsGF!Ǻ]̸+N9n;Ӽ!<^͞fT7uޢq^i;.2d9czzx|VkcVElj!HRIW٫D&WLrblԙ2js&.(uЫ>=*` (|:[%sˮO*].arل>07a}鰦w)(B|N9U#:n"}j䞔>mK$tjHK TDSk}Eљc)j镏?~3"nX۵O>->&C%eXu9GۮQm);Ϝ᧑Ilh9>gzćɽݘhVr7jo궃x{}3<d^}/s"jo_+e|DT44ה^߷sߙBC/;#ٓ!L;e{2kŶ7gFFpM17-)."K-'K{K]œv=Ym鱞]=yL07#->6C%pK{Km;t<\[Kc2KGwnu~b=z:,um3b=6]m}7K% ߾Ǝ:cXXϣy=wv,fkWoxo9"3H_;ͼ&fxwhSҦNwy[Fa'aRvRjQ2@_FD$ (sqno$~S5=H"_3##.Hq έso&l-xj8F!H߰ 3?˧wg@D#]0%M*Hy%PJֺG&p=9~krS'P{z5ͼu@9۰!TYEd휕ˊ_^GdWo/uȄ+2h"n[>;j:ʷj’ot#Coy"H̼4v}KXkć^Zt_}vmbVŃx2~8:43;m oTx.`9&i=3B~5 M[ba ?mfI`zߘ8!r2Wr֓6[;qΒss.tȋ46۷:X`hB'e,Όь[|gj]cmbcgXo5;J\#9܏<7 Vm{79[yz^0/vpj? ##x0o@r=~R١|l׽H)Oc"S椧Ϙ3hCAd-NVvw/"X5yڸMR^Pv v\13rFʥuGmDwB`YlϭF GFQX9b?rO3XՙojoR8㺓_b7^zԓf$yk)(gkm[t'6^LMyGOOڃ?L=1-kn2)#"&^}zd ys$$ΊQs*q<FF ;ʡUG[|BA$"s>Y3ոXMi`=z B‚y*jhrVCcq~Wv{Vo1!@rv]l0q""k8NIEa=zbRkvH4֝۹buʼnܿ<7ml|ed2->Ay ۶䕷Z8Pwqw99ƍ|GC&l ;UH0%#W 5)=5TN;OÑ<`D2CebݑTd{.  7nN М( ׿XҾΞ*$% !Z)yZ|*( 1_=;;mVMŲ*=>vA 7{w5GVo+;}'[jD{(IZE"d2 z-Sxj܉Bsr<0$P>Xp #0PH?s[u 7gßy_=]u ykqafCIa%6V#PPv[C6tr硨MPs!lVA;ntw=,ugֿũopdr#2t=k3:}㺶o,͵5jv&W#r06Rq/(TF6{_x'¨Ȫ#""-x5 5& D&0F4 Kge-9ɘxl/gc7ݣ%k>Z*U\45Y gF +*(l1>)-(<@ADagsbd xp,x`Xwvn{/W? .DgWv>㿼=~AgLį~zݔՃo= 2(]=a=z0tc*ЛE77KNDd!Ap q]]KO~Wg]L nn$Vy{:[GS}ʠeO_^}z L) x+ Kӥ#[/S>a¥1جs\;h7#'b^ Wwo82=(F1sciI k8yl$ںF`ɱrmeU]wXUZa6Vq"֊n=ed.پvWZnG3YaH##CF'ʍW`n' DvTD~oQ ]jhMHn.-i&&3kj>JX?`؊K*֓:h\EJizֳ##xo6 ܨwQ]wϹNhTFw!@1`0ű울)7dM8[lkcIDPkFS@Ih.<ߏ_p8\tߜvm-,l]]rY$Dlnm2,65ӂeA1.y/D(55gש)K\.+R֒xz}##p@xMŽD\֞ۃj fh+;LLoIN];ywK8u …5\ɸX5{.9A~Quu-&ԅ[#sdDg窇klI t;}Ff_ɮKze2kv]j ˫d3E|#};r.V6a}Չ]ʈJmsAAF%{bO]Gv乒'Ya{0!lŶUs"N ZjZ;6+܃f>y\/918S׳sD,X<ŝ4vfF5-]6A4{ |Tp»,]Ķn9\jEsB'MѾ\36fuIi4JyA'wzL=SuPll ~E3fhH[""ok0;zʔ2bNscZ#(Hcjin3X\0m)kEPkx 7;!L_ DL p4ݬ~`;o2&{{ laGO_qn||GE}´Mq ٻGRu,xf>o}6b~]Բ#3f(K{CS㼓)FiJ{7׿r?X᱄aOWl~]2f1Zz;\)?78nԳ}>Xw0'o8xyx'CIϚS{P&R(!u;kz,/nE#rQ__{pEec8)?9增Qϓv,d/<oi,6<z#8mXe{s%5uE~)\)EXW s+ɣ YaCg:Leo'sa`ؐgW2E[68sOo}##<0`OS^x;{?SɌuײԘd*Z V_v=;P{{tB,7s ERyjj-9ZKS~vNAPuL,>luWP/'|[MƎV=kn.2t5o##<ư:ZMuuU{?\ !NW\dSjR.pDt78ӝy֞k_|Um+'> wLBYa;Y;k sϟZQJ!VAmXe{s;+~'9,#F]ϣzFą&Uܦb:>ܹ˯e4#<"h_w7gF`Yn\G߽JڰH_7]t\^`*NIp?:pg!L^5Ӌd3/deeeeeel0l RҜ;ݜdÎG섵!ŰcP:2Nx=0Apnjʌ%Rj6ck啣UGώБ&nv$Ӱ YCs㴔~ήS z͢o(8Ϯ%:]=O؜Ya9oY:~k5&Q{=w+aa|~u`8:}6rMtp<Ya'rdQ,0=dnas>x^x~wmÊ(YNSf/}W+,^:gf3gg^|翵Яy-Ͻҋ? E`cO_x~l13S~2/v<$P\&-bQ!0$َ9˂V?oPh&>`7^͏]GCa! f1dz~ #T~Ƿ?n-tsc#ۉrG!l W^x JRy^/lwu$'svЈ|ۨg4zb,06&bٖ'ώwS˘"*O茅kn{0Ƶ[2bBTA ֭JVBl]]fB\&/DvV~Bx]|\y6d_:02b IDATcI8u˚C*O)wh좇&FQϨgѣrb[ Wh(!N;{lLp)QoB\IZف\ox;DF!(R("WmLpĶK_쿮ȣByACo緺ggɇ`o=KpzzvF=K= =(%+bu2[sީcZM"AIFOY@a?>wPW^6ԭ3'fh~耭[ s5^:z.l\hyJ/#Gꇸ[]Ga#Ɉ u&Sz^$.;nybǙlKxxbM?V/~&Z-}g,_5w` 4$~굧ㆹ4\;rl_<5ʟҪp>S SsO mѐbwu5xr)$/_+C?сG۲-ѫ_+;0}~}gbS֧o_nQݼͩ :VuW42qFp>hj Qhy{ԤnfG57u?畸m[poapvֳ,Q08{αF=sC{Q.|ʔJQN*q0z*=5%40q\Ies[{8]jst":eZImSkkk[̭z{pӯf:5jS*51}d=[ݩ&sOzE) {=v rjؔMDk=w+vfd##b|Oۨgt'oڇp\[Ϥb#>i~άjƙRB f8quےu4v8< cǂܗOqoz+WvA=;62c O &26Ϩ,6}`SgGhH1L%}ٍS\H/v,6bd WlPi/ECmua)G~}b;rAo^lo^lt',h᪅jj(?wxnyK׭R'XS\5Ww"(vYuByr}R###<(mywV]pjT2g@Դ{ qw=Գ##;1id?8?K><$F=K=<ޤF=K= tO>`J#ߜ+ޛbS˹5Omh?k{sVG-Y6MK,e)%5򇻙]GaãdqF/87lfRr|:(qAtWyU:١Sx∵ɘ0מ \;r阎vT; TrojĜM^+ّ=>+rĭ-͹ѩ;wfSZE:%]$Ww\Wbd4?8?߿,mF=߿aiFP*(a?L;/|-3#DR |U*9!lV9SMҥ1b<]g!䔩]##pxDS{MoW|tԳ##3yslNuLß zaG6B8n}.bMyu~<:~6'[K3D)VW߅z+ =gꑐiˎά6Y~yup͉EsYaBfL/^NTz#Cl(,j=/8Eaoc$Dl**l=#LEBn3q?8?W ,m}8NF=Wa m!^r/&»M[zGs!+r{vǴhoGPf6`ڢS?lGLbW-d0}]1-#Pt߮v8l1!!>\GKkGYd\1oYw1],Pf{Kh˦xg<9-##<x5R];v18C7n@={BּƒApWz*LYe0ΡBäny'ouSZ7׿r?XYa>:g-vMιO$ 0Cz#;Jܶ-ыUf~7w[v|>#kѩZO_g0zb,Fu~b,0`O$ 5LqQU YWspuX4_Ym&k[y^aIP( '֮Ҏ]kt킷rμ眵c=xy}hbg]2jFm)ɯbv`]g`Jͬo+q!șC={mEEJqB>KS~vNAPUJ9O,>l^-rμ_O&;/4Y0Zjճ憹"LWKo_u4$tӗ&y^buƅ˥TRrXMYG>銾 {dGط*RQ)ƚ}0Nr2Ya%F=K=QF=K= 1 ƍpbt ҆N7H: mt ҆N7H: mt ҆N7H: mt ҆N7H: mdO%G-\,OE)zPm4,㛗i8,vÜ#e'njҲSC}ԼVWvyM:ms?Kw垂[5yx'Coh5/9vj_A12c6㗪 #hq.AqI>ZQJ!VAmXe{s;G۝aqÎu=0y|o2"nZK{ˉ;ʠEULu<=6]##pxtG1Fo;T 繁7nλ1=-u~7w$--1vG O< PID(G.}fyj7,8WkdɅt3fXLε, v|]o CgK c?= nct; bG=a8C=K= 1-.K=xU{nf+?d42B( ˢlNs__n83!*ϰ6㫛/'֍<<.֓' _TgN9I'XoH=󄎌0y\ܧ[*20&ҝv{1'o B&zv2cɱ}GTmZyc%FFѳ5+a.uTX_}0Nʟ8XDQ?? B6VGDd>k);M:vZ@`J ao/_5n&B?лQ.i͟>$ܻ|vNr2uAoY vU=ڸ9Z,88Kו9<\wӪDOZcdj6L&d4V_fFf{uF5n2JW+یfmf=܀N8zCa'rdQ,0aOd͈ tUɈPYtrNay@t#䩫&BOp:_/2KE~o+G˼|uܕN!>SS}y1O?~ؐ32ŽRHW Tm撂glQuM&r Wbo଩L<[y?MWL hnʆ?QuAZNcU?\  yyRI};Շz/Vt\jCbkgV7*h]nܱ_S1:Vkh 3߽֜oߙ.F7pv.\@H@ ;-YX[ł8y`HwF!U/:Df>7ymVP*?=#K2Sbs*"2T0)Wl~0ۋQCBcCsǒ~̋)}S{[o 7vЈ|ۨg4zFy(LW.x B=;[mH1 A=&bٖ'ώwS˘"*O茅kn{0Ƶru;ڽR LN 1wu!UV%!.3!T.݊ZO(QTX?szƚ#OV^O NFL3E-#jl2[yC_U3Fu>I/|Fbm~; /vثKJJ eM}ob7Vu_y+d`7%0.Tk|.hB/*W)!N^~{eb4:!Ke'DOټ<@~K7?"'On7Yx?>bhmnBa-fF=wbqd=Gou\: `o=KpzzvASY0=[RNPz.zh[jrH6IySE/YٚN;Wj 'wMZ0}ʲ% =5o USBy_y4bkt\Esh#2_F,ddGFx\#Qnۨ0Tzx,r0S^̯<]z>i]vFX.o8Z]]('C]{v6Ž.#~߹le&G{ ##>G\x"ֲ;nȚ۾8$u{ξBNyXO2z8x_{vV'(^{:nK~cgdRNB]Iku'_O)WVe%Y`Krb#K9L]gn޷=7|az(Q)8 %EՎe6'''MY}9ݼͩ :VuWlb6Ŀ]Lezóųu2bi~֞s&*(-wbkpAi!ZNrR R}VIuq0V^x~Ո#/aNŖNt='2O~bM`tD;y:(0s7dVdr7L)acNecTK^:RILE>ɨd4#K7LA<ȕʢT7@( [h9kq Y-})fZ,w=>ސzx|I}zzx ^>eƂSת_{mڼ:hNPRH:ݓK nhmkW(qƊПxh†[r ߺ$/a鬺 Ty&r龂ʶ>2iu=Q3¶r۷U{yJ񭯻 Q"(̓!%[V>EZX:.j֜?ze↷՝upƫbV>bh*8X}uD9AMM`WYn 7_~/\9X٢>"7umYe{s;j108;IۨggfTq髮TMloA=sC{Q.|ʔJўOG<5鰭)kJb,;dAj z]kCÒu5յzsasX_1Uh9Bk"lLDk] y0e1UihJnҸsj8|FSx/q1+&=9j Dl8W3^M b IDAT@u=?U~!B(tI\a OmDzV\g.Z?B->W0eGrOY[&v5Lc)!J3W͸6B~##<~>1'r= 1wej$1BE6ϩ#(JceGkXr{?,՞Mݼ9 |0BFumиsut̞m Q7aÁR~OM'x8@l<NuQ3̓4p9ꑥ&f3V~~<:ci7N4;^tbš!-^uI=+~5=yxXoH=<ޤF=K=E%)2NP$S.78dcW BkwOT *֜jFv,p_bSGO4G l] 7.uHqrސzx\I~zzxmywV]pjT2g@Դ{ }+kgzz@4|h-G,ŧ9W5ٳ%ǟ-6|[ W,%F]H@ҔCLt##*[! IcuR5>rm߂|O! b5۾V棇ZEB(~mz~qQR'LH{GπFScP}~ w(}A;1Qj\-h4_h;5ٽcwzhg=1l s!_=2IpsPl]]#?xx((aFq!p-ROi.QRU&7DBe>~:[j4IdDpq 339B6=zֶT(N D{$XI],r!ֺKf[KfvTjOeӫ_yFF=t_ շS8_eʦ ,w\bM1DS^IG ʇ 3|4\1o4[^6tn.ffm.pHJ !Z;\RՕ䪦D={4{FͳyJan2&2B9>kΎ2ڰXS^e#=R9rʬU fkj~OWn؜X> F`5ԶSw{іM:\y/rZDGFxL zvнVM0twè'0ܣ U6ʝOH5.!߿ߘw1/W#d0pggBB(kѩڞ[9 0zb,Fu~=clH wsĎh)9kCaI$j EV(-갸iLwZ\v[7qֶzP*rO] _眵c=xy}κfeT?6R_1G5,2RJSlbgF۾=}KʔW{'AnH;eYwwr\ەV&s*`-/H;X}">НRBZ/WPGut%rȕ&=#5 ɆEN"Spj%'Io(7uĶ"U% %Njtoņ+힇 rQrX ͕Y}>W8_O&;/+fk-UϚ残 0].}a>ʑ%fWk2ZV8P~=;}6 Ź''ZfYH?hȝٷ*RQ)ƚ=sq R;`4,6Ya̺ KEY,(a6ƅ#gJ:i <. 0 `t@iC n6t@iC n6t@iC n6t@iC n6a'TSfO vS˨\[sZem64A?3&Uی͵e7s/]-oՑv8>rxli_?\'hGCtU4NdI>?_.YmmLL>W;D26tSe؊gYx~Ey7g~S@}UF\xN7c6\>pi{;^OCgK cLn 8,7v<:g'ĎanPF,]0XMMygOfTv1GдKD,x0zj{U*WA N.iȴsM*b_FaRMt@ݗ ՝Go,u[?,krifʅg><ܞ,*= }b2jeSEε9k{<̬7cR_xXg4Uo6{/v\ƬWo*X:M@b?t)˄7K즧h}G/;jGE}bMo(v":vCTXuvKrϰ'x⑲_Xۮ%fm'wÖl}|,/sC}VkhdP^<}CEy׿*Vq?g60BUaqlX;W7'ukI_N4q<\:R>~gY`!cP:2¨qzz@rpninSc\(*"rYbe7s"ID5ѳՔK;zhm+G+1292' չ=XWaJł5ng㢼tw(bEQTTD,n61DMm:t,o;wfz=NOw{$Mʦ^EPYD/"Z)eq}voT;%B*oƜ`I&;zչ$0igEl?/f=-_,;KX#/b?ܤb#s:Bdn!9Ola.F_z y┛*5l2Ka"_Q`",!c??}yQJ!?"ѧ*\䖗SGS2F2<|y]r|7K=DV') +Fqc>55 =i)lϴ=ʴp 0 ˫3rw/̋ӌganҤl~*è/ZlU26lH J+tzpAT!F=C$g`VRrY#{(?%4 9kX1فBSW]UN6xe(q]=Y>Ir3g3%Z?5>(EBFȗ KMWw aXS%3} i4S9t1ܚ6-cɻJ[.ORWEܾ/,MEoM>ȋ&^Z;'.'. mۛ.Ba2#}}r$ߟ3|(󷗬7-3h#" uOu)įz埿9]o*[e.ޥ{&NW^cʼnf!lg''_ydL}GvqKDCPGDm,ݟ37W<Jbk^*\kϽɝ1s I{''o,<36xLM <{}.U6gFenyn?G{}a,UeH!pDIՀ(bg< HIPףqS^/t*l'|L#K%q^)9l><3vIcH ̓Dsy߶öOʼnW~RFnLQp̔ WU\7U!^JfmLՍIxaCwM:׷__ 7cR~@zu2+Isd1|@O;g|3`MEW 0 j2-aC7nOR{]сSٍ 3Sl00g7gtE)YɼHG$lXkc>5]rrEd}=<l.THV}hnfP{}Gl>\>9Ntj61)uF egpW?/ŞSm\enl^ IDATz؛.j^Cr$BgyBƮSܷ \r%q|zg^qV7s.u{]:|rNk,UFʵA4Ju$V.B>Vyp%L]O'+rt ۈg /Ͻlk{R<⦆;S4dϴ-gUziє~ͧͳ?__4zgN_W-v^yW ٿ?'Tj*ypy\= }6 Y/J͕e" V}Q0ïrz#KL*g\lA]N_o_ ?EKK S;$ݭ"SY\1hvu:m{֯ߡ>\{ LZ#Uou,sM?'ؐh}Έ=ݦbt;5[ߓ֚nQ]tYM'<{>(7+q|Pܸ@,v\8 &o{%F%՟}>?EZ! JپƥϽ콖D]`Y]Ԋg ?]kwB虜`,6HZ)KT)ϻ+ۺ,Kot22{ :vYsޘy2\WztA6i KwN_Jz9?NOߙ|[GF؛zkS7BoiKe`c7=zq@-SYq@ i}*'1sw&Hh෶$~}"+G3(_\t_i!8;NxhKNW{׺t㷎n"<@tRTlw9C0!HT[7/ٚ}\։E?⑗8MX/~.+qrb>!LN0ߠ=#uXiö Zfʩ =C)2Y_5&FcZsǺh 5]:RT/ɏ5DZ WR#(#4{v8vm-KTѡ nԮ1_4|<}m@O_?g !HB%ۃVi:}&ZE!R4?5OF^D}uqUwBT[JsDڐOܿ1m|m[ ?o_0S[꯻s7>N]  sDbBu>?2Ϋ+v&+u Pݳ 瓂d07_&'oUOgóM>^sEpcpAQhCSBӓ>2Q3³V}NK1Yw+ʇ,{sAW{],(ZbWo=f&o.H7gNc\tʖ֢u{d IoedL"9# { g;qUL*B7 yWڿ6(8˓,UJ% Qh` W1bu-.]Ri#/8."=U! Q'^gH"YEXCkePYa]o߹:;U2?[eYl;uvi ^ZZ"&bE[ק[xuwdj,>hyV=ùaB@%vWV|>i uϱY`xó_& W_sյX!bɊyYZSO'6}Fxt`gP"uvN\cj/2/>9ˎ*en9F3&( Ήѧnْ'NsiIf'&/ {Kr\3h@ D|%Fي|zϋYACo*~ / gOF0m?YO 3Df$% V1Dp4e !Ha  m֚]m 4lgY=>E@O:ƮbL8T]yV;wxY/VQ<ֶecDݽ/SM F:]L:2zJ''l*XIŘGxSZ‘.ݰgcNԘ[댌Qtggݰhm ko< 6[fia_]hN+T7HLz d HH֡vo% Da٩0/~FN94Jsv7,64֥*SVJG^Ê)JFr57~^[g8Vn}&'o,6s _1=z].7 ~ ⢢/ IUtFB28Tgml N}spw 4W!0!nJ7N=N NO Lz{Ow( d\Iuh;WBG!ҀOL >v{BbҞ~)[*]v[{@8"aEvFHEoFV_|Sb9H~7-Y@iQew+kJusc.}Ȇ% x㯯DBDe$eLRf7BʀEh>s5w8N&0Jl+f׸+_+QNЬX&WvquF0V,[<1wX\\@d=_z.#'}eG>xC?K\)-Xj_y0Ha8upʧ%ZFh={HQCu-ˌڍ&;<ԅ.&9b M6wFk0iAYy5AkZl5Z1b؉} 噹tNgEe?.5:.. 9DUlL R>e7h Їʲ {#IΨ:h;[5_xuD!)/Oj|Ki\lmǎ\jmjͱJCF/⦈ݻ':G|d { ~"OO豾%#sS>5W SS ٪No9Unq{iJ~(fn?{tK#/0J_d)¢Ps_?VjW) \bo0|_mYQ}֤{wĎS?G¥;TdK{W b5'~EG׮ٰW_ۓv5ώ59׾5;_m=0~iuxpTI 9;o}.nΏ '?Trox NT<G/|qm8?*YzBHMN0ߠ=,;}t>g.$cJb_]GEU#6ݠ1 @!q~]uZFŹ͵e玗ThdG(l3a K2#LV{m q6To*JTp5dhqę1q!wnEǫc~䥑zCtuHld {@]6`XөYˈNvJ1{$!+GϢ kmսk9G#Z~6f%c׹*^y! {m\Z1Tw^=yq%AZr-bP~e߾lZE8lΆ{֢[~sbU-ԄhI`5åf˶בn2]]5yNQ+9n)=]nl ow{̄A9pWgٞn"46Ԇ@Fų#V7`A{XwLd}=,[PVQ1r/" êkO^ݠ1 @3G;;e y黕 0pz ݰ tJ7 +@7tݰ tJ7 +@7tݰ tJ7 +@7tݰ t}}=~ _7qU/?vI0M!>#kEj\dHl}m߼`uۑNT~iLs2 eB!B}Re3a9g繂> e{I\yv_WRԦݻ"TOKvdW##,;,>Gۓet3aY<g?aYaa`O7%~U>̭nL tXzD_ܭeE¹ZN!*%;/wYXDwO1ًFgL{XeBfFG>=~<(6FkopN{}z֔@^]6$I.A@زM1##,;,5edP LΌIm7N\y8(Il٦3a<과[@eÃ>S ,n_S.\QT[q~t?]؀?Xi>?Bd}=,}k>;s O,sy 9!<~?Lm\`3/lK;ˏ}xOQ!DP28Bƴv維B!!Rxگ_msΘ>z*MO\F'<aL X,.̠Qp ð: ,>#w¼8߅>ý}{=pbϳ̄O 㼟3axg '3axLd}= @!M2 ™HXe)cc MG.mI5^:y>AKΔ).M!½coݟdI^ \ӝƊv7O]YRntXvAV:Bvdiqdg%<#&dUFqvU=}jYU@XªM32'w|瓛gOGFxVckNܦW|g~S~yQL}6xtnaKc>dY.B48QR5 1ʸXՓ,hc|n?j?a8~x>qTwWU|N5F0A91JhR|sʣa3CCXFݻPRk$" vk[gΞ5Y{$#\.QOr!'Y{Uԗ}=J1jcBZZB1@`fTNod ^W;.˄N7.[3ysBd_~|L0^,.dW[C#e*%yWpE}[b]JAa!AaѢ"&"H&3x+ݾ޹kCB3Wv>oK>-kՇe}?kH{4ť;/%VI˧ΐw{d aSִe" h~PsVeF=aos }td }=^sEpcpAQhCSBӓ>2zG@^y>ýǗGi*'3Fd}= @t b`gW#JDͪU Bz"ZbWo=f&o.H7gNc\tʖ֢u{d \ujVeXbc^V֔>>ý}{Gox6d}^6xt`gP"uvN{12~gMgs6L{x^d}= @.Jx1tU -*}*93F1oKc KW$IhmH]k[2w Sj^t&# qaoNNSr=0'>$HO}F}>-?s_& ?lϋ* @!.**Jf: PEg$)CuƦ.@t7w!@cyem8-Z74!q+ēs%=c]wㅎcw%Bs2X{&aapQOK nCX>,qMƴ ?q^3}%hM+FKZ:\ qDZ$>zg>3ZSFIKʪ3gϴ噹tNgLd}= @&++n>|X=cT$ ~;T⟮|n|C-sAޚȍ[%ICEG/5yqmڽcuj};riylЪ6*Ŏ]2}^wOu4=qYCGr IDAT{cw~B?I+w}>#6{ w.=~g>9JM%o.a4}=,;}t>g:'3a`O%Nj5*o-;wG$3=B1v !Duޯv*ZTcb~XYz乪yn\ JR)q Y;kn9qs..d}xuM7wF a!ɳ2i^!1?vCg(q٦65*yqQ}Ϟ馪3a3m n=?9FiP{n?nVnXa膕nVnXa膕nVnXa膕nVnXa|lmQ ; L7gdH Pswyay>ýÞbCs J>\v/gx|XVgܘn,BO* mGiv厏>#,3,<##,; @&++n0C_QԦݻ"TOKvd/_< >,>/ݷ'ȍ[YgòxgYgeeAvde(EEE bVض"=}Jwg Ă,GG|Թ˷|hL!0:NYU#5cFFXvx~p/,֔a냲j{D^ayIyVr#=sw;Uʏ>Ǔ>ˀ>#>0upN71*2Xw]Q<}ʪ-#9ubDfi}șzhSV虙vde /*"߽ks8D/G&ćGi˄>ϳ}= @!k%˓#C4 [:j+nv&0#IEk/uBXcD(Hƚqjk$(B#~Bu9YKmc^DB׾w}$'v~xh+ Jcr$SXNͳ`1?{Fy9ŷ~p/FmR)Pl;Ρ' >ý21,B4A:ˏ>i>?Bd}=,}k>tO,sy 9!<~?0KayXpڏQ! !14$B4>mm" 9a ţ~jpƬplȪ55v>ϴ=i)lϴ=3}m0}ۇ4)0* gV7["aA 674=aY85;u.yǫ;S½coݟ`MBxN4VLFyʒrò j?)_ ζ'Kۧ8-lƑVUyf]dUa 6mΌܚݒ {MgGFxVJc\RZ|O" 'N5F=Y O5;qvLW^+MG}&2Foxz?& {}a,UeH!pDIՀ(bU% }Kn =pO"Nc2ńWCM}Vf;dYe(JΩ?{2ޅ^ I[۪->s6gy##}x&zt<%J>5fFF𬢾3Ed}=<(lϴ(=ݾ# o ^KHVa}wFtK5=XR5<}~6 +>ƨ ii eWR9%Dbƽnٔ!J*W/SˏTË=sBHYJ\QeXzmɻ>B>âEmEdLEbLg V}Is׆X;[G)yb{xJTe$H3]P\sRqjh|N yGF؛Ϝ1mM \!rΡ5oU67{e##Mؗ3Fd}=zlY>{,?L{xVQ?Fi6'3a:`gKu?R?7%/0ؙ#07T*H} _ajxAtH(5?49FIp:'|ѧnْ'NsiIf'&/ {K[[k.~vY$"tn% |X=cT$ ~;T⟮|n|C-O@,y+&nU HؑKMcV޷9V)v~pܽ{zDGFث'vHb_]GEU35}4e#6{ w.MQn ٿ?'Tj*yptG3a3m n=?9FiP{}Hl_-itzVQq~smٹ%=% f)WoCv;RTJG\C֎ƚgNsz`NQt:f%K#0g}M{4~UT Ip V];}R}sinEhm zgG lk,oa}4elO7UGiN)lϴep6L{2 t3x+@7tݰ tJ7 +@7tݰ tJ7 +@7tݰ tJ7 +@7tݰ tJ7Ѝ7qRLEu7EyO Y+R"CԜ`n{xk^GFXvx.^찧МsBKfg/GFXvXVgܘn,3\[>OV~n3AiЄʲeym LVܧ6ޕp%?]lٛ>ًݷ'ȍ+~S~,vư,܎0e " T|f񉯊.?\F!ļmY*訸pVSaJ]-u2z42²s}=2@iˣLሳڧoGע7}=,<##> 0upN75!.s2䛭|ѧJ2̑S,6AlۧI6eUoՑK̑FgL{X&F`~NS1O9?}=,<##> 0K22'Ghet4V*9M;ٙ1"c$gcMع4P[Ӹ%!Eado …+,̉^js:Ž#9C#_ddeP3Wg$‚tj@7,yІq/ŶaU}>Ű,K!4Gi4ck>>5FiP{}'}UIQZr|Î}M R*òqɏ?Q( !!1mkc5ahAT(EWۜ3fgs`CV寉ǃ"!iSŅ4 aWg_Ey!}Wya}=OLd}=lϴ=>ITQ!tW_8sb 2el,ؐVh40I"Jcj4SlΦ{eWovL};Knp^D.-;Y+K^ʍ.ȪZ@2]8ۮ,m23gĄܴʨ ήʳ7[" KXisftX֜4>k>~gon 05/5JΘ/}FϬqމ۴cjޘ_oʏ>/<36x1Fo; c:]tG,kEB'J$FfY! 0c968:uv^q܄ n_n>|MA%LPF~N8ZS!,#](h |3glsya }bdDjr҄O ~̛g/FF𬢾3Ed}=<(lϴ(=ݾ# o ^KHVa}I!qICe.E@drv^0Uڭ.w1l_hΆ{O]Nط96r햕滱M*boT| ?IDB8ǿ& 4W5Oϰ+-5}>gGarO&3Lgl߰cژm (,!3uU3RS9"#MS"vs9)SˏTË=W[C#e*%yWpE}[b&ϰϰh[~yYmyX$mn_RFfܵ!VNVsIBUKi9 $q=DRӗ^ΏS$GwzgȻ=2ބ0P})hkZP ˆvt4?}igGF؛,Cao¾Gi6'3Fd}= @ t´aۆX-3p!a UMQn%QJɐ!ekk0vյ 2MmRBД[{02^ V"eir|t18Р(!)!Qt1c~p/V}NK1Yw+~o>~0܄6ysA8[vS׷Lw##UMkU*CĒ󲢵)՟VW^^M2Fox.z>/0ؙ#07T*H} _ajTNTJGԄ;màQj4hrtNOݲ%UOҒNM_0ãh\jHExq{g##>O }?2gKgs6L{x^d}= @.JxU q ^ٵT%v\=xJǘuIKU -fIZbSLURuFKL5)I4wC2K7'\zfC)\`Y'VI{ _1=z].k[h2r񧱇eDQ$q'>>-?s_& ?lϋ* @!.**Jf> fPEg$)CuƦ.@t7w!@cyem8-ZKt0;0$nI 䉽[fǴ78VgpzJ6d{{DiN}&/-'5J{Ffl߹::J|bzt`pt`v$kde=Ee?.5:.. 9DUlL Ru >Qִ2kcŰqHG}F35edzRS~<36L{Xwilϴdee će޳1FH`3*N:Z.zVj9t"W|^rn|FiYlmǎ\jmjͱJC̮GWDݓedMg~\#z>2^=d؝0Hb_]GEUcyŵ[,s#>t ٿ?'Tj*yp̵3a3m n=?9FiP{}Hl_-itzVQq~smٹ%=%L!nCPU*C֎ƻ7ϝ8]fv|P}g57Μ8S9/.d}xuM7wF awsd {@jWqC9Hc铗ǿ }Fϣāfרxv ¶_g>{ϴepϔNg2Ld}= @=q1f`膕nVnXa膕nVnXa膕nVnXa膕nVnX aoPVf4xC|F֊ԸȐ5'-yhde{ ٿ?'Tj*ypyηϞΔ1'#>ܠY!"ԟ'U Aʏ>>ˀ>#MXyGFXv&LVVNW1}8G/:}}6ޕbZ&<|@^a<^C^aY}{[n}=,}0IώWXS'e8:*~]UdCc qΪ{42²}{ea XU3gy|XeR޶GG\z4q-ϴBFg L{:8:.57'VEDkř+M3;?{wy^h6ͮIhC ڐؒ3Q$yLIyGrs2$|&}߱EMl- $f_EoU{ 0[7[_z$?|]|*%:o[^bzZ+Ϟ> 3~VDmˋV.ʄC/9lݯ|x9a~!RXat7i/B+dϲCD?/ee󢠟eNJ*ZԄU<=ުi~ڷ?'R1G.T= ΨΔDM1m &D07γ&8ۣBh{N,Jьk›Zڛ=3T&z86g9jޱ7j[$de.0`W~F׷MVs /Y!UUœiO?ќ\6,{8DgmY0H=+I^;P-'mM }NiJS)Z1G/|ϲZmq]p~'p!qj}ջ] YX3Y{QBMV+"=e^q". 2s2 >chG&5e/%)1V}|Aݍkm#a"E<4MfBVSŮ]GzlAIZ=Rvw8ʄ%19AULOww?CupW#}I6 X0|ϡV&\L==R!OϡV&𢒾gËKa~=$g VW{'C#">RM޺3ۮM;]/ T[t⺂볾=pdw)]@٧e>ݘe[}aP!bL9qcj~ƫ~F3z|+O2f{Ԏq-yY槟1 9Lm 6 ot0%™qcv3aU;ZST&jܺ9AULC/?{APC%;]Q?.w=)tɺEǓlRYBo9zؗe+i nkWШ'0N?UB?cՒUd *Jm t$[oˈRФŲ6/;R1_|q o=NlY5˰m043œНwJ2#즯r9Lx!yty_槅=nߺ>+^Hxq Lx!O?^lr&M?Iҽr}ˈT6q"WR0wo?> w=-c)ξB~gJM!k|zvɬ *^@x?踛)w]z3~Ml'g_\]~^ʄ^LT& 槟e/*mYb{ئeX^1ZƼXU[.}uu{a#ѱo>{LmKt~m??}M|z“/Uphi;gCzff:20823L~בFUS3ښ8"E㞱jibϿ3,M?+W~PS͕gágáYa~= Q p |$E#c;iz<[Ըm'Nʰ=}X~qpkځSo:U_|Z3`_A (ɎQ4FZ>.=~û_=ɖ9x?};H?rXtLQ^'])~=6,{8:?9lϲ@2^AƓ~kQ_WڞbD!|뛺(V{Dݪp*tOУna۞sO[Ísw{'\KKqxQuuGnJMO6tU&pÆYaEolZ7US>G?a/c;<kvž{m~g)9=R5?,{8gImYpu~sئed :w0J7@ntJ7@ntJ7@ntJ7@ntJ7@ntJ7@ntJ7@ntJ7@ntfY(q?烩1at}o wLe]WPeCfjBL{F6޼h8 ^|+9 vjkuɱ(B!s;%XtLQ^'ӝdYp(sø7d ~^ʄCLB7蟝Xfa6_7 SM]j,Se!ʄC% c^nϲCD?ύ~ސ1yi+9 aOT wФD}'?/^k Ssכ:ش6'|ZB1Te!Reaπ l 4oOh9? ϋ~= :r123`Zt4X%:o[^bzZ+Ϟ> 3~VDmˋV.ʄC/99ʄ÷Dǭ*n>X`C}S{XecbzZ4M╤:S55ܟ4GcgYSQ]!='OhFo_R5UjvjofTgSp؜w丒",{oߨm~? @( !ip}vŒI \%_N{9?ќ\6,{8DgmY0H=+I^;P-'mM }N oT4NI؍[9tA^lj)Bu!õ;!}B(6yT^v/4gadE 5a[ɮT]{9QAkkFA~j:]ܖYC6/}.wx%yבNU(q%EkmyfLOe ͒*黊)L;Ume. <ʄ0 t  sCO'sϲ6,{xI>lϲ@^9i "Zr(;ƩȜ-yQ{xqx.i77KV=9݇3^Uv넅?S !4b9qcοN?R0z|+O2f{.Zd dئ1`1lJ S"7f;cVUQE;U!LeUM[sNr0k_:]nm$[6&ۺ-kTS{[7=-c)ξB~gJM!k|zvx;S` AݼMғkbȄo?9;*^@9gËJa~=g VW1/VK_]i #dJtޖb4 .뛯k5OD:+:3v6v1yW&<F:u=BǦdm_\*ڛW:~2,E=s~^.6zKamy@, {L]How3JDDĬW *x˝汥<ą}6cM aS?;Do8|xC+J˯"2dSAU&rx pgåvB&:=~J9?^*re6,{Þz SXZDoT!f`esCwf,m{ k cU .bI/RTr(7J7{}@ܓEl{UphɯOŔ5'5泛?p79\ݯ|@pL0xq"9? a~^U&6 !---Mgy鳧d<BJ?ơǖxX[NԿshAyr{x87KwCOG,bbESq|GEqsX积OOxrcTphi;gCzff:20823L~בFUSJV&< HWA+R ?wϹޡG:-Hg,iRVdYphg9mYphs~qئe|Bw',,I;O]1M;:UQvgcHwK)1[.-V?ޞreyıLU٪m;qP*wִ.t f2%;Q~oi}\Z74u{teHM,:s(lxڹϹ';2/sk< N期e`>,M?ra {W񤳹tDEGEF:Z`}"Xf&ޖK3#G^=nY5ikQyni ;Q7J0MѱIֆq3ʄ9`ذ>+S ]+ugUܞnauDĮv-O{:նM^, 9g!O?K:lϲC06,{$Þnܦ=3` 7Vrc 7Vrc 7Vrc 7Vrc 7Vrc 7Vrc 7Vrc 7VrXl 9[с滷npˆԄMw=lyp`Y+9 (EcUQBy7Ԓa2l3?(^o?; $j,iq]s8lo>+k6V:p2ag=TpadI;pB6yYJw ϲ@2ZZZ+}V%7qwU>//_q5ca;bg9ޅ~˷ۇ-kbפgFֵ "ʄC'[N[@gُzzc4C.6a씘M.bz}(mf]rb9j,7%:o[^bzZ+Ϟ> 3~VDmˋ혃L80@RXat7i/aY0Hnßzo8s}urLpD(BC==?6_<VTgJcw֘c%8P=ޯ?K|3-q_~gL80@RTUBs r ϲ@B,SnLuX5EQTKDLҺo9Y阐4Pb'hENjSBk?y}wCPlE^v/4gadE 5a[ɮT] PaD,g,USe|,sG`nJփۜVwZ{kmmOO~+ž}v:7?SyDB-6xjo,ǍPcfM_y8wٸNӵ;mwơ{HOyGzu!%{Ҭu~7ge‹XYsש3E-ŻxGWoJ7EaILNP]OP;UmH_nMx!YY_%{oMIOݿ|?ON IDATt4m~}.g*v%:c Jڄ񕲛3x> /< ɇmg V, S !4b|Q$s^M4yOǣk"s3sP0w)]@٧e>ݘe[}3X%+?Eg%R;:(63 `Q=eJ~78884 L?Hq}t}n'7;شƒ lʮ;8k ֞udڢIyxv @pJmg `qWVnKݼ/uB|A[G' Jthݹη !v#m -LӜ ͡;U9dFM_;sW&0le$Թ#0Pjv ?WX;#k.b5~0>MCbM[׊tt:ZpلBƧ82d#,{n(0Qw=6%kHW޼?5"̱1Cĩ1ъ^;5OD:+:3v6p*^P@F+=l3?0ȁ=lC(TM 0P"#%y- L!l!Ps8lB>CV7>!Z~S\{hL80+%mg tX ZkTsrcgEB 3f#.wфݵ97N擮eLP7ܽF~յa}TS'opYSuM:d%fSɡ(pWsO%g<Upayɰ !---M$ 陙ȸ0U[t]Go UM5XDΦȈT{c?ڑdzύmO(a|~^Rdz?*gc +'^YSI˲QSscY&>=nRe!“*(pE5nmg aPpDIvۘHKǥuCX?9bo|\(f`e8_928#>˳U/Ϥjܶe،>r?nM;pBkOk&줙we !5̙DOjg$6a {,saG6S ]+utft8owW9a"0ky E`|Fʻg%8FAf(~x 7FRcӓ fp /0 泧[aY0H=sGctJ7@ntJ7@ntJ7@ntJ7@ntJ7@ntJ7@ntJ7@ntJ7@n>zmǶYiw>l}J, lLMtHڛwᘃL80@8R\KuXTEBuPKɰ,{dz!(QY2q]Z̕9YExpcv_KeL80@X8vS<,`;\mg ---M> {}+ * )k֤d%77G:{/ߪo$M][2*9 l9ůoIg?=>3%petX촵EowQyS'`F315zSip$u+%G(򏾾  w?hKڼ>k-ue!”&g(ƹ+ !-sͰ,{$),3wο_ǿsqI)%nmz"{5m7:jBN_7t+Mu$jok?Þ6h)'T-y_~OR' AK}_`*= UUœke[aY0HsEbw !ݯ!>>BY_w_/dgdWEzˮuBLxa0h3Ly)m ϲ@>#07%amNݻP~=5T{LR'm?ZmYk%&&Fo%:&ZzʁZwi3/_<;l\Z}OyJ'ݵms"Lo{X0ofy:k6nV$:ݩ %hM:LxaH>l3?I0uBS,ocj:&g55ݛ]#~klZ T|Ա w֛K%+o Ԟ}PF[ݍ{]vm*MV9jbљ3Ef{ԎNፕn"//˞W@֮QO`QuE#9)[lQ!擁ASWr3k_:]nm$[(B|񷈬I> w53 룚'|3jU1^wߤ1J̦CQ".*,J>xdsw*9 0WU$zx ?س!=33YjNZqV}xs-3]\orE{-6=7YXŊOYSI˲QSscY&>=nRe!“*(pE5nmg aPpDIvHKǥuC`M?x yzpd!0eWBsL^PJ@Ouٵ@ / Fb}bQ)=M}]aY02w$l=i{ʯ5jIvGͿősui[`1-ػzӗ=s{HOyGzu!%{Ҭu~ $r`e]%NzOz)cf~^a{, Kbr*>0upW_U^x+o)`i ^?n>*]G;U _畲38H80d$e$ `Qn)7̱{5+tPӛK%+o Ԟ}PF[ݍ{]vm*7ӜUo,f aO71V_Y;1.uBa񁞎 ok]aS1u_.a(-n).3AU&r6a{,-I9to3!D3ܽF~յa}TS'opYSuva(1JFfO{(96CL80df~^Uaq {6gf&##>TmIwٿ1*oT5OQcX}x+;?/l~T1".V,I,Pq|GEqsX积OOxrcTpaD \bAM7}FaY0G),,z+}%;QNp0K놞oPJ\]>{qjrd 5nۉӇ2lFOGV8vS_˧5F /( Ģ3g?rϲ@28Æa۬"L76~Xw Sl)۳bg9rũ)0kyVZƇ{nTۻah ;Q7JQwD$kC\e tf~= aO7@n!ޞ0J7@ntJ7@ntJ7@ntJ7@ntJ7@ntJ7@ntJ7@ntJ7@ntfYYrT.m)D, lLMtHڛw 2Hrm_T.9aQE!zyP5mg ]N͙_Cl0 }y:Rp8fa]u_KeL80@X8vS<,l;,mg ---Mח_$U,[oΌ0upW_U^x+꼱hi3}SUv+,ɻlwB+()Zk+e7{g<|*^x@2ϲ@a}(#uƌ.Ww{^yg5̙DOjGVwx ƞn"Ty%?r2R1U7=G?!<.+}X* žncwtc\}4=nߺ*4{mLӜ~͋НwJ2#즯r9Lx!a=l3?I"1>>WUoȄ’ܹF:Ζ1XGΔ8B("V&0le `F:u=BǦdm_\*ڛWBwWk埈=t$?V?uf.i,mU&0Vzf~^a{,;p髫PɩN7~D IDATpL5&'@Ǿ("axo{ kOD*t%fSɡ(pWsO%gQ?Fޞb575^La|~^Rdz?*gc +^>eMw$Y/GM!1we}~'=fH OJ)Ի}gde|B׻҇@zZB%1 i)n%z[5Z:o{%ed [ݞ + + + + + +Ͱҭh@H4]ק<&;%^ >~<5WW}?ܳqkE5=ޝq]gN5 v qEmVRcYɌqM̛NIcQb[MJvJH/  $}:n@7æB[νu- ?ҭ/\HN1kqa%&%@""""""""""Q ? 7/9r6v朌isf;QuLhilPڞdvZJNJcWј6p:k=p_cDHDDDDDDDDDDD9 j(V#hQv#"""""""""""‹nhS*DDDDDDDDDDDȵ6ެmWH|RY펎؜Ntj$D C U\wG(q#"TMS U^t t7"""""""""""閘0ީÑn""""""""""Z虳fжʊZYH-u|hٓ]Ȋ1GikOAw=9MDDDDDDDDDDSMhU_͹aͽ5\FY Wݰ"?Ύ[GΪFFDDDDDDDDDDGe/Y6hw]ǯ٥ p`DDDDDDDDDDD^qWkͅ3>TRչhEuvvaDDDDDDDDDD40toh\8MDDDDDDDDDD#DDDDDDDDDD8MDDDDDDDDDD#DDDDDDDDDD8MDDDDDDDDDD#DDDDDDDDDD8MDDDDDDDDDD#DDDDDDDDDD8MGl65h\-:{[^|qXENv8D~a$m7:Yωg~0';( 6[|[_UQ\V=Dۯ_p͑>=5cgKnwMvTDDDDDDDD鞒$qY|/٭;rlK]_pk?vg +ׯ^Tns5ެpbJ&d@ӞXdE'_;=B7˖?yS?޿h ,^#:GWΈĽPtϹ#RlL{Z`1?G1IG1٫fXi;kMxΟ;Y7lu=5hq|$+<w~˓uID2zgJ̜`%>?(H8Ӗ>g SMQ_hi?}r4=*Dm+?vFT{kA0ls/{[ ز금 $H7ƞf}=U9xkI WCCS]yr _y/_;X=mŗdV|!VNIq5ӹkVY?w]6aXu󞘑~m.MWp?K{G nLS={QQvD/|pAhkeZ]ݭ_,nU^h'*KDIr?u4Fّ0W>x^ZξsÈIJtk'յvꞺqR̮#ʻՈ/Zo &p#4Lq5'{N]kmH[^:6.;~p)z_ztN4.48 MຕEI1a6W{cSwqV8l=qκGZ:+=>L:.[:dMbVO7}ň-l$mdygWs?q^]~pUܬ(YuiAcZѮgke[.pLϘfot=2Pئmwȴ\7Q}dۻjW6F[ڸ_+>uaFnm6C5͑&[9ݞ2૦mw=KS;;:-kvGt]/ܰiҙcfkՒ#nьy4^2 ssRק7DDDDDDDDfWu% M˿_7m6ΖEΆ[굛&w7’ ~yˢآ/} [32aO~쬨!S~-b~|binrfrll ),η6-I6gdr'oNnh ڛt!@GKk _gF {@ !ihO[pgo;Łv}l*GKo 8a3@B?r>Wg%$bocew,aBfmBb9|WbߌEݸzNG^eTy+lWż}Q֪yc':[.{e)U{g3Uk^z/dg/vsAo.r&x5tVc6RW=b\?8X^i>ws[ʍ,x?<0xth%|C.RvP0[Ǝٿd􇶮;;Fꭏd9UVr)d_eSa8Z}hsi;xbr3/=^WzywWo;ӴWM};Voaa]}_Ӄ`nmVF59Iԕ#,^|pF̼7/)Y<@1ѡ96+uM_V{٧gkǚN@;*Ԙb_lS EFGxF$a0і3_y庶nWw[o3*sW.l-޸y'YWO^b${ŘHu*r?vYő(~ouC!@l!#f/>G[צ`$Xn(:/ /{ﭣwl1sf;;;Kޞnuuw2@{:/}WɏwΑ1ꘇhKھf{_*᳊[y0U"K&5?>;}G7>; yD"m-UCM=6 i)hWs֏n*>U5ȑfe}ϖi0NsuS|gbֺs֒̚] :Rs r"n~s36nY}'^/Kإ=5;{눧6JgݷNx7vp{:#G9}ח?޹ >sgTQhT?b&"""""""jz??ޫ=ocwhi"""""""""6?5E" hG?#,&῍'- þF·ˍJ=ޏk̴-:IxjFđ3#r]S3ו9Nɽ\z!"""""""I3H9cᚍⓏ??yu@m7Gn/ Ѓcmꯡa2!aƬm?FyߦDyœm9w-P!sC~ ~}Ͽ>ݧO^=ȗN-5;;'3FEg-Yëf%:ȇhm7]݊GXoJB ۮv 3R@HhdD0$"2B]( NǠ%tО7Jxd =$zW;԰h)30vuvp"fs[JWŧ^=7?ަn]z'GF"1aSw#DDDDDDDDlncS f$MSCƐp骿Zy{iIXsI!=o*m?榉\Gނ9Oڞ>oNnUdΊɉ7*n'0jo3w\ڷnDY7$MS#2fD}qhE{t/}ݍ$z|h7Yn}(]nFgOϊ~s~~W*ПKo] ]Յaoe*q}Wh}"3g0}g* gb3ٝ?ޟ?8>`6;uC%fɗPasF%.޷4~&^(1DHHE9{RJdіo<0=2G|ڗ^*tYu[&l̢(@1K{yc S'ǹ¹aK/͋wol),?vz73v:ۤb_unO_~͍k~TSU_bpj_Oo$zt_߽޽u_tPjWփ 7 =9ڟrMn##%7'Љa:{}#3}OмŻ~_#e-U`xVhlY{.rh 7OV~e9.V2v\Ϙg\;];S5QTy+[Lw*Nxov`҇wµnvU]8+/ YŲʎw:M]'ޚ͚?BXozs؉Z?|G8_Ul]qv~lK]Ma޳ӫGͻ|Z6vLrr3Ɣ}u?n6wT/ksn~ޚ)҈shѢq`e$md&*mm8Ј[o4Ɏ>?tzTE5@iiXaE~ݷosVx"""""""gJ}LSyu=!3nƋ^[]j4^kfƅDDDDDDDDSptyO[?}/OXv|dG3)܋tkwcǹZk.9g:MDDDDDDD4p&@W]ɞ%{޲p ֶ⃧D?]_'"""""""N7qUe""""""""""pG(qG(qG(qG(q A&;""QѽMd@t\g]m쮘p&MW΁?tɁmR>9ML]Pvy-iɎzK92K.s=M6 ׹s.+E`1#QH7Q`5x;>9LHD vNgta:lf!"^d~Kk0g!N|.G5MB]6FJMv4%f$" * s6rAac)LvDD49RufM|G/ȌDD4>ϓJ9 [.٬k"$ ._}Yi)bؑn+_22vW:ٲOj7^ۦ?lӍYֿo0ךA(.~h4aaL}F8 kA&iCWnW3r&lx%b~mW]k{#I!l^igj򾒮ol;n~l+բKvH>UMM~}pi~&iCg+jb)o巴`azsE.BOg-UF>YgQ9Y27ZS,#Hӗjgr*CKOj@M{U9෉_Z,W>RT9C/Ѭ!3e6M=hn]8A"mw"SYƮ@<OhͯKvP. uyr52^Ӆd`Jsf}b#Y @{|[Q;Ip 4E:S$1o oh8kr:@V}f4ݼI=(G|-<*ilDMb"!@G-})#,]7=iw+X425Pϫ[s0n :K7l3_ðL0! )k'o IDAT $wN]VP4}( tF;U3?O25\g>:6Aփ:ﺜn.Pc 躊Koc i){GtMiD͊F ҘɎ-t:= TRD' L[dGd9vs9SnU-gPH>NsO4t݈JDSjK|yhҞqc0s~.(`w؋c>g@{p䣉F Nܼ=tqFiV' \ ]Mޛ=A*M]"n߆XCvQZۆK.(uÿy*ps#&\9 ŋq3&RxS:ص0hΕ&T,&H39S$1Ot{цdk~v!.Sc둗5ّVLm/ޕӵP@X*"ȝ4es7:uo4gkvͲ9_c<_*u+=g)P*YnZ?+&h2:o>5.vAg+jX_5 3uiHhSrc5l(341:[P[%O˥aXڳ݉dknT 3%Z1K0tބ!1+:͆v]@#]8{ 6K&9{o˹wJ푏~(2ub^.cJ<$]_~Vjk2 <Z,Kzx5 ^3شJVFhAnK'vd9 )=+߭F~yLز7swhn5xdU&>yY)}nSW D$p4oM)KUԛʇrR4UG{GTt^֌H C]`ǪE Cz7_{B#j,987v)_|&n#mw4}ZLr&H)ԢB' ٍ~ 'OK g$G~8/?t_YK^2z5^юe47G9* 7QvtZ9.{,VOBY9&vGڅ2 e.a7P-ykd y?]5wWߦJ2?{]KdHF% O~CCpub귞PW=7rEA:#Eʮ]}Zt>^nxN7ՌԷmc{.cixB=gj\!yZ\/i3)ɑ_{BC,o\xgCRyclbۛC3}{[r? D#jJ(j/'{qʮld&b{eR-ܜH7~hE#=Zuyw/iO/w1)=-]!#%1MFx b4sV7>>5 ^W`f%*nKa%F;EkFO:r^%@ ̜e0B_;z{P R̛φ)8Ka׍5?ƶHLuqyd/lt H¶k̖9866@H"P]4t #u:m@7&[4ц|]swI0]F#Pԟ2]` WGĔ0|Dl[L40qsC~fs &Yc1s>yK[| S [O&/6C鉱aó-ь ,V>,Xm2,s`-L]έ*Ʈ5QB2?3z976CaD"BР0bkq0R@] ܅: AkI.KϾwg'QO7'oð `}F T7PgbI@gظ9+P7a"MFF• Djn\w@v(q /s -o!Rit (i}іIpG>,d@{>R# J*jСJҼ,Hn%=R%>uBS4L:(𘦆/IC7bkN l6d6l0>eϞy5&Д:3-SɭD_ᑺ0Yv IKui:ĩy3r9 H,Ex;'LRj\&.aԔRݛ²0(=?)]NAhnϋ>r V!xq0*p Y(4%`"6hNt+F@\kTΑ359-rKRi"tE|ri*jᲲXt.XFX0'_܄ޝn] A-&^Qગ]1fCv,{ݽESٺ:@]~A*mٚX]v>#-Z)eM/ o>8]R g †F*')HY@;sB(׫>B72_#:nED"!#I/7T`謞nc3~&aon&m3XYc);2u}&$c":cWߦT2?1ʹ$\!JJĩDFЂ|0Pg܇z_}.:o>6mc^VVKJ:>tH}h-?Kc:_jR k7DT_DFaNųZd;R쾒."^ @ψr/CɉW#D5K#J<h,--V7>уH`ޑKe{ǒ|r H{0K{h+7uz2Rf Wn8qI*4-KUt #38|+x{ L=b!\ +KUjY`;C"AT$0@cƱ,sY2$R׬X;  '>y2/[2; iGk[;ߝuЅ}(3R5yPY]N8s5@.alSzߓXDz3"^ #7q4(nU)sM8<_5ˉrbI"e;[6z|8"/#rXiKEgtKFgdW|+@l3Z8d挵TE=16/|muuO_O˛|wʹIL85L설"@(09>7g.XxwYYJ2VCgˌߘ-%j43 `-qxo̢_R! *ۤdke6K#QopK+Pt{_}u>:o֚-oU.jZn\0DHwjkSr +txTFRp .I-鄆H'2U[(@_멕=;'3 ԇƱ~S,\WA}6M3 4u#:Mg.'ë񳝞%^B5aآt& yO0@i] %dn Hu~Z++,]Np݅ A\ l5T SZ}c! =VwhIu[+KEgaW!2ju>ۛЭt"6ҁ vt!2t5г`S~kwO>7g_.XxuYYJ2K/׻ǫ֊4F$aF݁EenŊ4zr+EB57YStYYwTBy_L;1Zlaߒ{ĉwYĭKR~^{soTEsӀHd"eRm>[ 4-aظw}=7gg݈+:\ b t[@R&:pU(↔0,qw\U4jd:QމLV#.9:=sņittx[.R4pMgrlFeR $ħ+̇*e9ðU+`!>zjeQgjP 뗢Py Mhl:ܙ7H_e>\C&Jw  8y\lBI`9ܲvAx>r|$j㲿Xt#vX91󿔺]NWC-hVܽ-V޲Xt;ݣq_~}o)—jW h8Vv{φ2Ёa~^{J,ԙNMS| \SRv|_dl/u, s,å\˭%]`Nd)zS):v7nSb׍Yl")QQFw9n5S 4"* P45tƪϾg7e^-e~m4C03kD&k֛cR7 P].5ZI妉,Mh<)`_nbrYML;8*o:S;|3 .% ՍvN&+#jR.9rr<+_.XM\g[2H BH ߽Z0&?#l>"û3vV=[e1f9 W !iȜdpɻ ֞KE:;bhBHҘjcWQ` N 8FlNersUؠҞqcKH?BBxEm,DGkl^2-scCN\]G ^aÒ'^0_x^$ &9l51˪Yty-W֍0ą?Q::QBsMʍr҇zW4a%S$>htI QW+nWK9\rǣc12yo ص )OH© Ri}UCJSz<[4N)k}7U?cĞn{vY?j7oBRk aaG,}ddj> S~ !>VCwSH8ډ?1`Cr @MTWtlWt-" LO// \X+:Vl%_5\QCdW7NJFU[& &eiR:lr>?|aqiJ4kaG>% EbNHg<=f eο*gnAqhH1]zY8SɌFXCf@:-VW-ocnVÜC>OS.}׶#EρCm8vH-%W7I9\L{>X=+g5:'"W.N+T%~h-NݤiNh>yr]O/t\wLWkFhT'ZEA:F.4BPVs-re 3F,%prZM*Pt8+t=rbWv= ֡Ӥj=Ŕ{XnEEA]4eh̀1kEgZrw|DuÛlQS#P$DC\r 0IAb ݘwZJ2VζvTF323`ޑ )]t> uŊ;ޅX2Yͼ-wtY$.k!KWr%ZX޿A,|\=B=8^ghDc50sfSoMPW,i^8$B|IաQtxr\g%U7 "Xk>*WV贁Ϗ,"M0 taƧL#5a7I|EH>etEYFgK,arY'ntl]:)K4ކDxK5, %(}BRit\s -h@x=XuTFu2(Umj=sM1Rه57$/2l/7,TQU(:B IDAT=O0EJAtݯ!u҉~/aM@Ew bpAsiq֗=Ǝ }t'qOzf4ȞR?MEtfk @Ck;6 4.m"$UkkO2ZHs4+wL7?Wm])>Sӝ/k}4t"*E"=՜ NK4t#6Ez\2ANl0H@lgrMkǛT`,l]Q $bU$OyMu_7b5]x!ן쮓}P&=UM0֍E-MD|$ .hlf">4c׫ۆ+< v]ŕQ^7aX>w1 ;l7_Ž h fzz;u Z}sk2 1ƅ Zka'/#y(/ǒHm!_X:ݖnKηZU1xZ>% DjNjfD]G>-qK hOhZDޭTq*{.tb:`6S3h5 ]pĆ4U(7fc<&;aFaQ꡸U,xXڳ%°g+kxNNyX|jbCl+>'J?'5'"Jp7OȁcÁd\wa]āG:{ D9{RtY|Z-k"琺aacŻ,5'Ag +[/ʾ<ݘ ze챼`؈@F0/} BFzUۦ- t\Sty2$Kצw:nn!}vj2RDar61;+2G5 !!]>]],;Nݳ6GuZ8ĆڷvddވexJ>1\^Vw"X*:K֞qQg3` "bW:oya565=a7Q犥iZ~e[M{󞃮nx-nF0oQf@k0~C0asY2PQ&< Kٷ* ] R'06k-옩kAΗ^.k .D~#T7NjFSW.jXh,7l)V} kI7څ6m(XTuyRX34)N]muCJHI*-neZ0QNtBss!c]^p.I۸R9Eh8mljxV.\kK{Rx_ξ뽬Q.VIyyfG MÆ6ߔKܕIT=u7_ghrBC݆7􌜿157"MOk|rUeg̢yhU{3@35)&%gbfɞ#~!}@Eɛ9a%eRSjcpgNsˆч_̓r4y7ʨfjZ"0mM*gOb 3)YN]-\WWۯIAfѾRV;BU)%ڪK 4!]-^.GʭbxZ&X(:Nwt h_^lGl ˖jN"h)AN~?qUym p_\)Sg+ &B;)]] h"#q菫[HK4/3qtԅٷ.Xaζ͆K^cK|u'x^uݐC 4o:'>/m=q&9qL暵V7NR~KRz,$}}k"-o)v#r&;qT3yM-Z9v=C0is};j~0pix>pu7GW6vWLfTæ_| ]*ţ|O4eSkJ6iw%[Gs!@2A NLUv,{*sg>{MVP7&=?]d~eu_:A_Ie0yq-d86y\ (|j ^& 3Rp`+H4^9$Z CxylNDޘG4xE"*t hWmXK~rRV6MO|=x2zؐQ7Zt?uc󳟯n ?40#$]@dGCC0#M)} t}$'tS~X즔Ov4DDSIw.gu|a921=\r~ԧ}TxΝ;cV-S9Z륊 ^ (@1r\${raޯdk3Y6:N7w tz7:ލN7w tz7:ލN7w tz7:~ ZSn1rPbk)/8s˦O<銃_sJ{v-٫ޮ([k&w{&,V%r spa#rĨ4Gg_D` aî?x|)qxz] C{}S_٧[ENqByo|k~e穯ߺ7굊pHa,u 56s5|繗>d`A_tSSJwixHٙʞܹ:ܾwO=romgwO=ԓzGGtSz2[Pai=<ŏJ ^x [ϖ fn갽J?xAV%ҢB%Fj"0Np81bo7$'w-{o6@O+nD"*<}y`p%⭫kj~\DD(p=3:aK> q,[_>ǺU?9ؑkVʰ>ψn3gOѧNpτ-gK 37wuسw/~s/gimj46qʼn->sglyϯm/}+]@7P13neF[q{4ghʌ>+b}`q`ن]2 ʐ3o2،+YE  w9A~O aí0)9Zn$r q=־j@6n+Q6R_Gn['Ĉ=kXlc'lV|nhgWn n:A=>rw9A~S12J7w+ +" )>lG1+s@X| 5+x1 <󞩅;ߑ*kT֥v]{g~0)*bd}WK0ʰ 7`Duּ&sNY5ceLP4|f+DmJ}#fD+o馕9ǻPw]e J>y]3č]}?[!}ZD2Ol[`8ÜҔnŶ]=pNpτO a'@Na Jw/W/Ywա2`XDDsj#fҌ̘6o_ ݠ/vuݑC5CyGygIEDTPЁ]vBNpτO a'@Na6}խ];@N۷k>0mPN蓻eg2PR\G$:q=.QUMa=  L ;} J|߷3f՞U/Α- $ܩp+Z-N[>л5rk {[RQݓV,YSwggukBp w9A 37ȑd&؛o~OK 'H߰:_,^a$GE+ѾFnI NYZX*(=c\dw[*r Ņ%V1YwJ\Vy$|]'?(ʰ\#F~'_եNpτO aᮟ*8eCO|!fuޛ>z_uL9AueYuhLlS;ZdňͼaSiha˰%''|+]F繪\&D$E6TW6鐸!f/;3ޡOXrG9mMySޢ ,Um c& KXx% qcgesr/~o k57W8+#(:_ώr;A=nlI a.Q櫍KiZ36 ?=Y3lLTyeu4bʼ猍Km+?MnTFFet6bΣ<0*g ֿ»]4oxx3O+l~s4d΢g$Mo>35uNݷtKeX 7W6gMD㇕=(;A=:- $ a[zS. "W~$׿{S TؖpΟ l۰w{xW>[D<'=` sPUtocէ%-z~pJ?t㒥W8w_uAs|a]y;iQ!YG_nGrƝG XqBm=jޓXm w|$NaKӳRC.6vw?A =J30LWmeV.}s^@CkWrGJՊN7w tz7:ލN7w tz7:ލN7w tz7:ލN7w tz7:~ ZSn1rPbk)/8s˦O<銃_sJ{v-٫ޮwkR̈́*lK^pjj"Ov~bL088Ω cu[7\&+H{甈qΆ ?Fơⓟp:pRQS.N}]/2 A#b/f !% )\p”ڻztoTpo@xJM͖dnuMrr~{xM}^P/ -*lܽ߻w|r~5:Z9xXbT\z#/Oh5tOxHxazS_u ok9ƉXÆeOPϬ}4.s +nXpE)ѦѮ}ʴh%{KT^ӵ,-^VE(vd]TR[3h'o}>F)@&?ֿ \.bD~|qyihnVnΈ2t]с.][?|X#>A-NM$R}mOM+߱2]gg[+{Jjsp=SɝZ~O=wxD7n_'_|vlv[2[S|W -/1K&tVAAJ+g>fW"޺EDSؓo?coM wQβͷj5_ZݍJJ-͹}T;|=ZpE zxey^H'2o]x|"*Q8=EZJH nɉC]s[*tik`3K׶wn -_#] oYͭL&WN$+]@7P13neF[q{4ghʌ>+b}`q`ن==v5g(Cμ#Ub3gVj&8a Ѧ'grnjH8>-suEB&ݚ0u7|U@Wo2=o=^0!iA]. hnfW#vl8Ov;Wuc>x=S w#=T.,7Եyk CZ~e(1ƍ>ܜWT <ᖹs&m}ݢ@7nS[֐?$12rkjvWr!(7`d܀MZ:W7z1[ӂWu.t-bVn}}͘E#n(%) _Y .FwqU߶Yst3)Oe79ř}U%D/z"9+y]3č]}neHq[ie+7萾rGVvogضbFq9)݊m'X5K5b֏?޲uOq5BU?jřbgV'eE%^v* QQSK–i_Suў_~nWbLa Jw/W/Yw n;ݺHޡW͡#uSΤ~""*(q.x 5 K5pkz-BBC`[e̫?ݿt5qL֏Zoǧ+L Үyb}Z d4f=cUá/Yܽ,_n>?ᒊX:z$iЉwtɎ+ih́R̈́0}Ml:?!a#6(1'.hxLI2 JNv7dL~][]]iw:C}+4ogg$ͪ=^|mk#Wtk˭EB.lCoIEMwOFrlXdEN["ߝ_[PqV[p-8$44$q?}{DDMM>!ltvtBE<_ {L F=3G.@&?^S.H//&=**\56wK  #r~/ _z6K ZgkեQ&)-.,iʞwM 7T*ݴ2#c?9r9~}Tobfvo~m)*r\֨P%fEqn,gk1"Z}\Ya!J2]x¿%)ҕeա1OhyfB2[ 2'sːP:g][`l>Jy*W )i UM:$nȤ Όw;VQiNqS|h+wU"bǘI%dψ3=+5,۝[[vjn>h\Ϯ~ ?;R߃Xp}¯5tuDuG ͚`^fC|M~ <=#$:1|LS$qw<4)(H{?^Uq&a3rP{aU'*8(㶔*hN?zjx'5EDڻZyOV9Fg j;U^Y]E$29cRO\ A|H捊5_m\'M֚ ˿o6bΣ<0*g ֿ»]4oxx3O+l~'4d΢g$Mo>3Tԅ/w/moVÖj>H3&"W}7ifJ#sGF]n* oS;3B7k[}&C8Î[k%kԙAP(m^ԔhL~72x%\';.8YlÒ@ -¶ F] cݯlI{SZHН̚n<#x=4dOCU/6Uےnz)ҍK~^}e񙃣w8VEDfM}Mʽw{jB-ƊȀh{Ukncf@/($iO]UqMrsAÅGDY;m6q{y{E˟4]7 #j>/5!0[d<]]SH-0&?fVVxR w: U[Yx`K~Uhn3FOJ L,۝m/{5R_#%jeVy.=gt b@%ލN7w tz7:ލN7w tz7:ލN7w tz7:ލN7w_#n䔛fj ܲ/Ozܹ`Ҟ]K~j:iڴIc VSeؼqۡj_OM,L*j6 "f_ce~[S[M讴ë;keңAU̖x(D.=f)+>/N~5SCm¯r}%/GLn=89:歯.?v`MVujbLk.]zM TFFet>ߚzAF7NRYs1V^ |˷uGzѺ m^‹9YswT3#8N7}ᢹiaɎN$a4 FGDvm%lQ@a#v<T[qEؕe̖G}wE V^\e )`QYӭc?#5T?}gucUn;grʠ[K7v$_ϡ}S{Tac=tH*|:RQ iq yrwm,LGºf˳?r5x} fwWV$2Ο5,DjsZɥw1[0 CD>|Z\Vhn{זtڣ#:m}1_ֽ+xsa#* ?YGAM=AnAD8s#V^\e )`Y_٧[ENqByo|k~e穯ߺ7굊pKTla2'S{gV>i}㯯l0%4mƴ!}n(V^\e+)`]_tSSJwiJٙʞܹ:ܾwO=r砠VG=SO-MW?>-$<T!ޒOiQ!J-ofl/R̈́{&|#~sJqrײgaa% FwJ][vTw' A0 %"١.2[UXx1O4 7U͏9pBڰ(C|şnn!n:eqSi ~a+dZtk XW:*(8H4{ETxJ[Wxr}P{Mg t4Ö4}A.Yy]Ūo6ޥO~u_֦1Mjnn3gOѧR̈́{&ܒ->sglyϯm/̍⌸sS~.Ihn=7 ;[ [ AeXWU 篂""CyT"f ?1OW6hW2-XLt53tcfܞ9odFhД7}f}W75> / h%_y2W%6}Vaf=V{1_}՜]^{ϝ#Rϻ'ALD"M NθIv_}Uq|[~=Cú~oΖ0%obϝ31L<[>[ ׍ "1FtL!wC2b ihC$i.{M鋝n;JDnTWu87-{S3%{ְq~wbv *fBf]醃\ށ2+>[iԿL*hg]@uf=Vؔ 6?Pt8*ock3*LY)-"bJ\UEUNw)`4MdzݍohCFx?/2t}?>ۘ⣅'AqCR={֭~#&ǎQ)MKdдi !Ś wEc,]&';+x1 <󞩅;ߑ*kTfe ˎP5-H|%[7낵V;raHPsa02n&yMխ}}tgqsf~JW}2qF?n+p9SB6ƾnϟ0/EK0bf]Ѿ_*kշq;OsUb_s9S O}=ЫYLpM(NY5ceLP4|f._"Hq[ie[-qw]e J>y]3č]}Uѓ0Ϫ?mV2k[|Oe79řFgK*ڶ/_3k&YW>)o~}Gz}puZD۷.\SrGVvofضbFq9)݊m]V<ݹ#¤KL+5K55UyV(F̤1ob9qdžK×k}= ¾[?x=ŭ{hbDO U~[Utּc拥ϯ$W_HԘokl*>{O/*OUY.©t f/^nD]SCb[;T9whΤ~""*(qwqZ;x9룺tÕYpueYuhLlS;ZdňͼaSiha-coz/_ ­ŽQٿׯ-U3LEĬ(8|RKaKd4&i>yB!ѶЬf51u{Y߿),0F-tk XgKNNW_քȆ&7dwg;W4)>|s[廪Mtac̤aqksw^cčl-M8kڸtɛO5Cij>L{puNװ#=lR̈́{$=Y3lLTyeu4bʼ猍Km+?mw5}t=ɶC~ͨ -þSud :j:UqޣBfY0/3!_F̍qvHsA >Qo4 DE__U T}0iWd;PF[+Ktk X222\N}8Ps}恡W9kPmHEx?wAC,z|FєSm>MEM]8;2^z-]-bK^pjeY{v%x 2XO;cm[!KΟ)jI` ][y_"+";i j ؒ}4 K{ =oШkG8 Wuq~[iE)lwNnE5}t+y<ڰ)W=SswT3a*dNu I;ϽqeXsT3a*vQۻK?:PzkϒK}bg]?<2Oef]'×C-(g{?ml#-ٸ?34eFM{Y_1M;mp}l REodWs j eșq>R3aí0)9ZnhcuYguZ C׷~?S:YrʚVe.}|F J5{zM)ٳȻ6{;]00ƦTƬa|YٺMk^oKa+/_׍Y4oL-9Pi_2q_2qF yݤ5/3!k/\b M*bd}YkiuE qS&D)oΪvkb% pE]nrk,vC)Nir-]D,~Qg5sL9U׻h[mHq[ie6v`iuE4'M{->{6u;Ķk63)MVl;ck.ϭQI32cڼ\k j&wXD<ݹ#¤Kqs: akϠ-)veV|vf0;ݺHޡW͡#CTΤ~""*(q.h<}˾FPsT3a*wnjV쳫/$@~C\bSɝ4a@`KNN&*W )i UM:$nȤ Όw;VQiNqS|h+wU"bǘI%#h6Wc g_f\ᆬ_~k|wxe6,yVm|p6gMD㇕= g/^852=_ْ{SZu–]aϠIwxZV}fMxNܑ{ ǪOKZmI7=~v_%K?jq2qv"BR܎&fލ;uCyn u +䢏:XsK2 ^{8gڽ'/^ݽ,L؟7zzVje:fT`iY\*–A#}}{i;1,EZ *;RVtn@Fлntn@Fлntn@Fлntn@Fлٯt(63Ӽayg{{+6O]zQ+J.6X{QbTNtݑ;"|2[{ % a7f;tz-6oәSTpHiUp@>ݥj/j Y ,R{Rڭv4Ia8e5:}-6qKE*O,m!3M]T7tE"$qf/ab(1=RWˮ\up9^#%&:8'j:dX=fNxD:W(a6utfm~珧/ cLs(W}W1Gُq_ ]OCOGm:ce`xOնhV&_gޕ%A纪Q:6Z[I:@yN9߱ A:uZicyP1Z-H~2||J;3UϞ[쌒Qz(}lni꙳uz-\Rzfj쑦?ܱRZhthGuÇ~QRWNuL;D) OU>%ζOU~2ζ3U1KѦ>!`QJg[NuEMzDwP}Z`+O-4DO*I∐:)h}T)zl/XE[f_9=&&7V9.}l[ki8)+1NQJ3ꭽE+'ŧDD%R]bZiwT<7Aqnƹ̍nZDvչ_:+.SIO}*h߉XٷtTLQF1Od0m?""8B""VmxSe3NGX=)"ƷTޙCf4L!:+u%%"RO{ H%̐&SD$~""bRkVFQjts81 ]uUDo(".i;4T5iTgx\y/̠ A$DH5RŒ|lv;INsso}N7~Nߜ8q";v\$dQ7c;f{?̠}E`ckYkwv&jCe ZJvy%lvw2 @ _v~K?R.O7*nq? ʚn!B!B!Ĕ&ts}3_O1JOy'{tC!byq;nKt c2vS< %r6FC?@-B!B!2xĨa[_B`٭>D|j^7o"B!B!Bkv/d0lI3z4ˡ Et!aH]9 BjNKQH9\5y/Ls?:B!B!7n܌4S)Jlr-f0EM(-p*[GX4$5xp P+wHIo b9w6sB!B!B<fw+ ZyaBxrVV* z/t/8&37qj j{S#dm}}/Q; u0<\3!a G6Gm};o[Ah+7 e*11ml#VC<}JT΄XNNBߴB!B!B<&_~]ts_/\_L+âYBVF^HpF(7vt8` (B;޽ñqPb9$*0N4JwhMK9~5.+qJ2 9tXOmBk/>K``lu 3_N8:\:ܷ?d~ErV9g<ʂBvgoQoQ!B!B!I#ݤ <Ϲ ff;f^VL Bc9.:U,ݩR9""`-m(յq 8aL&p5#]SrGdXH^:rZ`uWB'ȝt2<3A1#dpmx9n2Ҽ/p0Y(gqpW9.T"B!B!B)ʁ^ 0WӕTpS~M9‘!Sߍ*IE#B>5˿SjsyIF  :ݎtnJ:###g@_j-[ v:e,Ei #TC7N̈́'pI9k9!f UJ^!FTН(B!B!xQAAA__\gC!B!B!쾑R!B!B!iB!B!BI[!B!B!&n!B!B! DB!B!B,lB!B!BI[!B!B!&n!B!B! DB!B!B,lB!B!BI[!B!B!&n!B!B! DB!B!B,l3 <̲B!B! Ns!6CPڧZsn%b%hi{N\!B!By8 $m3ˣΊ+sg JXk7_;냧;c;-2F-"y伕G~C%bW= OCeI! /JG޿֦! ΁t흟_N}k |.!B!B1{$-cJߴ5ck` 3,~mIVwGW4Rm~oP2m]7^Xe⮊S7yym?U"]qwulN_w)^y}҈Ɔl<5ӷnN^LXB!B!BhisO$Nuv) ?Tҳ'ܪo\DULIc׀so.ei_=:􂥑r糟|]U&D{ :o}8?^S&㬻xtT$B!B!,#;nT/8qad;E`\['}a[R-gPYІ^ߙIg{ӻm>clܲ.?3!2bp5<(~ةۍLYSDޖV&YßhqXT%ۏO<+MRՃwM w/&OA[<"g(qIF+7GՒ3mN0%?ٕdr|prDvɟN69}2xU00ڞvL :o}ޕ֩JX+ycuٯoo]|6߭RsmiʽúB!B!Cll9fQ-)IqQO[XS>0AXwkGOl Kɲ+!ٶnۜAj`xDLڢKk^ٳ<92(lThL.XP}u1l֟65j6h *XT[c^AmN_(wAI6/ ҳJ;F2֭Byg L{bCv0חd{,FR &?kX|򂤮;t!c=q6MT;(]gi5s+/_ng[UItu"iWe'%EJWG[WZ}5woW>m^]?rc5P׾MqwzwډXw__֜ſs}G鷤nXh6 n!B!B!f5q7tb֝;6E/}꫋7N wF[WZt}2gWY4UWtBVh3+O[U%[{n}r¦_62=e^žl+Vw:3>rWĬJ?>Wj3Xo}qtk??=ZO\ktKΕp۟-޴m@ڰ?W|UV{&'wid[w_+wq\v⽽ NB!B!B̒ wV.孆~O}wzT]poeyJJn*US5Λ|}{s~»?}fSu˂ɫ7tɊ 3qǵN}u}CQ_xGgvZ9se~#E!c5d$d0Nxjκ'o1@XW?jVۛwfP}j _k HѼqB!B!bڦݩ]UW>懿V};*qO:U]`b`VKt{~&clB옂ј.JT+7FfcB_~ƨUUtIW:Oط!s 3-)=R# Yⳋu.67.y{G{uuGzޚ/l!B!B!Ov=O_n˓VƧ%)W'=c2N jCWn[q)ݩ!B!B!|iHtS;6*ކ[ǿ ]j_gkǷ:;ebwZ{ ;%bi3i@ڍfjӷwg$f$O5Mxkrvo#B!B! EkM;vl\mqEO}kQ0ĬYi"nuthB!B!byZӭ֟[Sw^X'9a۾M?9Vu_uW_wGW"׿7'LmO.m1{^P'K˱DŽM˿w~bUq ;/-#NF*wL)vӾ F Tiā/qnx~|`;/nlڲ.?;1N{;WN0Q ޞt +|d\B!B!1C}}^,.%rɨѱ&}e~U& 4=z9ڒf(n޾63ԄK7ZB!B!b#Km֏~r0emWV=^-J [ߔjt ˏޒ0B!B!s%Bhǝ_k9kn^<׹ή7Ϟ8v.s !B!B17$-f@ x4's>^'-B!B!ļ#t !B!B!X]B!B!BC` us!K7!o9pu~C9i g_%8ꑼMkFdg;&ƗS\IGRu")yI5h 1bs8D(|qpa;慅#͓'V 4ǻ%ҭMCR8-gN )w$=1S-|0{yR$k+QǸg(ҭkV^ j{ҝg|Natw= V,ـN4TѝTfEW&?4!;ӣɩ~jB.a|ZA 3r[ՉGl*hbը䵖OBɼv'Flt.\č F`\ju"Y՝ ~q@H+Yw޸Q 0"02ҭR`BbKpAS ߢmC7^_XC 7fkܱqg ˧w^X՗V@W>87 6z۩._nwzqX&Y-]Ϩ{7tnXō)'z o4ϱqP0. v IL$#0Z`WR^ө#t XxKV޴FajepQќr4H6X38!np%Q)M!:s8.~P:[ n}=9dޡ~9uzAq0O<߉%t 5 ^AT.zL=阪%Zq8p2*,:j6_چYs9rᨡo91Jx /i)Uk(;Dx79ՈG!͌sQ.5{3?n]f|=DJo4ٴrWN<4YOa׵g=8OpJ\ nh2'=9 A=} [bez[K{ʆQO|[X1%@}cqg4~uZGlꑎ}y1eS9 w鋓=tO[똍 ul% Aɋo҇'kD>> tySr`IgGF@0R99+(_}LtH8ICop>t3ui0ʫF믨xpUC,׻GsN>QCwyu8HAVњ-侄(vr0LlJj=+4 acg l">29 }|zfe7l rZ$Fg1+yZpqlp oVו.斮~'~~lk H~fBr֕B<ԥOY#C?,g鱋[PBH11թ-h΢?X*5N`PA4(#N`FpG!+_GȆfڻ)Y7j>U .u {" ߣ~@hņ:FySݾMݪ΃a4“yk,J&"Sk̇ᦱarvU Na/' LvKŊ@B+C A!PNjSzNUz;MrH/p#fi W^Ѵ7Lo Y?\HU(EdXz8T+JMEc{E[ǨcVzi)YkBǾ35v Oy}fӎtS o5n[M k[0*˰ErF D.-tȝ!tىzGD!-Cpv~ $Ӣa0 eU9RM {:qmG MW6\BcqSj`QW%8#u q*—R:S{+6@KT;8 Fj?S??BL+ #xUgF-ChwvhI#$p?Z;F\a`DYw>+3Bmj+gkGZ94V`iEل3!'ut _!s#>8rƬ(X<*+uNruNԋaqrⶇk/TޡmD`ŗeMf\xcN iPD]c@ɆEco݀ OHj'TҨ- nz1iE yZv]~nMq5@M]@X0G|ًn0,Ө]zi뚪y;`n(,+QW_L|m^ ?A  ?[Ku< Z0T^ilVh]Z^m[_ihk墱Ga,A`N >2ėC.W66!s#SI9,Rpzb u\kn^u޴: V^[47~c3=wp>Gr<Ƭ3 g}zsuNtwclyوOr x0!* Bm?*9v2 )L!,c.R3Z[ Zb{dg3}; Ay^_yDe6/Crċm ?A>AjFG;`p"8@1QPfkPً/,UNGmZhFreMBV6 <5g_#MYtj^dË2'=@mm.@OF|WE_E1a嵕JC{:ViL㑏}y1roX_9GF鎉u?&\wn: Pn*(PBcAS7ߧS=$H}`#@VXw|AE#t7at:g& 0D#ֈ)SQQ 4Sjp['8MB]z XbB]?V>vl-goTݺ:Mj둆haASH**88]mx in9q K NZcL*J&}Ĵg:]u303a'S]ؗWs5 IDATJ{.GJuӌth5qU([+q~6džbo+0<ӆO0zh+zЙhmG0BǕlE9cڇL0\U!pKPڇ4@%}lߢ{KyYk [t˛0 ^wu8q#Ƃ&o˹#זt@ Lu<{ t&B@G/a~cYxjs-22U*kڎVje ad6ÃU#~H'so/ǨT[ 81rRD#8 5Zif'3CoG;C;U:GJz;P n*ESzڳ}.:F=#>j0˼BL}y_)}(N>y#;v5cumCh_Kءf"eD$?1rkoo8N#qv*#= 0jÒ_+dN8 0޾uШ֫4U>v-r~U"L}u-]]_?Sti2Uy6mLwʺh̆4ݦ,gtZd[`Er"BYT hFe(pxad[giCY7OBu岏~w ;;|L6431qA]cn3{ d׸"a[ό":\D́ 0.H!i5'ޥJod޻m ߢ/NƮm7qYgt$’%: 4GhE}YsLW)GD UHMDk>~S?muY;_O} c6g>K!Ʀ[DRChE *߀BOff E'3Yja堙xw_|1+mdnz$N"iiɩ;T2< }a5̃9 734}c_s\)|7+5Gר Aęǘ7@Lv)J D)|tlxrNݾIa+iT`w~;9TGF4^^aʽ//" Ir֛)OЫV\]zm]wƑr ~c_ 4tt۰ ;z r֕Btx<|7vY48'Kfe=C5~kul^;'=ҠjU 4#k q$_u:<vr>=EF4|b_:z$3yNnkj&At صDmAk@0 Rq3*v%ψNQK9>6!/ock&B!$,yMκSEW6tQުM%AHKCŕPao9d*wݱ$,GFZ܇뫔:Xf%Wޥ 5`WWDN\h}PX ʧ}7 嬋 }{t!ɜS4o[A/zxvYƐsm $( mcfN0Q7bfuAEQ pF|D]gĢMo]=k9/DH[T $>~ן!N4!5M<՝1ZбwM0g u1g8z/}⧼tuR':MtsOp (. u.`za{iTw:~"]n?^99*]rk >c ?T{%'qY]}ħjZ@mMK5mT36ho ZmhD2z+/ dY.Fةt$@m)/M]t^ xNN5m>;]m×s1GrQWi2N#.pm5m3+mDPJ $щJ0@.X^]HCǏx|!r>{@bܦtV*]8H^HpF(7vt8` W~]पN6/ЃVmim0Ǎ^P_L+âYBV}!t*r?1k\VNdF9IY]EG9{Kޔak2 0Fm1P`td)Z]{nPMhE@8'XuqԢց$|;tqһ'^yVD'+Ԕʑcq}-u:C]88,B)n!u!q޶JZE1?! nхtFKA!xh€ cs}Jd#EqJ™y '܅3Gݝ ]5Xu<%zHwK oW= G}ET7Ah5d{Q=csSۦa3G,}NhGo=KN]j/m2b>9`ӷ-yg{[t3푟yg!gA-nf_?\ֽ!oO2t'4:ۉaVp;ڜNF8:w_XfC{u{;ե\;uWA OIkԓo/%Cϒ"e1k#Hn*JK2‰ T2vy4V'][4m3+Pv=)XHmG|ؗBVNیd\{n!N6 n]tSX `m5hpEq2 ~:|ycv:z[3 PT ts<` BNa4ҧWuѕ }))gos{x#Qzb51!&!12iW8  9N;eyfbFS6ζx ot!3B LpY-'W+\Sܯ2G3xG:Ȉ۹Տ{-]̝t3hqmC {*g])/ă@$%{xц$6ֺQOmj<ܞQsQtѡxv+X7ӡ杘tDX4Hw[xE9 }|lRMW4#CL2U"tՠw J3 #ocW=Yh5|L{@w n˸yp 0Cmz"z.g#һqT\?+jP˷ۻX: .$dt@֠MVb@W.}]6tUw\]F)6_|o.;ar]mhu TWۘvN{!RjZQ BC>ՏK\忞i}wV+ /3`8LqF0ACqryzJGY 3XTo5k n4PM*5:Xa@*E&ry@ Uثi*Uݕt7GF2Gπ.4ҽ[tz#REW6t^Jpݫ|Hz"#Ã`6Tvn}Op[qT(FS ߤ;5`%`pG [Qzh9Al+`t]ҡ eq޾fo !LH!} j}ZB*EٝߓrRԜ9tj]}@)LB'ȌuڗGzb syyَi#Za)e߶J'5 b؜KRzb N:Zansk]FD1~,}zd~#?&4!*Oˏqщ?z mZh'#~q֥. RagϺN.H&С,wbD50MhKJBk > Rawo R'2C˙\nhV>e"cSiچ>TEwBegηw5H }Fҍ;|z*3 kܹRb^lW.J!Ba+'; PDFr6sߟ[..Oc )Q#"~O7i]-B pm$F pm$F pm$F pm$F pm$F pm.h!upOXxTwEV'K 1& 6leW/:gO_:c~g,La֤YʧǪޣjb=~e~Oa&e?~^zevR -,M}F =7pӺOo̕oOVuiz͢IuC։n=g&yI ] ?$>e3ZWhwaWv%N7H)* FZŴUup~xB9L9ZO泳pC$4`΢a"ooRY>ϙ5<$Es/qyz33Cȧ̍ 0Ҝݫf>9==1v™,/AZ}V٭pAө뇖xVuǁ܊\,ˌWܘX/J3V}bofaf--ܻUJzŎjU?_8Y}]sdG~>goxՆZuجʆX4@jYzZ6V+:t\eM=,D/%:UÙb!aݵ6!0NŋS mYh]/rz*oomEU\>n]r%Zp?* _4,@^?G_3j㣽*Sѯߨq#cczR};wRq-L7CV]<릝57}~?,5ԾI'[u/۩Û>Z5d>;Q8t<:K-nF)DEIi=i+l;[RURR![v!K)_m&kŚZ4.iRQX.|^^ьt~ OG=h͉Z5N S3z{{ctt*{&R*K2}{3d/<¼>f)PkȌz}fQB_Ya4w>;K:t\%MOW_LWԯ6լ^!ǧ5J-`t"܄%gW.t2pϞVzv'wjaMj5:mHsn^~- Bs9+i !sd-=x6|>f)JRVI+Fxu_`!ҿf.]gߦV^zqr浏nU 6i|ֵAۡ@Ytס7B`sŦs6)q\Nh~FoJku Whn:)͑ԄPkvAV{M;[$gma_/d{la]!Τ~[Jz~ol QFnIdѼ*!T}qZ84X!쵺[NjsLXlv!PUKZ IDAT鶆?h¬k-hCN3n+6y^G==Cgn\{KMf 0bȆȽ4%,[6uw~Aӄ}ı lܰ o]/O)ʫ5,YN%n=~ :;ph0&B ~ˉ1QSxD +8i=?R{ٓZ<ꟿ>V&n(4Sp! zw3,<7t ҄KMJ!tswBT[$JKJ^;4jI+%PJ5)ۯ%oO8)9%-+Vq>} :ed; >qɳ{{=Jю\HҡOg:cҭJ3Ã/gd&ثy[ٯsbz!=*[1ڄ1jg"n޼gZk+#ڵvC]:XgLʮ曇\Nz 4GV?+n*nJ&t韛ʌwgԡOSgá@Ki](KE nfkqw ے%\AsvkR-lK0޳^f)KR6Դ6_*ݻvuB̿\ٸ4yzԯGUVT^i94hƬC5$˹yUJzՎEhTUyuzpnC m;+/Xr*f' mAQWnzt7'&ys-һſO5e,6%d_cݒL}F?n+WBb):o&6BhW;6^~fz` i=+ˎUջU !^؆_ !fFRֲ/AJgm][) D{J)lJiO/J՚?C'-sӢ놰i/PcGnu9;7}ݣuL/s&;wb͎֫9h.88f5>X9tbn kYѥ3SW}z_~,_4~? ..[}QnyiPBDWrJB aM驇,d6y4{S\@~ݏ4B kWZ?VD]ϥޘ~[W^^]twb]f,>g{±|[W/Owkx z<6X6pcNUtr- ^|2]<&B߿{!lg]-k|5qڕ {5ZkvЉt.==\f=VUWu @y+3mw{ 3=-gNfwB+#ąRkl41,S/wdž|6㾗YS!*sSHzބ\f2v~^nRzJΩCRӲn|VЪ',!(閞3fbǝ/|CS~1K}[9qveWtY`ULkPeY'J/?ӶIhh=g)+砀Cb7}QʅʶYߚ',5&閦sM yr4b}|ά!/{ӛG>dnlA^0ɑ]0T_﹯ g),u UYmv:]ہAHO%+/i#5li}#/7ٖ6NX<:2c7&KҌU؛YXjYK 3pUF^c⼚VzF7NnwVnّ= [~f_?}"yln3dBJғ_[6)[Mh~CG6EwQYtkBRY2ӞX?8 ][lBC_85`Λ/Z>_N7Tm}Mw׭KXx@ T3xGW|XzFM^`|WEv}W`45ndlLx`WO^wf%Y)+פf|F~rl/thU_?YA.Nhӳ_oS[I/89Xe}GiQ*kjA㤚)(IhDg]ޠB?Ε cؤqA:J.**5/jHtR#'?_ N:v+*[IM[D]>Y_>[XnB{ŵ3_}Gm`BQG?p4JUy=sg_4 BRy6vw.r*+-V[NSjbR9&;h u1ϾO:[ HBn;E ]4!RRg(݄eɫҒR% 3,|2ceŖC:eҭJ !Rdki&1Ӥ*nJNX*n^:=O!]T,UvWtQO\^\/P:r!IIA~>76eWG M>aI**]JSPXBHc`oJfv`ω齇x 7.j8)Q{Zs߄^ѮA5e),k㇟>8p I_v|kIAsbz͛CŭuWݼy[ drU"}5QN4,p:4-e x>{n4DkDY*-Jwg^o6^7?\ؖ,9Ex 8[ ֔jaS_W_6Kl]yW޵fƥӣ~=R4HO}ȡh+f^a⮥o/k',`%_ͫR?Ыv,*EͯӃ[v8onKޑ_yyʔKVa0;io*rӣ_Q 7h> 7Ie;LQv/knXY**lJ.>ƺ%Wڅ4FS!t`u;4Mmv%42v}y73-ę`jn!{zof&uwnt[s춒?42Ϥ&ȑI):R;ziԼ*!DեɩV~<,]yVaܳv^&scLR>Tm:sĸi#%dζ(i4cnBBi>Oqn>Wyg 5념Fq  -,72]xy,fQB1tl@t)·&P}6^8gR>y6.}O5;xit5_.УǨUoNj?`V&\jݶrנ&`t_wTy{_MFq5_WXob,R>{m8_i s={Nx76tm<fɚWB׶̕oOKWLݕ=wnnu96!s.-0&8pSFk^RH}>~؅',;VڔNّhLgY-PgֽGk~*[Ueɵ]klʪpF3y[,緬]`Q3 jf [6?wjYˋ;=?) ھ[N[+2nm{xkw*wnڭ7_>Hdmhx篕Z^y#M>phpn~5iWo[ly5."&\!.TsSOXq2>>O8U{uxG5Q҉tᵲߟ*+ G I7Ж^+kfm<|X3-$@nx2Ҏjmk#>ƚnk#6nk#6nk#6nk#6nk#6nk#6}{:8'On,n<[ڻ"m%ߘQFDŽztN޳璘[s3&0kRY,{cUM.p~5;p}1l1I /2;B\X[) >#zОf4eqf΍vjnL%3zL 57H|6f 9nC,;"]ؕ]I]jhF[֮9r_xך5?*TY Aҋ?7OGg P&閦sM yr4b}|ά!/{ӛG>dnlA^0ɑ]xWͿ:}lb͏u?Z.݂n:d?SFt+RJ1^RfpRZZwŇ2J̅32~qrui~EykHZfύknLYtkBRY23Cºk{mBalys˽Ѻ_pCUwtg| yݺJ]K8=C]}f#5&=&/\0>ګ";u߾j+0726&StƤ[ z):{3خ\/DMJnJAQS#EoJka 3l4co(!yD{ʢKEq4^s {M;pl/s兝n!dၯ_8sΤ~* Gz~PMR/ L2Jup'U*/!eqhBح;_mn.CATG6ܗ]GUyQn\.J-F)| ۏ+,hޙ4S&Mb{Ŧ/1yýG{̍+v^iah*D?OQzrEY4holoa~nZ6h: T/8v ‚v)EyuYZ@p&%:ĭO\_g nE*B3tBZگup+,LMzCc,0Viiu'{ғW/jŤNv1w-)~=wiBإ&5"yUZRDڡ™sqg#"NWhβ~97jXoct@c ^㢍RUY'* M7KX.lKޑ_YU2eKS{ 47sM-gۥ蟿]7u;ۻ\̕v~SmXWY**lJťZ%ׄn/r."Q+:V6gpч9 uqFKh>mQ4h܄@7)1| /_~\}".ԬB}-\c+:4XMo4e4T7|nj֚w[>dccSziRH[3A/P9!Q=ݤʚ@w5NYt Uz۠r}͉Z64ּ̊䔋w.gݶrנ&`t_wTy5L6-s\U'<\s-vu'gTB4sTŇ6l>32pϻY\!M^ Ë49ў='!e3ZJs.ʮl.5IS#ŭcki mj\E?h/N^=D ÏR.T@[4I4 hb۷9EODsf  | \̬<%sc 4g}OĻj9ckW7{ҿ&x>c~[Qދ"=[+nLT>\7jf]᪌R%by5se/o>zݮ5uGߨiQ$}횆ږ4ϮnTeʢb{Qϐ ޚ*IO~SoUlWNm6 U Nֺ襲d:^'RVg&5uۄ:/N 3 O~ Uy[}kӝ-u.+!tM/odhQv &۳􌚼ph.RE]Ō>yx>ҩĵf6D]烗Kom9kvfv}AM90Qegݾ;rfܭ5뻇۬g}Q̍Ij_7f}Mz{ͫgپ䵆7mpu+0e繑6EСu[݌R*!9zqAWvvBB!R3"'M<5ї.h\Ҥ0\Lj\\4g1u6s7w~#fM믓{S6chn9tzϾhϕc)z?=7!xoy'ИK̽O 8_k/^>+!p4EMg&G>C_X?ᚓTsWóo Op) ,Iwm┄*}fZFT~߯f 8>yUBh&,9[v>ϰ3bL$cs7&5QIڥ7;o{e]ԙCL81g֜O~y뛿Mn"'`kݺ;D٫͋Ǟ(,Wr<̦z]0k+϶rkG9fi **/,hwN;f/a.ܰ8牑cOݍ\M z3(:ҧA/R` ە+6+ưItB85]TfUj^U_ ?(=~7BC"z[^ӎsVe+ٹi4~ک/>ۑyR QUwt#4FӬglw<[,6B*Kϕf76tIUzd}˪PUE9.[5kwXZ8jbR9:vHޚ*M_arZֵ2Zv-+-UJz9lh3Ν:]ݽbS<tӣ=Dy;[@v:~Yi82o؜nvkJ8Vgݻ*=},g ;. V:ne=Tk]͘e'džu Ke<^g1~$2p184H7lͥ(ZSn"{ޯ7)QSgeg6%ҲO֯_ԊAj˪ޣWV!FC[0[u̹ %=b~#xUš c>UV]ٹ2b G5HQQQRRޢup) k])!R,~--y$xTMiZs{ybYڰ@k0DTW2?bBۅϝ=uKt4|z{}KnOz[ܒ,}Ңp7{fIyJ˅m˒S.XנCHoiW4M]ivBX,VWmZsv!="\^V#K~B7!cR ._mYV!tpo6BhC;@:bPȷ+j,Mӄ)t}wOyHa?ײ,B[̜:͌REsk۞W51:n@cnw/^7kmNQ4WXc8~&7ELzn@Oiudב5ߑO`KWU !.mONͷ ϾO$ȇE >c|A㦍hZp(Bo{RUf<Ѕ8UI^MNvpt͂k%C4i5[>dccSziRH[3A/P9[}zDtB(k-ߝ#5kc{a Q|&w?Kj7|\=MxխܲKAK˷ JO;vCk,k*M_Ej =zZQ mPe9>DٽtKlOaKMNxgѧ5w]^gљUP,Ǯq5_WXNϚgJ*<~e֊IOӿͱRӤJݸfߍ-gSC~{̭u{߿^LYq2uW^W}o.x(m>?K5fʒ)uoݔ^~op^-0&8TGj^RH}>~؛',;Vڔή&ܪ7=31,؏j=_:|VE p螢Mpi&BYYM',Cg+yûSް *gZl\`Ԍξzj_۷~KԂQ^S7.Q*=7fLn+w t]1iڄ!LzM>l5wΘ؀p?AбMaƍ602C/Eϟ~gꁜ->K)}<-9d\{rム1'Vq0ѿ؝qR`B}噕~\) W&+++ۻ 4V^w\鷳E?zܐp -o\W]fAHnxi ڥL3bt-SZe-vfX3`6t\I7t\I7t\I7t\I7t\I7t\I7t\ p.ǫ cM:[Y ׭ع F5G鱪M8gvgo|zX۬Æ;d@d޸sЮԴ6'ܮ4_po^;(CܒzzcU˿%3zL 57H|6f jnC,;"]ؕ]I]jhF[֮9R??h/=z 0$vӇ\txQIiE@鋦eMY%#^qcb*XኽV0sWe*;&Ϋ 32~qrui~Eyko["}LTIzw|fܾr2o˶_ o β[ʒyxHY?8 ][lBC_85`Λ/Z5>_N7Tm}Mw׭KX5$ӣ=DIՇn޿\=f8SdQ̍IjwQ.Uoey4pQC=t3G>Qt]Ō>yx>ҩޝEy}?}aedU@Qv\PшѨ}jj,M?ms=omi6i$Mb(j(jDMTee>l ~.s}w~ߟ,h=s{4mg?Ʃ1uEiϮeXkfDEGz8Z+R_Y{ܚ!^?ɔYݾ(<$%bҩՙJEj5iHB1V<:XO1\NщPXOZ8Dw`OTԘwn鹌BxDN3;nt{%򩋗G\aY}~,;Ss6=4$-l?gfĿa1@ltržTnƙ6PN Y=p5IB cɠg"tYY;>qWʦƮv w[L7*Y6]h0TȌW㦸$,{Xanb(=x³3Yr?wտTlM 먯B8ʑx7g'r3U4En W:Úҿ|;kN2_]ōzᚄ/崸U0=.TkbҌcy742xͺIbcG;xI5>q3V_-47*|WkϱsͽXsOwBB@8hsͩ]wh'4Q KD_*^Ί o1B]llMmü_-(uO^o12ጁM$j1MF6w=%F$cϿUGj¯8}'j{η2*#|] ǾY Ds]^[3sqSHGѡ],5jf ]NޛRe|hՑ[TJcyljztBX[a{?qbwJ0ɑP|O֚$vvuHsK r3ݕbCHq#G]o>õy%@^CI* r*,,X_w'jJAI`l:uǡkǛtןk K6]gFZ"MgƯSMÍEE6<jMM7ONki9Ǵ6XD jOX"Pt~Ԯ$D,3m['\WԤ0<>iprga3&;(%ۇDN!<(%6 jj(Oqx&dx!{$|Czҡ7|rypRzHsnUȪJ^xpOpsnSO]5MAn;\T=[rll[?Xku_v!NC'SHە#=}VgUfo+ 6VbzKM1&|Mڿ~àRBUmFa57]hy)0 IDAT[01Nk7' ܽ|ua3xE#lhN CFw"P;_ͧ?b33~mC&juQ<=Q z#DTJި:dM|55OږRf 6h1 8_Zf/HۆsSۙv1L:w+*T(xЧARJXs©\!5dv8YOzet7˫L}BT9Y=-H* GLOE5 OX"I')A//u_q}m.= |VOJ\l٪;ߜW+;R+回?E/;¡)s]u-&K}ycd9545]t=R^~U{)n߅4̈VL*;{MK>mv=G=jYkޤLva eRjt\:ƨ" H/\ goN݁|r+ON2BL'RD1}Ebf,. 8mIՔ2,%"K-gKW;U\3=mN rW^}օOu ޶bJPbs|2}qVpwiJƮsPB"gDk+ җ.3QNe;Q>3D<M ܢ*jm^zo zrs**5)vc-_;ôrr~E!OSfoqOTeo/ -Ydagon7%nB3>_93i6r`+ zu%G:TE?Vf %1CݺaL\8x4cc߆inrZ&(9QςrWwyo哑?>0Ɨf$݄0m)37tZ&0w4߼qk=Nb}dT}*xypG;zǮAjH\| sG.VuH=1++g?j/[fx}kCU g2KZ,]2[}]mfݭ+Y'ȫ4U)ؒЩj|c<zvyq봸u?R_*q_ql)(SqszIIk}-Ccf+3D̜Rv\dw'_=X埒uZ= tS12Yu.۔KMgc"\<۸39GzuJO='Zdwm9^s3v@ܐeUUP^Z"鵟mKm呏;n-^첩qn Dy*t{>u^g4uEgw}'_Ǩ*x^j}O .{z"Nhs[|?Vu\Mp{ 櫬)i˻F3k^<3*|MQ(5$w!e }"e֒{(?X>;b&_]d>;zLZ+Xz\#E}Ӟ+WQ>rR/WJVw)3K9@%ustCN5Ʒ~gNʹQmda.ӥnzQ[`' f.;ךGa*C ޽zhÜW̱VsE8?7["!O]ŋmd11O69zJJ޹8`b\Ɂ?AT'8,uZ}cm(Ke —xkQ/^(IKKe=8ؚ(}BAOsͩ]wh'4Q KD_*^Ί :(.^nb Tf#Tx?~ibe>yS3ƸwuͿoGtKʁz#!KG{ z~,yn R,2͚* Pd 1e>&}7V_,, :wg6ON_uR# j|S(eޚ\8`GDbrהB%B//TS+/~% w׫iL+K-zu0ceGyKU5JFO[7#b6y-)wkxc606R*zwDij0:cF|gq%[Y/ α}S`<05-1&1K#rB+2[Ӳ{w"\$SLS剤j#QL_: NU}3rSKyŸ3_v,q7Ʒ ttWxNij|勧Sӯ BNb}dT}*xypG;zǮAj͎G>˷+r/ `%!mcu Oe |ùkx:nhN x5%,TlikCM{S=- 7|a}\r-G–} yn.# s̜lVSzJlen=sU_o GkmKԕd~ַ7~6n*Ml#TSّt\9ٯlVSu.L<jMqw -5N)}}_Op5`4L>DW[jf<`WFjaޟ0j_I7%i?q5`  }`|C nߐt7$0! I7oH`|C nߐt&|!j'gMJO}CtaKEvQ z8s{KmY3'hkb7$Tryi(O8.xՕޒK_nbca fo$ey7`*6{BHzG鶌N7Wz a.Lm D,6Z!0kK /=~ϯ[ >Br?.2|`d&PM>˟Yv? xGGSsgH xF齟ҍsKJZs˾Wr=Ѕg]s*2."w~==g}h1f6HNDŽIw:mʥ&3Uq.m\]yu#?:At-2zO컢붜{Y=pko?7aZcƈM3&1ilIWp(9ҍ&# zyy7mיe~ s 5V>aT-RUȼ`e=:[T3>ޓcT0_보:ȼcO|?Vu\MpZt&JM77EHBnh^,JOζ !DW/-uΡ2Ch/Cd:oa73H`ܥ)s)i"Ȉ6o ̹>*oXGY~FfFTtdBL-ENέ8o鮶2TQqz6Ȝ>X鿟~g~ˣЦ{h d&Rl3Cf˱Kcts8Aߚ8ۄJ- qW掖Φ.7}.+i~Sc-j+e7Ӿ?]@oԂ=r!\[~ҙg.7;{{9b8ַa,9Y17C=۞~R}v,ژDIX"tju&Bgh}ǯiMZm'!B);N%yAbLG*,St"w11T<=!nv|49ؓuϜ{X5LP"xyąמS?1jxADJ;7ߙv|bSm~q4%0fM7FHa ~Zw=6l/91f2 I7c "+Y}~,;mFА?¤*h3m(RGG{"l*~?[yA6Qjghܧ}ǠU(s 'D5b&lsOڙ|ɾOGDI{r%aӅ&aQzѹ #Lթ;x5nKL‚U&<)!ң;WΊm]k0!\wK3Tf]$|ޣY<ȸ@ u'Ϥ6OSR-Nٽ/5JkyG|r~(5k0!VZX IDAT7;|?dc@K.QSD>~}ljYé<5a&t6ؒ6mnz֓kg9I{ٙS*"k+WpaM__cI塏?LV)&X:1[xwS[|PٌyIG=Sv(Y"umnA gmc͑G8#w  XPݵC}rcLQ4Qt! )! 6t0לuz/JvNKJ%La j;+>n !Dvvm5B'Y[zpFؖo}qZ}O!|gԯoeT6-G3& [LY[vg/UiM0}c/iߚa &`4?Z":'cƒs*}~.Ė*R9`1MF6w=%F$cϿUG~ !u"}RXnf̨9m%-B@_"T2?T1]ޞҋڍfc{Cqz{t*"|e>_WZƈe|>T=?•#a*W7Jś_K\#D s̵w. s}"V/󑑎CNՌ04kVP]a̦P X‹/nn0;,vxX[EANNN^i[<+Τ9gG2CqΕ>Ma ~pU@Tc^]hRM \D|}W[.a8LΗ wXkbBgW灿K{W3ma^Nc]MrwRf(k{bgW1}c,:}8MOKȅPLxn{KmN9Di;kW1k#3 _~%z/"+wi4OAtwg+~*9̰m(PdSRB#{TBԵ+g:> k6|fRZ/2V8u?{>&"ҩ˅bŸ0m[j&Ý^5b rNBb?Rm曩_q{@[9k_Bg06U'Uyjҕ|6}i#3!n3Fa#ғૐkv5 Y_I ɼg= *yz󲩪Z=$5GF )_~B:r7A@rAYzpg\rp{ݮ_s][âgxhBSsy^~g|G֑X&Bm"~F+R'o? О că.hEZzH6ZҜ' >_iw(wRu誸 ?zK6571"RJeGJf!Rv/T*wF!kCxҶ2QNyZh 1)Z[K)a7.r|SO,PqXa.7q֜bʴ9#ÀrkI[>8xk4!bt?̠|?`TbE-zl}˿{:-&T0sGG'bnq`t7˫L}BT9Y=I#U)3UW6 kTPv@A>y9'W+*!Ty")HW$Fj9D.R@lJQy`0G %*R{F?־)3wQeP!J!!Tla`B8X,C,jR#{-*f1suR=_E=YB aƒs#vSb7;{H/e\dT=sW 8kf_y[n{\;s르V*Ib3tr,v ,[3ˆc׾;`A7(8Ve$"l~"M-* j_9emiY(&JM7a}_:nZWQk{_~[Ы6WAUI) 9w4mڹEuen}B~;omP/vǴ#4kphr/Lj ]I*LQrLޔpЋ|8ne|~\1щB'3lyh>83ܦ\`7j:_Nzzfͽo5E:4 V (׽1}۝̤0qʂ,5񺒣_*YoJuY^T#;\lѾ=ē_iC}I&h|<הyݍ_dqș0I7!Lw{[  7o\x:5ڭD.GFLէ:@ 7>|wk֌}o8|{oUǍԃc*w[:Vm#֗O=uT~GᦛDi.62U~CNj6t2B7)"j!Dj%^ʉ$ˇOF٫e"^PUr™̒Y{Q[ ͝.pt~m D(~OMG\9DMssӧ2Fv1`,P~8`Rˁ{QnH 0uf檌r0*tXU7gE0:tԬ=?pw˜p5`O7o I7oH`|C nߐt7$0! I7oH`|C n߄{KdpnZ.0Ԗ^nsW;sOFZ}Wݖ97!Z?˷1f6SPyϿct4Qn L]2ec}]^ r0y(Vx[ն)+Zim}rnB)xAOaPѠj4k0K%&)3])܋E bI3!\ꥥ"s9Tf-WyLU#ffҁЧLX##ڼ{/4[LX:FS k:KR}n[*u ^fRW~5 哂/ qWIAXU{.J 5b͌`_Gk[+rϝ:[Ӊ1Qn*)!ZY:ک&mkZVI8!ceΣ_t^*NщPXOZ8Dw`O圻{ alX!.d#.ĘH>q|f]ur_퓼sv{*^t{L<3 __ՠS?1jxADJ;7ߙv|bK&J, .4 ҋuXaNydƫqS\bwD07qO 1yv}S+'{sB)Ga<8>,&I%4뚪ӎȩ[~"G9ҕS"ฑt89+':cMfoѓ]}_v_-0{!kzlZptTsm UY5Il9hVq}O8'2.16~fşftlEBJᠽF5vAD%,}w;SF$7]At{b"Pxy{{*&' O p 9aJ蒟g2bdfmuc[,Q>\Icfh*ڿӝ{?YjB5r >lh?GJ D:g`ZͶ>_;N_ !lʦHt0nLĚ1מu5S=-8utuf{CR5+f(r̦"d XB:#շ )G !]ִW6Ŷpy<S*Ά&Gx~ky'p"^zFc͆#|}~vIw:%! hJE07&?A|lrIΌDZό_ ɻ3˚*ҿs-7.Uuh/:N7*BݎVac9JLeWw̐'>gt8WPvhiT"SBLWmgprga3&;(%۵"k'rI7#{n7' x#W!g'74VZ$d#&\wlYk^faG@zjpdcdgDg!0V_qų2t~Y,R7/ I3fڒ%ZFRBqlKe}u ^E=D^av'}U:aO`0*{R)s7YjM eǓ%*puB͘ΠT&JEnJA a?.:r'Ttg9L70BB7U*jU *P\}}+dJ4QjUYS>$T#TU^ݧڄ' RCzSL\}V_K/7MOp9_9Rߚ§(*ǾB|ozj[ DzZ:{ytIg8BxAO송g]hoqقC]Ù̈VL*{T>mv=G sHjrO ńnt&QE@3^5^((ߚݻ7gJ%1V؝Ze"*O$VbH͘\2DxK]'kEbg2-$CD/ Ov[hnD̙7lZ䪖pP-8vSu^5g" =]+[9ψ˽$P~= %WJ cW9J(!P3"ٵwOgڪfpKMKּk0444T?iܦnZ]0^vӏ+)=ښRSձ9\~gE^q:ôĶ.Mښign5UGrW,v/V第Wɷ3y!\wOԏeocsd9o2zO!Qbn/3{JE.=^rʘUjG)6?{fIh[m=,;,2Hڦ'zwZYs2izCyw}Qt9 \.兙G~_:s.כr\&qvlXƥ^VTVVrVynھdZ͈w\;q U5LicRHE])6k||p5Ƈ T ?JO7X! I7oH?{ו~S9dٲd[%=sOǹ=3w}oagyۡnlٲ-K( @D: UEǢj׮uua:0 s*0 s*0 s*0 s*0 s*0 s*0ig:U4I]O*?,`"U “0voi=ZeQhbk;j]orfg,͑2ꈙTۭ0G|yhlһ=x%H]!Pl]Jt9LJlRfMz5_vn/tq!""nw'1Qw=5 snQFnȔ44̭vQ}`nd\3WBDrC-u~>2|΍'ኙ_1=ؗ<'v񥶙h3n]%`"^rr1ed(Qw+W|nQ(HDnDm&.- >3$1Z}MCWvYGޢ;]1$KF)Lڛ/iVq'%/ &T_.qi"b$5^B]K=]X%WY gߡjM|IwԇRhXyv!5yvbm(u\ا['}S, }+Ö"%'![H@M,|ctRT7Ƒ2X~1n IDAT4z `JJC})c{I% ·OkD3%a\ۓJWOqJ*Q;L{Z! Dֻө1j"&Ҹz>~x̘k.CnqMDhHBaZjI^R>Wտ;F委\5@$C΄m?|=kc0`|^bn#&abm0{{٫BVOmZU UXLD6>>EDwoHDf>T"F&cTr*:,K[ӑ򉉥oDVoertBDdH5R]CEwZGd5@av'4Myf (uT7n7YxX:Glr|p] 7eHB) C?opLn7%H!k,?yꉉH 4KtIX8),a;ɪH#"R#Gc!W,Xw҆cOKgf%DH?\,~zZCMin2+suxD=TCQ:b/WN6!"U;\ κ?hy}-7y)MWW7'¢7tmJ7+0oǥL;fɶŤgb tbJrx!!uɸE L^u)\>R2m.(gÉ5*Â4SQnPlO>G6K)9cPB W }Jg>`%Λ+h]䏮rΧ$t= l9g-ȾzinpPxW<$n4ߑ,͐@ҫd 'TI]^ I _rSI 5\rkQjS$=IF Ct_)ݫwA-!s5+\TMs79a,`?rגk2_s=m4C"Hq@<*]WKwZg723J7mt܆J7mt܆J7mt܆J7mt܆J7mt܆J7mt܆J7L0!?poӄl`txy2RJ~\SlFƨ;ٺ(čV~aÙH[6/=0+W|JWŏL5kIWuQQu>8b^hTJvJqe6e6g: 9JTmtV`mƗLݐlO&XkB}{nq*o?y O<ÿT86pPmw #%:bǃ,_/s=yiI'XT}aj\y;Z+>m+ukǻQOg&+0([ÙG&l_6ܾ2L0 Fӭ-`V]n0mbj-.6+OkvongØUvR(Y& U-8mkzСU54?mQ?hb 2UO?@j7E-CގCW[cغZo:z2ə =t# zX;[o:r8417s?]Eڪ+[Md:_}٘UU~klZ.]MUEG>:V8ʮ`]@uRC|ZGN^s]/&ծBfו:@CR_݊_oW;@i-)TbBs94aaa6۰61DKX"s[gCM]u* cNpIjU|C%Ե ܪz-h-[#V|3o>(:ۨ*}"g-I1V_)[t|fN|{۞mkfV4zwPEKS JL}Oim%H3j^Rlx|CQN{U=▭+7bx^⢬keH [''ݴZ0Z&zC'}9,(+\YrU)A - +GΕw;Ҿ0:@Gn$= ٙg1Įʎ[]G>Ft[LeO(ޡC&JO`U0kvd/o'dދd(qev)mڬgvaaNb lLmrj_w+<!"fE#&-CWn}U/dZQ_UQz+DDRZP6*W J-ڰasfǨ==*):f蛵O0έWY3oɒlHIb+0 /; /Un"ŗJ-JH@i] вXn_i+aMXDtoTǗ\Ѧát&yK\yGvU%Վ UqcnNczۻM?[a{*Jʺ  -LЍƶʳg+f/Mr#""HI1^jdshϣX獵1R;xZR7e:@ga_Z;DZ{Ȃk׭Ό0t`WbUͪ1%oĠ'bnYJD$Sϕ6yKIXzbyewg,-m=C&ggЅ`L\Va W߿iMFB]ءݲM`a)[1_Y83962480C($.cIƻϿpzH vRy,?d$Xڮ9DDm1=ӁqΥg xn3rњMVz+'}t,cnS7xʴ1mX?Tv! jʭ{&nޜr^Rtn2Յ1ZV/pŽ"C),rgcBZw$'rClL>u3j6W519oeJOXSj iwhJ0?YURZmge{"ig>2&T+왊bҬVnb:=LCZOw"XwLuKֳoer[[SPCP7ߙ3ٰ/ϓ>&cJ7Ft+~(bO%zO[}M* W n 6VEѱac? -KOm_IZQ?9Owq ?wd+JlVVYY$֩t5~˛3"|&ָFfn}8 IWyY( + Z3|/*/\0QgYg"gĢG#Ó\wi`sk*{,KZEk _w{t2ZyY%%hnwuܖ҂+ºĕKC584pɆhy Mҕ:+WL0덶[;uiWuO!i>{W췞YqkOtw y0tVm:ޙW|eWF'""MKrg\LwQզF0H>;t^{絣_ _AϘkNbۨ!O*y*'mӶzƞ~rrذcuAát"\piΉ4]ؚ#d-u1Z[LvO0uzҮ[wmA>jnmyOlqA;}GXى}+GG_׮[?/۠eTٵGϗQfC@'XJ \Z:wnZnS]w+ ò!7oJWnX5?*Ǡu77ܺ^p؉uӹWg'3j{{>/~zi>H;pφ\/6Mg'{ʧP,L0]8++lfFDk:B|;,ґ#PQ6xhϚhڍtdm}]t8b>0_Fv0sZ&"Nڝ_{r?%O~u$?m{M2Z͔̬X-K弒fJI{/*?yg:qGН9t1JHVvUSA^PѴ)>O!RQ2* `nC6T`nC6T`nC6T`nC6T`nC6T`nC6T`nӎB+Qszi[[DCvsL)Ks$&:b&""v+f8Y(pR!hڰEC.~e{LQ+ ^e&®yJH2EMԷW2qk(}_7hח<+f~fp*S})̃Hl|b_jU+գnfp*݊3$1Z}MCW V>f]7?n",Un>S_I.@qR&fT|R^ rj q">)ڬLnCl"aw3xSD%Kj.8oПHc%%HTkLjs0cWk|B$> 'ٙB)H%Y'` :KۉzjI+D$1Js &X'BDp)qa"?Y 5%C jhvL~!M)8|຋`Mas_LTjm6+y1Sbx?(۷eyHvO*xT'4QcyH$yMHG≙b%:7ÃGX DD>yM*nV4rz_"1jKݎq.!_3u"bC<iO3I@3Q{!?6r) =YCn+JڪqIۭ ͛.\2(ōꋹ*=(;B4LFQ /7=׳tz #&+ݬ@.eq MZ)S? 583x`de)L$T|k{&Z˧Kڨ4߶cm&jlFjl&J-6Rc#wd6.By_WԹ IDATBwzÇq6-w2"bJH=)HG{2c#"!"'Gў2Ʌ4#zVʇr9p芃FDDBmCJ:<{` Qw9-\DDdkAqH; N;;t|/|Pyq%"3{+Fnw:BmChmtD6on#1Qjnfn2ѝNDUnI_ qFfp o4L|R&fĴ|R^f*-R Lr)}pE6G+xP)nwEQ'PF oW(&"+u9r>U&Ia 7hy[_k!n2?KK(>Q4L$T[->/S7+L'eg6%OZeaN2dVT\TIAQ&`YEݚ>tr1E%$I_/Eg~=сtӭ*FY.N{7aS٘Hp1ރןb%9)jDqŠ}7ro mEGRBDdbI=Rfx=̘I2fZb"Ʌ84x4ϓU–H1VK|F2_᳒uDOM@>yfcY:K \f!"bzd$D訾%%D&>)NNF:&yZ>)g=g0n^roouH, ʱ2HV_v93x$Srl *u7PJDJVTJ;1&+擦- C:䒪& `JZ/;v; C:II%AM!T"8b" DdIq.uM;R]Ygi8=nY+ʖl`{I56Q+DD|3])2Q[y>MuRFrN Ԑ[IҪ\X'+ÈJDi4ú3٨~%#| }a4mg:^;fp ;]N KxF5RtDꈈfj:ą5&4"tEܘ8:&DmjB+umg4\vgi8SBjtф'0GIFF |5]$DHe\>R2#GS.MbV]5.Җ%2{gQ1R(i;))" }U?Kԭ4lDl k("UVeR!&3冑^su192Iq2uBOs𥀀/sqrnQK_k9c!4n?[zOs7 PF4,1uIi>!@g{瓂&?&s/z㊓n]򧿲=3X''=r1J''GgZhiofކg*jSGop |c<ԭߝ2Mzo. oLvfvଜX-MnX`͊Pc[EZF[uXH0}fHZfmN ,1uIf鸲ov%68,?}:;sWO9խ=k%mq?Ȏn'm7"LVދI[b<-٪?~CU VZ.|Yz3T͊V0l *V.斏խF 3U06*J. yźUY)!>F\wz#'5ٞo )ɍ=ɾVo(5Fuƭ'uS;LrXqulXoʢs?8 _/}fĈ o7K/=zB(;Z V]'kgKCՍg|5= ׮_mkR$ԑWP}C0Ud:?J7{کGޣMO5˼W=A\zjߛ "hnٴ4%]r|tq6Y¡A3]{~1ZpƓ|vI0wP"y2 jt\gJ;6kgc{C#uydC ֕zdyk]GVacJF~`_zv-Tt؟_/Vj`0)eCX:΍ՆO_}fizt+W&#tn8W{.Hn,9VaD8B]xdґ`ٛ!j*˻Xe`^:}MX,'dЂwKNQvkFV+D]mN!TVԩOoK|,D=^ޝ&O;tTp(f'`Mĩ֟XYUl?8W?iXj}ʺHuεRWN:Z=V_wD3~~o=9;czѪbgN C8 s|K=/=wiY;M7}ia>׆'˗׳˳ȅqx-^`d.ʽ/\Hv^E'qp4 GN'=݊fmOK]JxTr#oz%ԒUcզ+Cvl⢺b# PPBK!vzri}ho~ WˇVs?<@wT7&I[eQPG%SהLV{zT"Rt: Ѡ"-e>->\r:~ƐyKDyFDĆ4/ͼxEAWcQVS!kC:/Z-҇(d/)P}|[{?(JYno}ǒb6XubҖDux7g@Dr@e9^~ح.^E ۆ.0ޓk*.q,]a绳t\-C9/?>fvZB'J}X'*{ۺz[zI{[ТtuviId7w7"twMWۡlmxC:J?}t1,5'{AloB>UE |t:*ϞXHl`ҤʋD쑚bd1_Ͻ4jtћg@zNh_Fuߦ6ˁLNx F,>I.W?Տ?$bOX6$?mukuj]xMws8)Syrr=ˡl8xa-ױr)<ةag=@ؽ66 ptK~Ƶ kYZ;5o0~2vmIqJK޾AOd,HTK[Ceٵ+m^Ƽ%Ǥyg,K=P|Ӳ绳tUyd9\w3 QEHD&Xjdt_`FW\uk:r6d.:1y@gwjFy̮2cɮg{腱8Jjܲw5v> &eWa6'l$/j+>;'Ɓb/[ck.;hqfrldhp`;$~QH\ƒwX]>}e)2}O+9 :ųs,L;iy%:v0Ш`7""ևEz2MͭD' $"'ff 1j>vsrpHDD0 5.ܚUFѵ1Ea|~*2FaNT?_p,v P|>*l#);|C@Ԃ[Lܼ9#弡KN//8.+#X]erލXuYp&wӾƹ,{/b6VTruinn2jFw&s[ƕrm{n[xk١l8ta$-^kTb}dL舥m<{J䴴H wFOczyqcKtvpl!9qnV,6-2N}yoEeKǻ_In߹o)QO??>5"d[W-){c%uJ/G?xVUwzL<.dz1ԄF~]ty9 w~Č`N ֆ}a6ܪ'.J[){/JԳj*vIwC}J?8J@|Z\UB]2S=HO pUj álWUQI HˌE Xyڵko&KwM$6_(h4qF,{t /[(BD@1*&m%Q:Vo_t? #tLL3uwIũshfbvK罒׿IھҽGV͐0o2rj̵|Re%ϴm/ pb* ~kWN)+S,jg,wcSU.g -^ijmߙ8V_S ',LVF$&%%yR|yݺ7+jjZl22qIҸ@? : [U~^Y(7lLnk]@ Kݕy7 BlE|^$I} cfܔ1;*vb̪X;a3&'c zEd~xF:ȕD[Vt}ꘛn+udI=XY-Nݘ;uj̮Qݤ\ IDATm5c %睸&SW>zvtЭc,_ʈk/Y2HS" A.Gt?Xc|"ӟ}zilA膅N^1~v`a-)HO,Sݰܸ0T#uqua+Z~!pܶ !a3' Q4ed7M .W/5CWy 05q,: Jݼ;2h ʒ$ qOߨֆxܠ-:>nTjd^3C5U{.ۑLZT'^I6!dYeI?w%$:G& `+)6U(E40{z17xi w齼BR;܏vF=4)Iv֦n7c{U-Wda_m=TlƬhߵ{sdc̒[|uZgo H8VT,á}]6GlV($ v5ۍBISȪ(Zfkn'MH(Kv䂈9q q1{v s3 ,5V]돥2{i/`Ƽ;(0VMݷO7~wOGm={|KվwoZtۂmo:k?ܼbRд忞ܮR_FW6fθ_ƿ_~:';?y݌q)3S쟱_]w}5'KUggރ*fC(Mŵx# ɹ]ŝ_uhs@[pa&$QodlZZ+>{_;E֥yq# KG3ysKszrniJ]01lln<]9-DѵhÆiUfC5n{49uִzLUڶR4,\l?<0\V/TSԪY ^2cjtX0_lt^k(+1x;"_Dbѓ+D{w6/KK bmo9]n PwRn/7O#{[3ݗY3R09⎶¢湹zǬvU5mkv_6ggv}]SK,#{\#ZtU'̉k/9rOhFƍᯓ5T7( o_.KZ0;vBHPsXӡ}9Mzv=lHڍQ 6&ʶss'wQv~[eg5IcW~S少dh6.lkb|Nnx>TRF.ˣ_S';׳// .g=Un` M#҂Rbn2MTB|"5 %hqV' t ``&$NTDłiQnஔc|F5z>w (p#tKލC-[8_鹑~~\ n$F n$F n$F n$F n$F n$F nn%tKCJ=W S$_|*7++vx'hBCCVZoȘacg͙隷jbg;_wax1lT>Ri;enI$ai={p~Y3\3H^vǤ֝-:_Zo  ;kҺΟH^GKXZʋ矪ED7:r=W|'$p/SL4=ObnB 8m~衊$@\1-V#槥%M Ms9PTny IfؘŋN }&ў+On%b /- Q3Ip[(rCN%ў+ h4ͷ>(4v8D{i[lq833ӭ/`MNs8A/$F n$F n$F nh4= \$I0pw4IENDB`riscemu-2.2.5/docs/debugging.md000066400000000000000000000057131451200553000163750ustar00rootroot00000000000000# Using the debugger You are launched into the debugger either by an `ebreak/sbreak` instruction, or when an exception occurs while running executing instructions. Consider the example program `examples/fibs.asm`: ```asm riscv-asm .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} 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 ebreak ; launch debugger ; exit gracefully addi a0, zero, 0 addi a7, zero, 93 scall ; exit with code 0 ``` This calculates the fibonacci sequence and stores it in memory at `fibs`. before and after it calculated all fibonacci numbers, it uses the `ebreak` instruction to open the debugger. Let's run it and see what happens: ``` > python -m riscemu examples/fibs.asm [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, and most common tasks should have helper methods for them. **Available objects are:** * `mem`: (aka `mmu` or `memory`) * `dump(address, fmt='hex', max_rows=10, group=4, bytes_per_row=16, all=False`: Dumps the memory at `address`, in at most `max_rows` rows, each containing `bytes_per_row` bytes grouped into groups of `group` bytes. They can be printed as: * `hex`: hexadecimal, unsigned * `int`: converted to integers * `uint`: converted to unsigned integers * `symbol(name)`: Lookup all symbols named `name` * `reg`: (aka `regs` or `registers`) * `dump(full=False)` dumps all integer registers (unless `all` is true, then all registers are printed) * `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. **Available helpers are:** * `dump(regs | addr)` dumps either registers or memory address * `cont(verbose=False)` continue execution (verbose prints each executed instruction) * `step()` run the next instruction * `ins()` get current instruction (this reference is mutable, if you want to edit your code on the fly) * `run_ins(name, *args)` Run an instruction in the current context. Symbols, jumping, etc are supported! Example: ![debugging the fibs program](debug-session.png) riscemu-2.2.5/docs/internal-structure.md000066400000000000000000000030161451200553000203060ustar00rootroot00000000000000# 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 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 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 construct a `LoadedExecutable`, which represents the actual memory regions the executable contains (and some meta information such as symbols). * You can load as many executables as you want into memory. If you want to run one, you pass it to `run_loaded(loaded_bin)` method of the cpu. You shouldn't have to do this manually, as the `riscemu/__main__.py` has all the necessary code. ## Instruction sets Each instruction set is in a separate file in `riscemu/instructions/`. All instruction sets have to inherit from the `InstructionSet` class that sets up all the relevant helpers and loading code. Creating a cpu with certain instruction sets is done by passing the CPU constructor a list of instruction set classes: ``` cpu = CPU(config, [RV32I, RV32M]) ``` riscemu-2.2.5/docs/libraries.md000066400000000000000000000014551451200553000164150ustar00rootroot00000000000000# Included libraries I've started to implement some sort of standard library, following closely to [GNU's glibc](https://www.gnu.org/software/libc/). You can include the libraries by adding them as arguments (before your main assembly file): ``` > python3 -m riscemu examples/lib/libstring.asm example.asm [MMU] Successfully loaded: LoadedExecutable[examples/lib/libstring.asm](base=0x00000100, size=64bytes, sections=text, run_ptr=0x00000100) [MMU] Successfully loaded: LoadedExecutable[example.asm](base=0x00000140, size=168bytes, sections=data text, run_ptr=0x000001D0) [CPU] Allocated 524288 bytes of stack [CPU] Started running from 0x000001D0 (example.asm) ``` These libraries are no where near a stable state, so documentation will be scarce. Your best bet would be to `grep` for functionality. Sorry! riscemu-2.2.5/docs/syscalls.md000066400000000000000000000034461451200553000163000ustar00rootroot00000000000000# Syscalls Performing a syscall is quite simple: ```risc-v asm ; set syscall code: addi a7, zero, 93 ; or SCALL_EXIT if syscall symbols are mapped ; set syscall args: addi a0, zero, 1 ; exit with code 1 ; invode syscall handler scall ``` The global symbols (e.g. `SCALL_READ`) are loaded by default. If you specify the option `no_syscall_symbols`, they will be omitted. ## Read (63) `SCALL_READ` * `a0`: source file descriptor * `a1`: addr at which to write the input * `a2`: number of bytes to read (at most) * `return in a0`: number of bytes read or -1 ## Write (64) `SCALL_WRITE` * `a0`: target file descriptor * `a1`: addr at which the data to be written is located * `a2`: number of bytes to write * `return in a0`: number of bytes written or -1 ## Exit (93) `SCALL_EXIT` * `a0`: exit code ## Open (1024) `SCALL_OPEN` * `a0`: open mode: - `0`: read - `1`: write (truncate) - `2`: read/write (no truncate) - `3`: only create - `4`: append * `a1`: addr where path is stored * `a2`: length of path * `return in a0`: file descriptor of opened file or -1 Requires flag `--scall-fs` to be set to True ## Close (1025) `SCALL_CLOSE` * `a0`: file descriptor to close * `return in a0`: 0 if closed correctly or -1 # 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. riscemu-2.2.5/examples/000077500000000000000000000000001451200553000150005ustar00rootroot00000000000000riscemu-2.2.5/examples/estimate-cpu-freq.asm000066400000000000000000000072411451200553000210410ustar00rootroot00000000000000.data // 16 words of data for benchmarking loads/stores buf0: .word 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 .text .globl main main: mv s5, ra // warmup printf "warmup" li a0, 1000 jal nop_loop la a1, nop_loop li a0, 1000 printf "Measuring nops:" jal measure_loop li a0, 10000 jal measure_loop la a1, arith_loop li a0, 1000 printf "Measuring addi:" jal measure_loop li a0, 10000 jal measure_loop la a1, memory_loop la a2, buf0 li a0, 1000 printf "Measuring load/stores:" jal measure_loop li a0, 10000 jal measure_loop mv ra, s5 ret // rtclock tickrate is 32768 // execute bench at addr a1, a0 times measure_loop: mv s4, ra csrrs s0, zero, cycle csrrs s2, zero, time jalr ra, a1, 0 csrrs s1, zero, cycle csrrs s3, zero, time sub s0, s1, s0 sub s1, s3, s2 fcvt.s.w ft0, s1 li t1, 32768 // cpu tickrate fcvt.s.w ft1, t1 fdiv.s ft0, ft0, ft1 // ft0 = seconds of execution time fcvt.s.w ft1, s0 // ft1 = number of ins executed fdiv.s ft2, ft1, ft0 // ft2 = ins/second li t0, 1000 fcvt.s.w ft1, t0 // ft1 = 1k fdiv.s ft2, ft2, ft1 // ft2 = kins/sec printf "executed {} instructions in {:.4f32} seconds ({:.2f32}ki/s)", s0, ft0, ft2 mv ra, s4 ret // first loop, executes a0*32 nop + a0 addi + a0 beq instructions (a0 > 1) nop_loop: nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop nop addi a0, a0, -1 blt zero, a0, nop_loop ret // second loop, executes a0*16 load/store pairs + a0 addi + a0 beq instructions (a0 > 1) memory_loop: lw t0, 0(a2) sw t0, 0(a2) lw t0, 4(a2) sw t0, 4(a2) lw t0, 8(a2) sw t0, 8(a2) lw t0, 12(a2) sw t0, 12(a2) lw t0, 16(a2) sw t0, 16(a2) lw t0, 20(a2) sw t0, 20(a2) lw t0, 24(a2) sw t0, 24(a2) lw t0, 28(a2) sw t0, 28(a2) lw t0, 32(a2) sw t0, 32(a2) lw t0, 36(a2) sw t0, 36(a2) lw t0, 40(a2) sw t0, 40(a2) lw t0, 44(a2) sw t0, 44(a2) lw t0, 48(a2) sw t0, 48(a2) lw t0, 52(a2) sw t0, 52(a2) lw t0, 56(a2) sw t0, 56(a2) lw t0, 60(a2) sw t0, 60(a2) addi a0, a0, -1 bge a0, zero, nop_loop ret // third loop, executes a0*32 addi + a0 addi + a0 beq instructions (a0 > 1) arith_loop: addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi t0, a0, 1234 addi a0, a0, -1 blt zero, a0, nop_loop ret riscemu-2.2.5/examples/fibs.asm000066400000000000000000000015611451200553000164300ustar00rootroot00000000000000// Example program (c) by Anton Lydike // this calculates the fibonacci sequence and stores it in ram .data fibs: .space 56 .text // make main global so it can be picked up by the crt0.s .globl main 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} 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 addi a0, zero, 0 addi a7, zero, 93 scall // exit with code 0 riscemu-2.2.5/examples/hello-world.asm000066400000000000000000000007661451200553000177430ustar00rootroot00000000000000; hello-world.asm ; print "hello world" to stdout and exit .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 riscemu-2.2.5/examples/malloc.asm000066400000000000000000000022121451200553000167460ustar00rootroot00000000000000// 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 riscemu-2.2.5/examples/mul-float.asm000066400000000000000000000004031451200553000173770ustar00rootroot00000000000000main: 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 riscemu-2.2.5/examples/snitch_simple.asm000066400000000000000000000013641451200553000203470ustar00rootroot00000000000000.data vec0: .word 0x0, 0x3f800000, 0x40000000, 0x40400000, 0x40800000, 0x40a00000, 0x40c00000, 0x40e00000, 0x41000000, 0x41100000 vec1: .word 0x0, 0x3f800000, 0x40000000, 0x40400000, 0x40800000, 0x40a00000, 0x40c00000, 0x40e00000, 0x41000000, 0x41100000 dest: .space 40 .text .globl main main: // ssr config ssr.configure 0, 10, 4 ssr.configure 1, 10, 4 ssr.configure 2, 10, 4 la a0, vec0 ssr.read a0, 0, 0 la a0, vec1 ssr.read a0, 1, 0 la a0, dest ssr.write a0, 2, 0 ssr.enable // set up loop li a0, 10 loop: fadd.s ft2, ft0, ft1 addi a0, a0, -1 bne a0, zero, loop // end of loop: ssr.disable ret riscemu-2.2.5/examples/static-data.asm000066400000000000000000000006431451200553000177030ustar00rootroot00000000000000.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 riscemu-2.2.5/generate-docs.sh000077500000000000000000000014621451200553000162440ustar00rootroot00000000000000#!/usr/bin/env bash # super hacky script to generate documentation when developing and for readthedocs echo "Generating docs!" if ! command -v 'sphinx-apidoc'; then source venv/bin/activate pip install -r sphinx-docs/requirements.txt fi if [[ $1 == 'generate' ]]; then # delete old help folder rm -rf help cp -r ../../docs help PYTHONPATH=../../ sphinx-apidoc -e -f -o . ../../riscemu ../../riscemu/colors.py ../../riscemu/__main__.py echo "only generating, not building..." rm ./modules.rst exit 0 fi # delete old help folder rm -rf sphinx-docs/source/help cp -r docs sphinx-docs/source/help PYTHONPATH=. sphinx-apidoc -e -f -o sphinx-docs/source riscemu riscemu/colors.py riscemu/__main__.py rm sphinx-docs/source/modules.rst cd sphinx-docs make html # xdg-open build/html/index.html riscemu-2.2.5/poetry.lock000066400000000000000000000657461451200553000154000ustar00rootroot00000000000000# This file is automatically @generated by Poetry 1.4.2 and should not be changed by hand. [[package]] name = "black" version = "23.9.1" description = "The uncompromising code formatter." category = "dev" optional = false python-versions = ">=3.8" files = [ {file = "black-23.9.1-cp310-cp310-macosx_10_16_arm64.whl", hash = "sha256:d6bc09188020c9ac2555a498949401ab35bb6bf76d4e0f8ee251694664df6301"}, {file = "black-23.9.1-cp310-cp310-macosx_10_16_universal2.whl", hash = "sha256:13ef033794029b85dfea8032c9d3b92b42b526f1ff4bf13b2182ce4e917f5100"}, {file = "black-23.9.1-cp310-cp310-macosx_10_16_x86_64.whl", hash = "sha256:75a2dc41b183d4872d3a500d2b9c9016e67ed95738a3624f4751a0cb4818fe71"}, {file = "black-23.9.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:13a2e4a93bb8ca74a749b6974925c27219bb3df4d42fc45e948a5d9feb5122b7"}, {file = "black-23.9.1-cp310-cp310-win_amd64.whl", hash = "sha256:adc3e4442eef57f99b5590b245a328aad19c99552e0bdc7f0b04db6656debd80"}, {file = "black-23.9.1-cp311-cp311-macosx_10_16_arm64.whl", hash = "sha256:8431445bf62d2a914b541da7ab3e2b4f3bc052d2ccbf157ebad18ea126efb91f"}, {file = "black-23.9.1-cp311-cp311-macosx_10_16_universal2.whl", hash = "sha256:8fc1ddcf83f996247505db6b715294eba56ea9372e107fd54963c7553f2b6dfe"}, {file = "black-23.9.1-cp311-cp311-macosx_10_16_x86_64.whl", hash = "sha256:7d30ec46de88091e4316b17ae58bbbfc12b2de05e069030f6b747dfc649ad186"}, {file = "black-23.9.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:031e8c69f3d3b09e1aa471a926a1eeb0b9071f80b17689a655f7885ac9325a6f"}, {file = "black-23.9.1-cp311-cp311-win_amd64.whl", hash = "sha256:538efb451cd50f43aba394e9ec7ad55a37598faae3348d723b59ea8e91616300"}, {file = "black-23.9.1-cp38-cp38-macosx_10_16_arm64.whl", hash = "sha256:638619a559280de0c2aa4d76f504891c9860bb8fa214267358f0a20f27c12948"}, {file = "black-23.9.1-cp38-cp38-macosx_10_16_universal2.whl", hash = "sha256:a732b82747235e0542c03bf352c126052c0fbc458d8a239a94701175b17d4855"}, {file = "black-23.9.1-cp38-cp38-macosx_10_16_x86_64.whl", hash = "sha256:cf3a4d00e4cdb6734b64bf23cd4341421e8953615cba6b3670453737a72ec204"}, {file = "black-23.9.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:cf99f3de8b3273a8317681d8194ea222f10e0133a24a7548c73ce44ea1679377"}, {file = "black-23.9.1-cp38-cp38-win_amd64.whl", hash = "sha256:14f04c990259576acd093871e7e9b14918eb28f1866f91968ff5524293f9c573"}, {file = "black-23.9.1-cp39-cp39-macosx_10_16_arm64.whl", hash = "sha256:c619f063c2d68f19b2d7270f4cf3192cb81c9ec5bc5ba02df91471d0b88c4c5c"}, {file = "black-23.9.1-cp39-cp39-macosx_10_16_universal2.whl", hash = "sha256:6a3b50e4b93f43b34a9d3ef00d9b6728b4a722c997c99ab09102fd5efdb88325"}, {file = "black-23.9.1-cp39-cp39-macosx_10_16_x86_64.whl", hash = "sha256:c46767e8df1b7beefb0899c4a95fb43058fa8500b6db144f4ff3ca38eb2f6393"}, {file = "black-23.9.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:50254ebfa56aa46a9fdd5d651f9637485068a1adf42270148cd101cdf56e0ad9"}, {file = "black-23.9.1-cp39-cp39-win_amd64.whl", hash = "sha256:403397c033adbc45c2bd41747da1f7fc7eaa44efbee256b53842470d4ac5a70f"}, {file = "black-23.9.1-py3-none-any.whl", hash = "sha256:6ccd59584cc834b6d127628713e4b6b968e5f79572da66284532525a042549f9"}, {file = "black-23.9.1.tar.gz", hash = "sha256:24b6b3ff5c6d9ea08a8888f6977eae858e1f340d7260cf56d70a49823236b62d"}, ] [package.dependencies] click = ">=8.0.0" mypy-extensions = ">=0.4.3" packaging = ">=22.0" pathspec = ">=0.9.0" platformdirs = ">=2" tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} [package.extras] colorama = ["colorama (>=0.4.3)"] d = ["aiohttp (>=3.7.4)"] jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] uvloop = ["uvloop (>=0.15.2)"] [[package]] name = "cfgv" version = "3.4.0" description = "Validate configuration and produce human readable error messages." category = "dev" optional = false python-versions = ">=3.8" files = [ {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, ] [[package]] name = "click" version = "8.1.7" description = "Composable command line interface toolkit" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, ] [package.dependencies] colorama = {version = "*", markers = "platform_system == \"Windows\""} [[package]] name = "colorama" version = "0.4.6" description = "Cross-platform colored terminal text." category = "dev" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" files = [ {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, ] [[package]] name = "distlib" version = "0.3.7" description = "Distribution utilities" category = "dev" optional = false python-versions = "*" files = [ {file = "distlib-0.3.7-py2.py3-none-any.whl", hash = "sha256:2e24928bc811348f0feb63014e97aaae3037f2cf48712d51ae61df7fd6075057"}, {file = "distlib-0.3.7.tar.gz", hash = "sha256:9dafe54b34a028eafd95039d5e5d4851a13734540f1331060d31c9916e7147a8"}, ] [[package]] name = "exceptiongroup" version = "1.1.3" description = "Backport of PEP 654 (exception groups)" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "exceptiongroup-1.1.3-py3-none-any.whl", hash = "sha256:343280667a4585d195ca1cf9cef84a4e178c4b6cf2274caef9859782b567d5e3"}, {file = "exceptiongroup-1.1.3.tar.gz", hash = "sha256:097acd85d473d75af5bb98e41b61ff7fe35efe6675e4f9370ec6ec5126d160e9"}, ] [package.extras] test = ["pytest (>=6)"] [[package]] name = "filecheck" version = "0.0.23" description = "Python port of LLVM's FileCheck, flexible pattern matching file verifier" category = "dev" optional = false python-versions = ">=3.6.2,<4.0" files = [ {file = "filecheck-0.0.23-py3-none-any.whl", hash = "sha256:cc1dc3fc2fc682ccd059b0d535606d32235613a32c018211d93aa6a99047ceb2"}, {file = "filecheck-0.0.23.tar.gz", hash = "sha256:1c5db511fb7b5a32e1e24736479cfe754ea27c9ae0d5b6d52c0af132c8db3e7d"}, ] [[package]] name = "filelock" version = "3.12.4" description = "A platform independent file lock." category = "dev" optional = false python-versions = ">=3.8" files = [ {file = "filelock-3.12.4-py3-none-any.whl", hash = "sha256:08c21d87ded6e2b9da6728c3dff51baf1dcecf973b768ef35bcbc3447edb9ad4"}, {file = "filelock-3.12.4.tar.gz", hash = "sha256:2e6f249f1f3654291606e046b09f1fd5eac39b360664c27f5aad072012f8bcbd"}, ] [package.extras] docs = ["furo (>=2023.7.26)", "sphinx (>=7.1.2)", "sphinx-autodoc-typehints (>=1.24)"] testing = ["covdefaults (>=2.3)", "coverage (>=7.3)", "diff-cover (>=7.7)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "pytest-timeout (>=2.1)"] typing = ["typing-extensions (>=4.7.1)"] [[package]] name = "identify" version = "2.5.30" description = "File identification library for Python" category = "dev" optional = false python-versions = ">=3.8" files = [ {file = "identify-2.5.30-py2.py3-none-any.whl", hash = "sha256:afe67f26ae29bab007ec21b03d4114f41316ab9dd15aa8736a167481e108da54"}, {file = "identify-2.5.30.tar.gz", hash = "sha256:f302a4256a15c849b91cfcdcec052a8ce914634b2f77ae87dad29cd749f2d88d"}, ] [package.extras] license = ["ukkonen"] [[package]] name = "importlib-resources" version = "6.1.0" description = "Read resources from Python packages" category = "main" optional = false python-versions = ">=3.8" files = [ {file = "importlib_resources-6.1.0-py3-none-any.whl", hash = "sha256:aa50258bbfa56d4e33fbd8aa3ef48ded10d1735f11532b8df95388cc6bdb7e83"}, {file = "importlib_resources-6.1.0.tar.gz", hash = "sha256:9d48dcccc213325e810fd723e7fbb45ccb39f6cf5c31f00cf2b965f5f10f3cb9"}, ] [package.dependencies] zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-ruff", "zipp (>=3.17)"] [[package]] name = "iniconfig" version = "2.0.0" description = "brain-dead simple config-ini parsing" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, ] [[package]] name = "lit" version = "16.0.6" description = "A Software Testing Tool" category = "dev" optional = false python-versions = "*" files = [ {file = "lit-16.0.6.tar.gz", hash = "sha256:84623c9c23b6b14763d637f4e63e6b721b3446ada40bf7001d8fee70b8e77a9a"}, ] [[package]] name = "mypy-extensions" version = "1.0.0" description = "Type system extensions for programs checked with the mypy type checker." category = "dev" optional = false python-versions = ">=3.5" files = [ {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, ] [[package]] name = "nodeenv" version = "1.8.0" description = "Node.js virtual environment builder" category = "dev" optional = false python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" files = [ {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, ] [package.dependencies] setuptools = "*" [[package]] name = "packaging" version = "23.2" description = "Core utilities for Python packages" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, ] [[package]] name = "pathspec" version = "0.11.2" description = "Utility library for gitignore style pattern matching of file paths." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "pathspec-0.11.2-py3-none-any.whl", hash = "sha256:1d6ed233af05e679efb96b1851550ea95bbb64b7c490b0f5aa52996c11e92a20"}, {file = "pathspec-0.11.2.tar.gz", hash = "sha256:e0d8d0ac2f12da61956eb2306b69f9469b42f4deb0f3cb6ed47b9cce9996ced3"}, ] [[package]] name = "platformdirs" version = "3.11.0" description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "platformdirs-3.11.0-py3-none-any.whl", hash = "sha256:e9d171d00af68be50e9202731309c4e658fd8bc76f55c11c7dd760d023bda68e"}, {file = "platformdirs-3.11.0.tar.gz", hash = "sha256:cf8ee52a3afdb965072dcc652433e0c7e3e40cf5ea1477cd4b3b1d2eb75495b3"}, ] [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.1)", "sphinx-autodoc-typehints (>=1.24)"] test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)"] [[package]] name = "pluggy" version = "1.3.0" description = "plugin and hook calling mechanisms for python" category = "dev" optional = false python-versions = ">=3.8" files = [ {file = "pluggy-1.3.0-py3-none-any.whl", hash = "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7"}, {file = "pluggy-1.3.0.tar.gz", hash = "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12"}, ] [package.extras] dev = ["pre-commit", "tox"] testing = ["pytest", "pytest-benchmark"] [[package]] name = "pre-commit" version = "3.4.0" description = "A framework for managing and maintaining multi-language pre-commit hooks." category = "dev" optional = false python-versions = ">=3.8" files = [ {file = "pre_commit-3.4.0-py2.py3-none-any.whl", hash = "sha256:96d529a951f8b677f730a7212442027e8ba53f9b04d217c4c67dc56c393ad945"}, {file = "pre_commit-3.4.0.tar.gz", hash = "sha256:6bbd5129a64cad4c0dfaeeb12cd8f7ea7e15b77028d985341478c8af3c759522"}, ] [package.dependencies] cfgv = ">=2.0.0" identify = ">=1.0.0" nodeenv = ">=0.11.1" pyyaml = ">=5.1" virtualenv = ">=20.10.0" [[package]] name = "psutil" version = "5.9.5" description = "Cross-platform lib for process and system monitoring in Python." category = "dev" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" files = [ {file = "psutil-5.9.5-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:be8929ce4313f9f8146caad4272f6abb8bf99fc6cf59344a3167ecd74f4f203f"}, {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:ab8ed1a1d77c95453db1ae00a3f9c50227ebd955437bcf2a574ba8adbf6a74d5"}, {file = "psutil-5.9.5-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:4aef137f3345082a3d3232187aeb4ac4ef959ba3d7c10c33dd73763fbc063da4"}, {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:ea8518d152174e1249c4f2a1c89e3e6065941df2fa13a1ab45327716a23c2b48"}, {file = "psutil-5.9.5-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:acf2aef9391710afded549ff602b5887d7a2349831ae4c26be7c807c0a39fac4"}, {file = "psutil-5.9.5-cp27-none-win32.whl", hash = "sha256:5b9b8cb93f507e8dbaf22af6a2fd0ccbe8244bf30b1baad6b3954e935157ae3f"}, {file = "psutil-5.9.5-cp27-none-win_amd64.whl", hash = "sha256:8c5f7c5a052d1d567db4ddd231a9d27a74e8e4a9c3f44b1032762bd7b9fdcd42"}, {file = "psutil-5.9.5-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:3c6f686f4225553615612f6d9bc21f1c0e305f75d7d8454f9b46e901778e7217"}, {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7a7dd9997128a0d928ed4fb2c2d57e5102bb6089027939f3b722f3a210f9a8da"}, {file = "psutil-5.9.5-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:89518112647f1276b03ca97b65cc7f64ca587b1eb0278383017c2a0dcc26cbe4"}, {file = "psutil-5.9.5-cp36-abi3-win32.whl", hash = "sha256:104a5cc0e31baa2bcf67900be36acde157756b9c44017b86b2c049f11957887d"}, {file = "psutil-5.9.5-cp36-abi3-win_amd64.whl", hash = "sha256:b258c0c1c9d145a1d5ceffab1134441c4c5113b2417fafff7315a917a026c3c9"}, {file = "psutil-5.9.5-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:c607bb3b57dc779d55e1554846352b4e358c10fff3abf3514a7a6601beebdb30"}, {file = "psutil-5.9.5.tar.gz", hash = "sha256:5410638e4df39c54d957fc51ce03048acd8e6d60abc0f5107af51e5fb566eb3c"}, ] [package.extras] test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] [[package]] name = "pyelftools" version = "0.30" description = "Library for analyzing ELF files and DWARF debugging information" category = "main" optional = false python-versions = "*" files = [ {file = "pyelftools-0.30-py2.py3-none-any.whl", hash = "sha256:544c3440eddb9a0dce70b6611de0b28163d71def759d2ed57a0d00118fc5da86"}, {file = "pyelftools-0.30.tar.gz", hash = "sha256:2fc92b0d534f8b081f58c7c370967379123d8e00984deb53c209364efd575b40"}, ] [[package]] name = "pytest" version = "7.4.2" description = "pytest: simple powerful testing with Python" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "pytest-7.4.2-py3-none-any.whl", hash = "sha256:1d881c6124e08ff0a1bb75ba3ec0bfd8b5354a01c194ddd5a0a870a48d99b002"}, {file = "pytest-7.4.2.tar.gz", hash = "sha256:a766259cfab564a2ad52cb1aae1b881a75c3eb7e34ca3779697c23ed47c47069"}, ] [package.dependencies] colorama = {version = "*", markers = "sys_platform == \"win32\""} exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} iniconfig = "*" packaging = "*" pluggy = ">=0.12,<2.0" tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] [[package]] name = "pyyaml" version = "6.0.1" description = "YAML parser and emitter for Python" category = "dev" optional = false python-versions = ">=3.6" files = [ {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, ] [[package]] name = "setuptools" version = "68.2.2" description = "Easily download, build, install, upgrade, and uninstall Python packages" category = "dev" optional = false python-versions = ">=3.8" files = [ {file = "setuptools-68.2.2-py3-none-any.whl", hash = "sha256:b454a35605876da60632df1a60f736524eb73cc47bbc9f3f1ef1b644de74fd2a"}, {file = "setuptools-68.2.2.tar.gz", hash = "sha256:4ac1475276d2f1c48684874089fefcd83bd7162ddaafb81fac866ba0db282a87"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] [[package]] name = "tomli" version = "2.0.1" description = "A lil' TOML parser" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, ] [[package]] name = "typing-extensions" version = "4.8.0" description = "Backported and Experimental Type Hints for Python 3.8+" category = "dev" optional = false python-versions = ">=3.8" files = [ {file = "typing_extensions-4.8.0-py3-none-any.whl", hash = "sha256:8f92fc8806f9a6b641eaa5318da32b44d401efaac0f6678c9bc448ba3605faa0"}, {file = "typing_extensions-4.8.0.tar.gz", hash = "sha256:df8e4339e9cb77357558cbdbceca33c303714cf861d1eef15e1070055ae8b7ef"}, ] [[package]] name = "virtualenv" version = "20.24.5" description = "Virtual Python Environment builder" category = "dev" optional = false python-versions = ">=3.7" files = [ {file = "virtualenv-20.24.5-py3-none-any.whl", hash = "sha256:b80039f280f4919c77b30f1c23294ae357c4c8701042086e3fc005963e4e537b"}, {file = "virtualenv-20.24.5.tar.gz", hash = "sha256:e8361967f6da6fbdf1426483bfe9fca8287c242ac0bc30429905721cefbff752"}, ] [package.dependencies] distlib = ">=0.3.7,<1" filelock = ">=3.12.2,<4" platformdirs = ">=3.9.1,<4" [package.extras] docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] [[package]] name = "zipp" version = "3.17.0" description = "Backport of pathlib-compatible object wrapper for zip files" category = "main" optional = false python-versions = ">=3.8" files = [ {file = "zipp-3.17.0-py3-none-any.whl", hash = "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31"}, {file = "zipp-3.17.0.tar.gz", hash = "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0"}, ] [package.extras] docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy (>=0.9.1)", "pytest-ruff"] [metadata] lock-version = "2.0" python-versions = "^3.8" content-hash = "9af9702109b79b70c4058104b346d3740191f0f5ee4117144c3f8599c8a29977" riscemu-2.2.5/pyproject.toml000066400000000000000000000016661451200553000161070ustar00rootroot00000000000000[tool.poetry] name = "riscemu" version = "2.2.5" description = "A basic RISC-V emulator" authors = ["Anton Lydike "] license = "MIT" readme = "README.md" homepage = "https://github.com/antonlydike/riscemu" repository = "https://github.com/antonlydike/riscemu" keywords = ["RISC-V"] # classifiers classifiers = [ "Programming Language :: Python :: 3", "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", ] [tool.poetry.scripts] riscemu = "riscemu.__main__:main" [tool.poetry.urls] "Bug Tracker" = "https://github.com/antonlydike/riscemu/issues" [tool.poetry.dependencies] python = "^3.8" pyelftools = "^0.30" importlib-resources = "^6.1.0" [tool.poetry.group.dev.dependencies] black = "^23.7.0" pytest = "^7.4.0" filecheck = "^0.0.23" lit = "^16.0.6" pre-commit = "^3.3.3" psutil = "^5.9.5" [build-system] requires = ["poetry-core"] build-backend = "poetry.core.masonry.api" riscemu-2.2.5/riscemu/000077500000000000000000000000001451200553000146315ustar00rootroot00000000000000riscemu-2.2.5/riscemu/IO/000077500000000000000000000000001451200553000151405ustar00rootroot00000000000000riscemu-2.2.5/riscemu/IO/IOModule.py000066400000000000000000000014751451200553000171760ustar00rootroot00000000000000from abc import ABC from typing import Optional from riscemu.core import MemorySection, MemoryFlags, T_RelativeAddress class IOModule(MemorySection, ABC): 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 ) def dump(self, *args, **kwargs): print(self) def __repr__(self): return "{}[{}] at 0x{:0X} (size={}bytes, flags={})".format( self.__class__.__name__, self.name, self.base, self.size, self.flags ) riscemu-2.2.5/riscemu/IO/TextIO.py000066400000000000000000000030271451200553000166700ustar00rootroot00000000000000from .IOModule import IOModule from core.traps import InstructionAccessFault from ..core import T_RelativeAddress, Instruction, MemoryFlags, Int32 class TextIO(IOModule): def read_ins(self, offset: T_RelativeAddress) -> Instruction: 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 ) self.buff = bytearray(buflen) self.current_line = "" def read(self, addr: int, size: int) -> bytearray: raise InstructionAccessFault(self.base + addr) def write(self, addr: int, size: int, data: bytearray): if addr == 0: if size > 4: raise InstructionAccessFault(addr) if Int32(data) != 0: self._print() return buff_start = addr - 4 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: lines = text.split("\n") lines[0] = self.current_line + lines[0] for line in lines[:-1]: self._present(line) self.current_line = lines[-1] else: self.current_line += text def _present(self, text: str): print("[TextIO:{:x}] {}".format(self.base, text)) riscemu-2.2.5/riscemu/IO/__init__.py000066400000000000000000000000001451200553000172370ustar00rootroot00000000000000riscemu-2.2.5/riscemu/__init__.py000066400000000000000000000007071451200553000167460ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT 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 """ # to read package version: import importlib.metadata __author__ = "Anton Lydike " __copyright__ = "Copyright 2023 Anton Lydike" __version__ = importlib.metadata.version(__name__) riscemu-2.2.5/riscemu/__main__.py000066400000000000000000000010711451200553000167220ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT This file holds the logic for starting the emulator from the CLI """ import sys from .core import RiscemuBaseException from .riscemu_main import RiscemuMain def main(): 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) except RiscemuBaseException as e: print("Error: {}".format(e.message())) e.print_stacktrace() sys.exit(-1) if __name__ == "__main__": main() riscemu-2.2.5/riscemu/assembler.py000066400000000000000000000216011451200553000171600ustar00rootroot00000000000000from enum import Enum, auto from typing import List, Optional, Tuple, Union from riscemu.core.exceptions import ASSERT_LEN, ParseException from .colors import FMT_NONE, FMT_PARSE from .core import ( BinaryDataMemorySection, Instruction, InstructionContext, InstructionMemorySection, Int32, Program, SimpleInstruction, T_RelativeAddress, ) from .helpers import align_addr, get_section_base_name, parse_numeric_argument from .tokenizer import Token INSTRUCTION_SECTION_NAMES = (".text", ".init", ".fini") """ A tuple containing all section names which contain executable code (instead of data) The first segment of each segment (first segment of ".text.main" is ".text") is checked against this list to determine the type of it. """ class MemorySectionType(Enum): Data = auto() Instructions = auto() class CurrentSection: name: str data: Union[List[Instruction], bytearray] type: MemorySectionType base: int def __init__(self, name: str, type: MemorySectionType, base: int = 0): self.name = name self.type = type self.base = base if self.type == MemorySectionType.Data: self.data = bytearray() elif self.type == MemorySectionType.Instructions: self.data = list() else: raise ParseException("Unknown section type: {}".format(type)) def current_address(self) -> T_RelativeAddress: if self.type == MemorySectionType.Data: return len(self.data) + self.base return len(self.data) * 4 + self.base def __repr__(self): return "{}(name={},data={},type={})".format( self.__class__.__name__, self.name, self.data, self.type.name ) class ParseContext: section: Optional[CurrentSection] context: InstructionContext program: Program def __init__(self, name: str): self.program = Program(name) self.context = self.program.context self.section = None def finalize(self) -> Program: self._finalize_section() return self.program def _finalize_section(self): if self.section is None: return if self.section.type == MemorySectionType.Data: section = BinaryDataMemorySection( 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.program.add_section(section) self.section = None def new_section(self, name: str, type: MemorySectionType, alignment: int = 4): base = align_addr(self.current_address(), alignment) 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 ): self.context.labels[name] = value if is_global: self.program.global_labels.add(name) if is_relative: self.program.relative_labels.add(name) def current_address(self): if self.section: return self.section.current_address() return self.program.base if self.program.base is not None else 0 def __repr__(self): return "{}(\n\tsetion={},\n\tprogram={}\n)".format( self.__class__.__name__, self.section, self.program ) 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 ) ) if context.section.type != type: raise ParseException( "Error, expected to be in {} section, but currently in {}...".format( type.name, context.section ) ) class AssemblerDirectives: """ This class represents a collection of all assembler directives as documented by https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md#pseudo-ops All class methods prefixed with op_ are directly used as assembler directives. """ @classmethod def op_align(cls, token: Token, args: Tuple[str], context: ParseContext): ASSERT_LEN(args, 1) ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data) align_to = parse_numeric_argument(args[0]) current_mod = context.current_address() % align_to if current_mod == 0: return context.section.data += bytearray(align_to - current_mod) @classmethod def op_p2align(cls, token: Token, args: Tuple[str], context: ParseContext): ASSERT_LEN(args, 1) ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Instructions) align_to = 2 ** parse_numeric_argument(args[0]) current_mod = context.current_address() % align_to if current_mod == 0: return num_bytes_fill = align_to - current_mod # fill in with nops: NOP_SIZE = 4 for i in range(num_bytes_fill // NOP_SIZE): context.section.data.append( SimpleInstruction( "nop", (), context.context, context.current_address(), ) ) @classmethod def op_section(cls, token: Token, args: Tuple[str], context: ParseContext): ASSERT_LEN(args, 1) if get_section_base_name(args[0]) in INSTRUCTION_SECTION_NAMES: context.new_section(args[0], MemorySectionType.Instructions) else: context.new_section(args[0], MemorySectionType.Data) @classmethod def op_globl(cls, token: Token, args: Tuple[str], context: ParseContext): ASSERT_LEN(args, 1) context.program.global_labels.add(args[0]) @classmethod def op_global(cls, token: Token, args: Tuple[str], context: ParseContext): cls.op_globl(token, args, context) @classmethod def op_equ(cls, token: Token, args: Tuple[str], context: ParseContext): ASSERT_LEN(args, 2) name = args[0] value = parse_numeric_argument(args[1]) context.context.labels[name] = value @classmethod def op_space(cls, token: Token, args: Tuple[str], context: ParseContext): ASSERT_LEN(args, 1) ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data) size = parse_numeric_argument(args[0]) cls.add_bytes(size, None, context) @classmethod def op_zero(cls, token: Token, args: Tuple[str], context: ParseContext): ASSERT_LEN(args, 1) ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data) size = parse_numeric_argument(args[0]) cls.add_bytes(size, bytearray(size), context) @classmethod def add_bytes( cls, size: int, content: Union[None, int, bytearray], context: ParseContext ): ASSERT_IN_SECTION_TYPE(context, MemorySectionType.Data) if content is None: content = bytearray(size) if isinstance(content, int): content = Int32(content).to_bytes(size) context.section.data += content @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") encoded_bytes = bytearray(text.encode("ascii")) if zero_terminate: encoded_bytes += bytearray(1) cls.add_bytes(len(encoded_bytes), encoded_bytes, context) @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"): cls.op_section(token, (token.value,), context) elif op in ("string", "asciiz", "asciz", "ascii"): ASSERT_LEN(args, 1) cls.add_text(args[0], context, zero_terminate=(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 ) DATA_OP_SIZES = { "byte": 1, "2byte": 2, "half": 2, "short": 2, "4byte": 4, "word": 4, "long": 4, "8byte": 8, "dword": 8, "quad": 8, } riscemu-2.2.5/riscemu/colors.py000066400000000000000000000010571451200553000165070ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike 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_BOLD = "\033[1m" FMT_NONE = "\033[0m" FMT_UNDERLINE = "\033[4m" FMT_ERROR = FMT_RED + FMT_BOLD FMT_MEM = FMT_CYAN + FMT_BOLD 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 riscemu-2.2.5/riscemu/config.py000066400000000000000000000013321451200553000164470ustar00rootroot00000000000000""" RiscEmu (c) 2021-2022 Anton Lydike SPDX-License-Identifier: MIT """ from dataclasses import dataclass @dataclass(frozen=True, init=True) class RunConfig: stack_size: int = 8 * 1024 * 64 # for 8KB stack include_scall_symbols: bool = True add_accept_imm: bool = False # debugging debug_instruction: bool = True debug_on_exception: bool = True # allowed syscalls scall_input: bool = True scall_fs: bool = False verbosity: int = 0 slowdown: float = 1 unlimited_registers: bool = False flen: int = 64 # runtime config use_libc: bool = False ignore_exit_code: bool = False # csr stuff: # frequency of the real-time clock rtclock_tickrate: int = 32768 riscemu-2.2.5/riscemu/core/000077500000000000000000000000001451200553000155615ustar00rootroot00000000000000riscemu-2.2.5/riscemu/core/__init__.py000066400000000000000000000044121451200553000176730ustar00rootroot00000000000000from typing import Dict, Any import re # define some base type aliases so we can keep track of absolute and relative addresses T_RelativeAddress = int T_AbsoluteAddress = int # parser options are just dictionaries with arbitrary values T_ParserOpts = Dict[str, Any] NUMBER_SYMBOL_PATTERN = re.compile(r"^\d+[fb]$") # exceptions from .exceptions import ( ParseException, NumberFormatException, MemoryAccessException, OutOfMemoryException, LinkerException, LaunchDebuggerException, RiscemuBaseException, InvalidRegisterException, InvalidAllocationException, InvalidSyscallException, UnimplementedInstruction, INS_NOT_IMPLEMENTED, ) # base classes from .flags import MemoryFlags from .int32 import UInt32, Int32 from .float import BaseFloat, Float32, Float64 from .rtclock import RTClock from .instruction import Instruction, Immediate, InstructionWithEncoding from .instruction_context import InstructionContext from .memory_section import MemorySection from .program import Program from .program_loader import ProgramLoader from .privmodes import PrivModes from .mmu import MMU from .csr import CSR from .registers import Registers from .cpu import CPU from .simple_instruction import SimpleInstruction from .instruction_memory_section import InstructionMemorySection from .binary_data_memory_section import BinaryDataMemorySection from .usermode_cpu import UserModeCPU __all__ = [ "T_RelativeAddress", "T_AbsoluteAddress", "T_ParserOpts", "NUMBER_SYMBOL_PATTERN", "ParseException", "NumberFormatException", "MemoryAccessException", "OutOfMemoryException", "LinkerException", "LaunchDebuggerException", "RiscemuBaseException", "InvalidRegisterException", "InvalidAllocationException", "InvalidSyscallException", "UnimplementedInstruction", "INS_NOT_IMPLEMENTED", "MemoryFlags", "UInt32", "Int32", "BaseFloat", "Float32", "Float64", "RTClock", "Instruction", "Immediate", "InstructionWithEncoding", "InstructionContext", "MemorySection", "Program", "ProgramLoader", "PrivModes", "MMU", "CSR", "Registers", "CPU", "SimpleInstruction", "InstructionMemorySection", "BinaryDataMemorySection", "UserModeCPU", ] riscemu-2.2.5/riscemu/core/binary_data_memory_section.py000066400000000000000000000032651451200553000235320ustar00rootroot00000000000000from typing import Optional from . import ( MemorySection, InstructionContext, MemoryFlags, T_RelativeAddress, Instruction, ) from ..core.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, ) self.data = data 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] 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" ) if len(data[0:size]) != 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", ) riscemu-2.2.5/riscemu/core/cpu.py000066400000000000000000000106341451200553000167260ustar00rootroot00000000000000from abc import ABC, abstractmethod from typing import List, Type, Callable, Set, Dict, TYPE_CHECKING from ..config import RunConfig from ..colors import FMT_NONE, FMT_CPU from . import ( T_AbsoluteAddress, Instruction, Program, PrivModes, MMU, UInt32, Registers, CSR, RTClock, csr_constants, ) if TYPE_CHECKING: # from core.mmu import MMU from ..instructions import InstructionSet class CPU(ABC): # static cpu configuration INS_XLEN: int = 4 # housekeeping variables regs: Registers mmu: "MMU" pc: T_AbsoluteAddress cycle: int halted: bool # debugging context debugger_active: bool # instruction information instructions: Dict[str, Callable[[Instruction], None]] instruction_sets: Set["InstructionSet"] # configuration conf: RunConfig # control and status things: hart_id: int mode: PrivModes csr: CSR rtclock: RTClock def __init__( self, mmu: "MMU", instruction_sets: List[Type["InstructionSet"]], conf: RunConfig, ): self.mmu = mmu self.regs = Registers(conf.unlimited_registers, conf.flen) self.conf = conf self.instruction_sets = set() self.instructions = dict() for set_class in instruction_sets: ins_set = set_class(self) self.instructions.update(ins_set.load()) self.instruction_sets.add(ins_set) self.halted = False self.cycle = 0 self.pc = 0 self.hart_id = 0 self.debugger_active = False self.csr = CSR() self.rtclock = RTClock(conf.rtclock_tickrate) def run_instruction(self, ins: Instruction): """ Execute a single instruction :param ins: The instruction to execute """ try: self.instructions[ins.name](ins) except KeyError as ex: raise RuntimeError("Unknown instruction: {}".format(ins)) from ex def load_program(self, program: Program): self.mmu.load_program(program) def __repr__(self): """ Returns a representation of the CPU and some of its state. """ return "{}(pc=0x{:08X}, cycle={}, halted={} instructions={})".format( self.__class__.__name__, self.pc, self.cycle, self.halted, " ".join(s.name for s in self.instruction_sets), ) @abstractmethod def step(self, verbose: bool = False): pass @abstractmethod def run(self, verbose: bool = False): pass def initialize_registers(self): # set a0 to the hartid self.regs.set("a0", UInt32(self.hart_id)) def launch(self, verbose: bool = False): entrypoint = self.mmu.find_entrypoint() if entrypoint is None: entrypoint = self.mmu.programs[0].entrypoint self.initialize_registers() self.setup_csr() if self.conf.verbosity > 0: print( FMT_CPU + "[CPU] Started running from {}".format( self.mmu.translate_address(entrypoint) ) + FMT_NONE ) self.pc = entrypoint self.run(verbose) @property def sections(self): return self.mmu.sections @property def programs(self): return self.mmu.programs def setup_csr(self): """ Set up standard CSR registers, can be hooked into when subclassing to provide more information. """ self.csr.set(CSR.name_to_addr("mhartid"), UInt32(self.hart_id)) self.csr.register_callback( CSR.name_to_addr("time"), getter=self.rtclock.get_low32, ) self.csr.register_callback( CSR.name_to_addr("timeh"), getter=self.rtclock.get_hi32, ) self.csr.register_callback( CSR.name_to_addr("instret"), getter=(lambda csr, _: UInt32(self.cycle)), ) self.csr.register_callback( CSR.name_to_addr("instreth"), getter=(lambda csr, _: UInt32(self.cycle >> 32)), ) self.csr.register_callback( CSR.name_to_addr("cycle"), getter=(lambda csr, _: UInt32(self.cycle)), ) self.csr.register_callback( CSR.name_to_addr("cycleh"), getter=(lambda csr, _: UInt32(self.cycle >> 32)), ) riscemu-2.2.5/riscemu/core/csr.py000066400000000000000000000157171451200553000167350ustar00rootroot00000000000000from collections import defaultdict from typing import Dict, Callable, Optional, Union from . import UInt32, PrivModes, csr_constants from .traps import InstructionAccessFault from ..colors import FMT_ERROR, FMT_NONE def _invalid_setter(addr: int, old_val: UInt32, new_val: UInt32): print(FMT_ERROR + f"Cannot write csr 0x{addr:03X}" + FMT_NONE) raise InstructionAccessFault(addr) def _invalid_getter(addr: int, curr_val: UInt32) -> UInt32: print(FMT_ERROR + f"Cannot read csr 0x{addr:03X}" + FMT_NONE) raise InstructionAccessFault(addr) class MStatusRegister: """ helper for the mstatus register """ state: UInt32 def __init__(self): self.state = UInt32() @property def uie(self) -> UInt32: return (self.state & (1 << 0)) >> 0 @uie.setter def uie(self, new_val: UInt32): new_val = new_val ^ self.uie self.state = self.state ^ (new_val << 0) @property def sie(self) -> UInt32: return (self.state & (1 << 1)) >> 1 @sie.setter def sie(self, new_val: UInt32): new_val = new_val ^ self.sie self.state = self.state ^ (new_val << 1) @property def mie(self) -> UInt32: return (self.state & (1 << 3)) >> 3 @mie.setter def mie(self, new_val: UInt32): new_val = new_val ^ self.mie self.state = self.state ^ (new_val << 3) @property def upie(self) -> UInt32: return (self.state & (1 << 4)) >> 4 @upie.setter def upie(self, new_val: UInt32): new_val = new_val ^ self.upie self.state = self.state ^ (new_val << 4) @property def spie(self) -> UInt32: return (self.state & (1 << 5)) >> 5 @spie.setter def spie(self, new_val: UInt32): new_val = new_val ^ self.spie self.state = self.state ^ (new_val << 5) @property def mpie(self) -> UInt32: return (self.state & (1 << 7)) >> 7 @mpie.setter def mpie(self, new_val: UInt32): new_val = new_val ^ self.mpie self.state = self.state ^ (new_val << 7) @property def spp(self) -> UInt32: return (self.state & (1 << 8)) >> 8 @spp.setter def spp(self, new_val: UInt32): new_val = new_val ^ self.spp self.state = self.state ^ (new_val << 8) @property def mpp(self) -> UInt32: # bitwidth = 2 return (self.state & (0b11 << 11)) >> 11 @mpp.setter def mpp(self, new_val: UInt32): new_val = new_val ^ self.mpp self.state = self.state ^ (new_val << 11) @property def fs(self) -> UInt32: # bitwidth = 2 return (self.state & (0b11 << 13)) >> 13 @fs.setter def fs(self, new_val: UInt32): new_val = new_val ^ self.fs self.state = self.state ^ (new_val << 13) @property def xs(self) -> UInt32: # bitwidth = 2 return (self.state & (0b11 << 15)) >> 15 @xs.setter def xs(self, new_val: UInt32): new_val = new_val ^ self.xs self.state = self.state ^ (new_val << 15) @property def mpriv(self) -> UInt32: return (self.state & (1 << 17)) >> 17 @mpriv.setter def mpriv(self, new_val: UInt32): new_val = new_val ^ self.mpriv self.state = self.state ^ (new_val << 17) @property def sum(self) -> UInt32: return (self.state & (1 << 18)) >> 18 @sum.setter def sum(self, new_val: UInt32): new_val = new_val ^ self.sum self.state = self.state ^ (new_val << 18) @property def mxr(self) -> UInt32: return (self.state & (1 << 19)) >> 19 @mxr.setter def mxr(self, new_val: UInt32): new_val = new_val ^ self.mxr self.state = self.state ^ (new_val << 19) @property def tvm(self) -> UInt32: return (self.state & (1 << 20)) >> 20 @tvm.setter def tvm(self, new_val: UInt32): new_val = new_val ^ self.tvm self.state = self.state ^ (new_val << 20) @property def tw(self) -> UInt32: return (self.state & (1 << 21)) >> 21 @tw.setter def tw(self, new_val: UInt32): new_val = new_val ^ self.tw self.state = self.state ^ (new_val << 21) @property def tsr(self) -> UInt32: return (self.state & (1 << 22)) >> 22 @tsr.setter def tsr(self, new_val: UInt32): new_val = new_val ^ self.tsr self.state = self.state ^ (new_val << 22) @property def sd(self) -> UInt32: return (self.state & (1 << 31)) >> 31 @sd.setter def sd(self, new_val: UInt32): new_val = new_val ^ self.sd self.state = self.state ^ (new_val << 31) class CSR: """ Represents a processors control and status registers """ state: Dict[int, UInt32] setters: Dict[int, Callable[[int, UInt32, UInt32], UInt32]] getters: Dict[int, Callable[[int, UInt32], UInt32]] mstatus: MStatusRegister def __init__(self): self.state = defaultdict(UInt32) self.mstatus = MStatusRegister() # wire mstatus state up to our csr state self.state[CSR.name_to_addr("mstatus")] = self.mstatus.state self.getters = dict() self.setters = dict() def get(self, addr: int) -> UInt32: if addr in self.getters: return self.getters[addr](addr, self.state[addr]) return self.state[addr] def set(self, addr: int, val: UInt32): if addr in self.setters: self.state[addr] = self.setters[addr](addr, self.state[addr], val) else: self.state[addr] = val def register_callback( self, addr: int, *, getter: Optional[Callable[[int, UInt32], UInt32]] = None, setter: Optional[Callable[[int, UInt32, UInt32], UInt32]] = None, ): """ addr: the CSR address getter: a function mapping (addr, old_val_in_store) -> actual value setter: a function mapping (addr, old_val_in_store, given_val) -> new_val_in_store where the _in_store values denote the values in the CSR store. Values may be handled by external stores, if that's the case these arguments can be safely ignored. """ if getter is None: getter = _invalid_getter self.getters[addr] = getter if setter is None: setter = _invalid_setter self.setters[addr] = setter @staticmethod def assert_can_read(mode: PrivModes, addr: int): if (addr >> 8) & 3 > mode.value: raise InstructionAccessFault(addr) @staticmethod def assert_can_write(mode: PrivModes, addr: int): if (addr >> 8) & 3 > mode.value or addr >> 10 == 0b11: raise InstructionAccessFault(addr) @staticmethod def name_to_addr(addr: Union[str, int]) -> Optional[int]: if isinstance(addr, str): if addr not in csr_constants.CSR_NAME_TO_ADDR: print("Unknown CSR register {}".format(addr)) return None return csr_constants.CSR_NAME_TO_ADDR[addr] return addr riscemu-2.2.5/riscemu/core/csr_constants.py000066400000000000000000000032171451200553000210210ustar00rootroot00000000000000from 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", } """ Assigns tuple (interrupt bit, exception code) to their respective readable names """ CSR_NAME_TO_ADDR: Dict[str, int] = { "fflags": 0x001, "frm": 0x002, "fcsr": 0x003, "mstatus": 0x300, "misa": 0x301, "mie": 0x304, "mtvec": 0x305, "mepc": 0x341, "mcause": 0x342, "mtval": 0x343, "mip": 0x344, "mtimecmp": 0x780, "mtimecmph": 0x781, "halt": 0x789, "cycle": 0xC00, "time": 0xC01, "instret": 0xC02, "cycleh": 0xC80, "timeh": 0xC81, "instreth": 0xC82, "mvendorid": 0xF11, "marchid": 0xF12, "mimpid": 0xF13, "mhartid": 0xF14, } """ Translation for named registers """ riscemu-2.2.5/riscemu/core/exceptions.py000066400000000000000000000113121451200553000203120ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ from abc import abstractmethod from ..colors import * import typing if typing.TYPE_CHECKING: from . import Instruction class RiscemuBaseException(BaseException): @abstractmethod def message(self) -> str: raise NotImplemented 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): 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 ) def ASSERT_EQ(a1, a2): if 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), ) def ASSERT_NOT_NULL(a1): if a1 is None: 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) ) def ASSERT_IN(a1, a2): if a1 not in a2: raise ParseException( "ASSERTION_FAILED: Expected {} to not be in {}".format(a1, a2), (a1, a2) ) class LinkerException(RiscemuBaseException): def __init__(self, msg: str, data): self.msg = msg self.data = data def message(self): 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): super(MemoryAccessException, self).__init__() self.msg = msg self.addr = addr self.size = size 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 ) class OutOfMemoryException(RiscemuBaseException): def __init__(self, action): self.action = action def message(self): return ( FMT_MEM + "{}(Ran out of memory during {})".format( self.__class__.__name__, self.action ) + FMT_NONE ) class InvalidAllocationException(RiscemuBaseException): def __init__(self, msg, name, size, flags): self.msg = msg self.name = name self.size = size self.flags = flags def message(self): 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): 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 ) class InvalidRegisterException(RiscemuBaseException): def __init__(self, reg): self.reg = reg def message(self): return ( FMT_CPU + "{}(Invalid register {})".format(self.__class__.__name__, self.reg) + FMT_NONE ) class InvalidSyscallException(RiscemuBaseException): def __init__(self, scall): self.scall = scall def message(self): return ( FMT_SYSCALL + "{}(Invalid syscall: {})".format(self.__class__.__name__, self.scall) + FMT_NONE ) def INS_NOT_IMPLEMENTED(ins): raise UnimplementedInstruction(ins) class NumberFormatException(RiscemuBaseException): def __init__(self, msg): super().__init__(msg) self.msg = msg def message(self): 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: return "" riscemu-2.2.5/riscemu/core/flags.py000066400000000000000000000004061451200553000172270ustar00rootroot00000000000000from dataclasses import dataclass @dataclass(frozen=True) class MemoryFlags: read_only: bool executable: bool def __repr__(self): return "r{}{}".format( "-" if self.read_only else "w", "x" if self.executable else "-" ) riscemu-2.2.5/riscemu/core/float.py000066400000000000000000000131551451200553000172450ustar00rootroot00000000000000import struct from ctypes import c_float, c_double from typing import Union, Any, ClassVar, Type from abc import ABC bytes_t = bytes class BaseFloat(ABC): __slots__ = ("_val",) _type: ClassVar[Type[Union[c_float, c_double]]] _struct_fmt_str: ClassVar[str] _val: Union[c_float, c_double] @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("<" + self._struct_fmt_str, self.value) @classmethod def from_bytes(cls, val: Union[bytes_t, bytearray]): return cls(val) def __init__( self, val: Union[float, c_float, "BaseFloat", bytes_t, bytearray, int] = 0 ): if isinstance(val, (float, int)): self._val = self._type(val) elif isinstance(val, (c_float, c_double)): self._val = self._type(val.value) elif isinstance(val, (bytes, bytearray)): self._val = self._type(struct.unpack("<" + self._struct_fmt_str, val)[0]) elif isinstance(val, self.__class__): self._val = val._val else: raise ValueError( "Unsupported value passed to {}: {} ({})".format( self.__class__.__name__, repr(val), type(val), ) ) def __add__(self, other: Union["BaseFloat", float]): if isinstance(other, BaseFloat): other = other.value return self.__class__(self.value + other) def __sub__(self, other: Union["BaseFloat", float]): if isinstance(other, BaseFloat): other = other.value return self.__class__(self.value - other) def __mul__(self, other: Union["BaseFloat", float]): if isinstance(other, BaseFloat): other = other.value return self.__class__(self.value * other) def __truediv__(self, other: Any): if isinstance(other, BaseFloat): other = other.value return self.__class__(self.value / other) def __floordiv__(self, other: Any): if isinstance(other, BaseFloat): other = other.value return self.__class__(self.value // other) def __mod__(self, other: Union["BaseFloat", float]): if isinstance(other, BaseFloat): 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, BaseFloat): 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 __hash__(self): return hash(self.value) def __gt__(self, other: Any): if isinstance(other, BaseFloat): other = other.value return self.value > other def __lt__(self, other: Any): if isinstance(other, BaseFloat): other = other.value return self.value < other def __le__(self, other: Any): if isinstance(other, BaseFloat): other = other.value return self.value <= other def __ge__(self, other: Any): if isinstance(other, BaseFloat): other = other.value return self.value >= other def __bool__(self): return bool(self.value) def __int__(self): return int(self.value) def __float__(self): return 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 @classmethod def bitcast(cls, f: "BaseFloat") -> "BaseFloat": """ bitcast the struct up or down to another type. Fills upper bits with zero. Use Float64.bitcast(Float32(...)) to bitcast a f32 to f64 """ if isinstance(f, cls): return f return cls.from_bytes( (b"\x00\x00\x00\x00\x00\x00\x00\x00" + f.bytes)[ -struct.calcsize(cls._struct_fmt_str) : ] ) @classmethod def flen_to_cls(cls, bits: int) -> Type["BaseFloat"]: if bits == 32: return Float32 if bits == 64: return Float64 raise ValueError(f"Unsupported flen: {bits}") def __format__(self, spec: str): if spec[-2:] == "32": return Float32.bitcast(self).__format__(spec[:-2]) if spec[-2:] == "64": return Float64.bitcast(self).__format__(spec[:-2]) return format(self.value, spec) class Float32(BaseFloat): _type = c_float _struct_fmt_str = "f" class Float64(BaseFloat): _type = c_double _struct_fmt_str = "d" riscemu-2.2.5/riscemu/core/instruction.py000066400000000000000000000034531451200553000205210ustar00rootroot00000000000000from abc import ABC, abstractmethod from typing import Union from .int32 import Int32 class Immediate: """ This class solves a problem of ambiguity when interpreting assembly code. Let's look at the following four cases (assuming 1b is 16 bytes back): a) beq a0, a1, 1b // conditional jump 16 bytes back b) beq a0, a1, -16 // conditional jump 16 bytes back c) addi a0, a1, 1b // subtract (pc - 16) from a1 d) addi a0, a1, -16 // subtract 16 from a1 We want a and b to behave the same, but c and d not to. The Immediate class solves this problem, by giving each instruction two ways of interpreting a given immediate. It can either get the "absolute value" of the immediate, or it's relative value to the PC. In this case, the beq instruction would interpret both as PC relative values, while addi would treat them as absolute values. """ abs_value: Int32 pcrel_value: Int32 __slots__ = ["abs_value", "pcrel_value"] def __init__(self, abs_value: Union[int, Int32], pcrel_value: Union[int, Int32]): self.abs_value = Int32(abs_value) self.pcrel_value = Int32(pcrel_value) class Instruction(ABC): name: str args: tuple @abstractmethod def get_imm(self, num: int) -> Immediate: """ parse and get immediate argument """ pass @abstractmethod def get_reg(self, num: int) -> str: """ parse and get an register argument """ pass def __repr__(self): return "{} {}".format(self.name, ", ".join(self.args)) class InstructionWithEncoding(ABC): """ Mixin for instructions that have encodings """ @property @abstractmethod def encoding(self) -> int: raise NotImplementedError() riscemu-2.2.5/riscemu/core/instruction_context.py000066400000000000000000000035401451200553000222620ustar00rootroot00000000000000from collections import defaultdict from typing import Dict, List, Optional from .exceptions import ParseException from ..core import T_AbsoluteAddress, T_RelativeAddress, NUMBER_SYMBOL_PATTERN class InstructionContext: base_address: T_AbsoluteAddress """ The address where the instruction block is placed """ labels: Dict[str, T_RelativeAddress] """ This dictionary maps all labels to their relative position of the instruction block """ 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 """ global_symbol_dict: Dict[str, T_AbsoluteAddress] """ A reference to the MMU's global symbol dictionary for access to global symbols """ def __init__(self): self.labels = dict() self.numbered_labels = defaultdict(list) self.base_address = 0 self.global_symbol_dict = dict() def resolve_numerical_label( self, symbol: str, address_at: int ) -> Optional[T_AbsoluteAddress]: 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, ) else: return min( (addr + self.base_address for addr in values if addr > address_at), default=None, ) def resolve_label(self, symbol: str) -> Optional[T_AbsoluteAddress]: # 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) riscemu-2.2.5/riscemu/core/instruction_memory_section.py000066400000000000000000000027301451200553000236320ustar00rootroot00000000000000from typing import List 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, ): self.name = name self.base = base self.context = context self.size = len(instructions) * 4 self.flags = MemoryFlags(True, True) self.instructions = instructions 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", ) def write(self, offset: T_RelativeAddress, size: int, data: bytearray): 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", ) return self.instructions[offset // 4] riscemu-2.2.5/riscemu/core/int32.py000066400000000000000000000206161451200553000170770ustar00rootroot00000000000000from typing import Any, Union from ctypes import c_int32, c_uint32 class Int32: """ This class implements 32bit signed integers (see :class:`UInt32` for unsigned integers) It implements basically all mathematical dunder magic methods (__add__, __sub__, etc.) 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 an Int32. """ _type = c_int32 __slots__ = ("_val",) def __init__( self, val: Union[int, c_int32, c_uint32, "Int32", bytes, bytearray, bool] = 0 ): if isinstance(val, (c_uint32, c_int32, Int32)): self._val = self.__class__._type(val.value) elif isinstance(val, (int, bool)): self._val = self.__class__._type(val) elif 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) ) else: raise RuntimeError( "Unknown {} input type: {} ({})".format( self.__class__.__name__, type(val), val ) ) 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]): if isinstance(other, Int32): other = other.value return self.__class__(self._val.value - other) 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): return self // other def __floordiv__(self, other: Any): if isinstance(other, Int32): other = other.value return self.__class__(self.value // other) 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]): if isinstance(other, Int32): other = other.value return self.__class__(self._val.value & other) 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]): if isinstance(other, Int32): other = other.value return self.__class__(self._val.value ^ other) 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]): 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 __neg__(self): return self.__class__(-self._val.value) def __invert__(self): return self.__class__(~self.value) def __abs__(self): return self.__class__(abs(self.value)) def __bytes__(self): return self.to_bytes(4) 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, Int32): other = other.value return self.value > other def __lt__(self, other: Any): if isinstance(other, Int32): other = other.value return self.value < other def __le__(self, other: Any): if isinstance(other, Int32): other = other.value return self.value <= other def __ge__(self, other: Any): if isinstance(other, Int32): other = other.value return self.value >= other def __bool__(self): return bool(self.value) def __cmp__(self, other: Any): if isinstance(other, Int32): other = other.value return self.value.__cmp__(other) # 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 @property def value(self) -> int: """ The value represented by this Integer :return: """ return self._val.value def unsigned(self) -> "UInt32": """ Convert to an unsigned representation. See :class:Uint32 :return: """ return UInt32(self) def to_bytes(self, bytes: int = 4) -> bytearray: """ Convert to a bytearray of length :param:bytes :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] def signed(self) -> "Int32": """ Convert to a signed representation. See :class:Int32 :return: """ if self.__class__ == Int32: return self return Int32(self) @property def unsigned_value(self): """ Return the value interpreted as an unsigned integer :return: """ return c_uint32(self.value).value def shift_right_logical(self, amount: Union["Int32", int]) -> "Int32": """ This function implements logical right shifts, meaning that the sign bit is shifted as well. This is equivalent to (self.value % 0x100000000) >> amount :param amount: Number of positions to shift :return: A new Int32 object representing the shifted value (keeps the signed-ness of the source) """ if isinstance(amount, Int32): amount = amount.value return self.__class__((self.value % 0x100000000) >> amount) def __int__(self): return self.value def __hex__(self): return hex(self.value) @classmethod def sign_extend(cls, data: Union[bytes, bytearray, int], bits: int): """ Create an instance of Int32 by sign extending :param:bits bits from :param:data to 32 bits :param data: The source data :param bits: The number of bits in the source data :return: An instance of Int32, holding the sign-extended value """ if isinstance(data, (bytes, bytearray)): 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) return cls(data) class UInt32(Int32): """ An unsigned version of :class:Int32. """ _type = c_uint32 def unsigned(self) -> "UInt32": """ Return a new instance representing the same bytes, but signed :return: """ return self @property def unsigned_value(self) -> int: return self._val.value def shift_right_logical(self, amount: Union["Int32", int]) -> "UInt32": """ see :meth:`Int32.shift_right_logical ` :param amount: Number of positions to shift :return: A new Int32 object representing the shifted value (keeps the signed-ness of the source) """ if isinstance(amount, Int32): amount = amount.value return UInt32(self.value >> amount) riscemu-2.2.5/riscemu/core/memory_section.py000066400000000000000000000126371451200553000212000ustar00rootroot00000000000000from abc import ABC, abstractmethod from dataclasses import dataclass 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, ) @dataclass class MemorySection(ABC): name: str flags: MemoryFlags size: int base: T_AbsoluteAddress owner: str context: InstructionContext @property def end(self): return self.base + self.size @abstractmethod def read(self, offset: T_RelativeAddress, size: int) -> bytearray: pass @abstractmethod def write(self, offset: T_RelativeAddress, size: int, data: bytearray): pass @abstractmethod 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, ): """ Dump the section. If no end is given, the rows around start are printed and start is highlighted. :param start: The address to start at :param end: The end of the printed space :param fmt: either hex, int, char or asm :param bytes_per_row: the number of bytes displayed per row :param rows: the number of rows displayed :param group: Group this many bytes into one when displaying :param highlight: Highlight the group containing this address :return: """ if isinstance(start, Int32): start = start.value if isinstance(end, Int32): end = end.value if fmt is None: if self.flags.executable and self.flags.read_only: bytes_per_row = 4 fmt = "asm" else: fmt = "hex" if fmt == "char": if bytes_per_row is None: bytes_per_row = 4 if group is None: group = 1 if group is None: group = 4 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 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 ) 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 ) ) else: 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 ) 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, ) ) 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, ) ) def dump_all( self, fmt: Optional[str] = None, bytes_per_row: Optional[int] = None, rows: int = 10, group: Optional[int] = None, highlight: Optional[int] = None, ): self.dump(0, self.size, fmt, bytes_per_row, rows, group, highlight) def __repr__(self): return "{}[{}] at 0x{:08X} (size={}bytes, flags={}, owner={})".format( self.__class__.__name__, self.name, self.base, self.size, self.flags, self.owner, ) def debug_str(self) -> str: return "0x{:08X}-0x{:08X} flags={} {}[{}] ({}bytes)".format( self.base, self.base + self.size, self.flags, self.owner, self.name, self.size, ) riscemu-2.2.5/riscemu/core/mmu.py000066400000000000000000000307571451200553000167450ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ from typing import Dict, List, Optional, Union from ..colors import * from ..helpers import align_addr from . import ( Instruction, MemorySection, MemoryFlags, T_AbsoluteAddress, Program, InstructionContext, Int32, Float32, Float64, InvalidAllocationException, MemoryAccessException, ) class MMU: """ The MemoryManagementUnit. This provides a unified interface for reading/writing data from/to memory. It also provides various translations for addresses. """ max_size = 0xFFFFFFFF """ The maximum size of the memory in bytes """ max_alloc_size = 8 * 1024 * 1024 * 64 """ No single allocation can be bigger than 64 MB """ sections: List[MemorySection] """ A list of all loaded memory sections """ programs: List[Program] """ A list of all loaded programs """ global_symbols: Dict[str, int] """ The global symbol table """ _ins_sec: Optional[MemorySection] """ Caching the last section where we read instructions from """ _mem_sec: Optional[MemorySection] """ Caching the last section where we read data from """ def __init__(self): """ Create a new MMU """ self.programs = list() self.sections = list() self.global_symbols = dict() self._ins_sec = None self._mem_sec = None def get_sec_containing(self, addr: T_AbsoluteAddress) -> Optional[MemorySection]: """ Returns the section that contains the address addr :param addr: the Address to look for :return: The LoadedMemorySection or None """ for sec in self.sections: if sec.base <= addr < sec.base + sec.size: self._mem_sec = sec return sec return None def get_program_at_addr(self, addr: T_AbsoluteAddress) -> Optional[Program]: for program in self.programs: if program.base <= addr < program.base + program.size: return program return None def read_ins(self, addr: T_AbsoluteAddress) -> Instruction: """ Read a single instruction located at addr :param addr: The location :return: The Instruction """ sec = self._ins_sec if addr < sec.base or sec.base + sec.size <= addr: sec = self.get_sec_containing(addr) if sec is not None: self._ins_sec = sec else: 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) def read(self, addr: Union[int, Int32], size: int) -> bytearray: """ Read size bytes of memory at addr :param addr: The address at which to start reading :param size: The number of bytes to read :return: The bytearray at addr """ sec = self._mem_sec if addr < sec.base or sec.base + sec.size <= addr: sec = self.get_sec_containing(addr) if sec is not None: self._mem_sec = sec else: 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): """ Write bytes into memory :param addr: The address at which to write :param size: The number of bytes to write :param data: The bytearray to write (only first size bytes are written) """ sec = self._mem_sec if addr < sec.base or sec.base + sec.size <= addr: sec = self.get_sec_containing(addr) if sec is not None: self._mem_sec = sec else: 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) def dump(self, addr, *args, **kwargs): """ Dumpy the memory contents :param addr: The address at which to dump :param args: args for the dump function of the loaded memory section :param kwargs: kwargs for the dump function of the loaded memory section """ sec = self.get_sec_containing(addr) if sec is None: print( FMT_MEM + "[MMU] No section containing addr 0x{:08X}".format(addr) + FMT_NONE ) return sec.dump(addr - sec.base, *args, **kwargs) def label(self, symb: str): """ Look up the symbol symb in all local symbol tables (and the global one) :param symb: The symbol name to look up """ 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] ) ) 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 ) ) 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 read_double(self, addr: int) -> Float64: return Float64(self.read(addr, 8)) 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) 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", } 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 = ("", float("inf")) for name, val in best_fit: if address - val < best[1]: best = (name, val) if address - val == best[1]: if best[0] in elf_markers: best = (name, val) elif best[0] in secs and name not in elf_markers: best = (name, val) name, val = best if not name: return "{}:{} + 0x{:x} (0x{:x})".format( sec.owner, sec.name, address - sec.base, address ) return str( "{}:{} at {} (0x{:0x}) + 0x{:0x}".format( sec.owner, sec.name, name, val, address - val ) ) def has_continuous_free_region(self, start: int, end: int) -> bool: # if we have no sections we are all good if len(self.sections) == 0: return True # if the last section is located before the start we are also good if start >= self.sections[-1].base + self.sections[-1].size: return True for sec in self.sections: # skip all sections that end before the required start point if sec.base + sec.size <= start: continue # we now have the first section that doesn't end **before** the start point # if this section starts after the specified end, we are good if sec.base >= end: return True # otherwise we can't continue return False # if all sections end before the requested start we are good # technically we shouldn't ever reach this point, but better safe than sorry return True def load_program(self, program: Program, align_to: int = 4): if program.base is not None: if not self.has_continuous_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: at_addr = align_addr(self.get_guaranteed_free_address(), align_to) # trigger the load event to set all addresses in the binary program.loaded_trigger(at_addr) # add program and sections to internal state self.programs.append(program) self.sections += program.sections self._update_state() # load all global symbols from program self.global_symbols.update( {key: program.context.labels[key] for key in program.global_labels} ) # inject reference to global symbol table into program context # FIXME: this is pretty unclean and should probably be solved in a better way in the future program.context.global_symbol_dict = self.global_symbols def load_section(self, sec: MemorySection, fixed_position: bool = False) -> bool: if fixed_position: if self.has_continuous_free_region(sec.base, sec.base + sec.size): self.sections.append(sec) self._update_state() else: 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 def _update_state(self): """ Called whenever a section or program is added to keep the list of programs and sections consistent :return: """ self.programs.sort(key=lambda bin: bin.base) self.sections.sort(key=lambda sec: sec.base) self._mem_sec = self.sections[-1] self._ins_sec = self.sections[-1] def get_guaranteed_free_address(self) -> T_AbsoluteAddress: if len(self.sections) == 0: return 0x100 else: return self.sections[-1].base + self.sections[-1].size def __repr__(self): return "{}(\n\t{}\n)".format( self.__class__.__name__, "\n\t".join(repr(x) for x in self.programs) ) def context_for(self, addr: T_AbsoluteAddress) -> InstructionContext: sec = self.get_sec_containing(addr) if sec is not None: return sec.context 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 riscemu-2.2.5/riscemu/core/platform.py000066400000000000000000000007751451200553000177700ustar00rootroot00000000000000from .cpu import CPU from .memory_section import MemorySection from typing import List class Platform: """ This is a wrapper around a given hardware configuration. """ harts: List[CPU] memory: List[MemorySection] def __init__(self): pass def step(self): performed_step = False for cpu in self.harts: if not cpu.halted: cpu.step(cpu.conf.verbosity > 1) performed_step = True return performed_step riscemu-2.2.5/riscemu/core/privmodes.py000066400000000000000000000001561451200553000201450ustar00rootroot00000000000000from enum import IntEnum class PrivModes(IntEnum): USER = 0 SUPER = 1 HYPER = 2 MACHINE = 3 riscemu-2.2.5/riscemu/core/program.py000066400000000000000000000100571451200553000176050ustar00rootroot00000000000000from typing import List, Optional, Set from ..colors import FMT_RED, FMT_BOLD, FMT_NONE, FMT_MEM from ..helpers import get_section_base_name from . import InstructionContext, T_AbsoluteAddress, MemorySection class Program: """ This represents a collection of sections which together form an executable program When you want to create a program which can be located anywhere in memory, set base to None, this signals the other components, that this is relocatable. Set the base of each section to the offset in the program, and everything will be taken care of for you. """ name: str context: InstructionContext global_labels: Set[str] relative_labels: Set[str] sections: List[MemorySection] base: Optional[T_AbsoluteAddress] is_loaded: bool @property def size(self): if len(self.sections) == 0: return 0 if self.base is None: return self.sections[-1].base + self.sections[-1].size return (self.sections[-1].base - self.base) + self.sections[-1].size def __init__(self, name: str, base: Optional[int] = None): self.name = name self.context = InstructionContext() self.sections = [] self.global_labels = set() self.relative_labels = set() self.base = base self.is_loaded = False def add_section(self, sec: MemorySection): # print a warning when a section is located before the programs base 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( sec, self.name, self.base ) + FMT_NONE ) self.sections.append(sec) # keep section list ordered self.sections.sort(key=lambda section: section.base) 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, ) @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") for sec in self.sections: if get_section_base_name(sec.name) == ".text" and sec.flags.executable: return sec.base def loaded_trigger(self, at_addr: T_AbsoluteAddress): """ This trigger is called when the binary is loaded and its final address in memory is determined This will do a small sanity check to prevent programs loading twice, or at addresses they don't expect to be loaded. Then it will finalize all relative symbols defined in it to point to the correct addresses. :param at_addr: the address where the program will be located """ if self.is_loaded: if at_addr != self.base: 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 ) # check if we are relocating if self.base != at_addr: offset = at_addr if self.base is None else at_addr - self.base # move all sections by the offset for sec in self.sections: sec.base += offset # move all relative symbols by the offset for name in self.relative_labels: self.context.labels[name] += offset self.base = at_addr self.context.base_address = at_addr riscemu-2.2.5/riscemu/core/program_loader.py000066400000000000000000000041451451200553000211340ustar00rootroot00000000000000import os from abc import abstractmethod, ABC from typing import Union, Iterator, List, ClassVar from io import IOBase from . import T_ParserOpts, Program class ProgramLoader(ABC): """ A program loader is always specific to a given source file. It is a place to store all state concerning the parsing and loading of that specific source file, including options. """ is_binary: ClassVar[bool] def __init__(self, source_name: str, source: IOBase, options: T_ParserOpts): self.source_name = source_name self.source = source self.options = options self.filename = os.path.split(self.source_name)[-1] self.__post_init__() def __post_init__(self): pass @classmethod @abstractmethod def can_parse(cls, source_name: str) -> float: """ Return confidence that the file located at source_path should be parsed and loaded by this loader :param source_name: the name of the input :return: the confidence that this file belongs to this parser in [0,1] """ pass @classmethod @abstractmethod def get_options(cls, argv: List[str]) -> [List[str], T_ParserOpts]: """ parse command line args into an options dictionary :param argv: the command line args list :return: all remaining command line args and the parser options object """ pass @classmethod def instantiate( cls, source_name: str, source: IOBase, options: T_ParserOpts ) -> "ProgramLoader": """ Instantiate a loader for the given source file with the required arguments :param source_name: the path to the source file :param source: IO Object representing the source :param options: the parsed options (guaranteed to come from this classes get_options method). :return: An instance of a ProgramLoader for the specified source """ return cls(source_name, source, options) @abstractmethod def parse(self) -> Union[Program, Iterator[Program]]: """ :return: """ pass riscemu-2.2.5/riscemu/core/registers.py000066400000000000000000000150721451200553000201470ustar00rootroot00000000000000""" RiscEmu (c) 2023 Anton Lydike SPDX-License-Identifier: MIT """ from collections import defaultdict from typing import Type from ..helpers import * from . import Int32, BaseFloat 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", "ft8", "ft9", "ft10", "ft11", "fs0", "fs1", "fs2", "fs3", "fs4", "fs5", "fs6", "fs7", "fs8", "fs9", "fs10", "fs11", "fa0", "fa1", "fa2", "fa3", "fa4", "fa5", "fa6", "fa7", } flen: int _float_type: Type[BaseFloat] def __init__(self, infinite_regs: bool = False, flen: int = 32): self.vals: dict[str, Int32] = defaultdict(UInt32) self.flen = flen self._float_type = BaseFloat.flen_to_cls(flen) self.float_vals: dict[str, BaseFloat] = defaultdict(self._float_type) self.last_set = None self.last_read = None self.infinite_regs = infinite_regs def dump(self, full: bool = 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)] if not full: regs = [("a", 8), ("s", 12), ("t", 7)] else: regs = [ ("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) lines[i].append(self._reg_repr(reg)) 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:])) print("\t" + "--------------- " * 3) else: print("\t" + " ".join(named_regs)) print("\t" + "--------------- " * 6) for line in lines: print("\t" + " ".join(line)) print(")") def dump_reg_a(self): """ Dump the a registers """ 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" 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": 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: return FMT_GRAY + txt + FMT_NONE return txt def set(self, reg: str, val: Int32, mark_set: bool = True) -> bool: """ Set a register content to val :param reg: The register to set :param val: The new value :param mark_set: If True, marks this register as "last accessed" (only used internally) :return: If the operation was successful """ 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 mark_set: self.last_set = reg if not self.infinite_regs and reg not in self.valid_regs: raise RuntimeError("Invalid register: {}".format(reg)) self.vals[reg] = val.unsigned() return True def get(self, reg: str, mark_read: bool = True) -> Int32: """ Returns the contents of register reg :param reg: The register name :param mark_read: If the register should be marked as "last read" (only used internally) :return: The contents of register reg """ # 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 mark_read: self.last_read = reg return self.vals[reg] def get_f(self, reg: str) -> BaseFloat: if not self.infinite_regs and reg not in self.float_regs: raise RuntimeError("Invalid float register: {}".format(reg)) return self.float_vals[reg] def set_f(self, reg: str, val: BaseFloat): if not self.infinite_regs and reg not in self.float_regs: raise RuntimeError("Invalid float register: {}".format(reg)) self.float_vals[reg] = self._float_type.bitcast(val) @staticmethod def named_registers(): """ Return all named registers :return: The list """ return ["zero", "ra", "sp", "gp", "tp", "fp"] def __repr__(self): return "".format( self.flen, "{" + ", ".join(self._reg_repr("a{}".format(i), 2, "0x") for i in range(8)) + "}", ) riscemu-2.2.5/riscemu/core/rtclock.py000066400000000000000000000012531451200553000175750ustar00rootroot00000000000000from time import time from typing import Optional from . import UInt32 class RTClock: """ Represents a realtime clock. Can be set up to start at a certain time t0, and has a certain tickrate. """ tickrate: int t0: float def __init__(self, tickrate: int, t0: Optional[float] = None): if t0 is None: self.t0 = time() self.tickrate = tickrate def get_low32(self, *args) -> UInt32: return UInt32(int((time() - self.t0) * self.tickrate)) def get_hi32(self, *args) -> UInt32: return UInt32(int((time() - self.t0) * self.tickrate) >> 32) def get_as_int(self): return time() * self.tickrate riscemu-2.2.5/riscemu/core/simple_instruction.py000066400000000000000000000031561451200553000220720ustar00rootroot00000000000000import re from typing import Union, Tuple from functools import lru_cache from . import ( Instruction, T_RelativeAddress, InstructionContext, Immediate, NumberFormatException, ) from ..helpers import parse_numeric_argument _NUM_LABEL_RE = re.compile(r"[0-9][fb]") _INT_IMM_RE = re.compile(r"[+-]?([0-9]+|0x[A-Fa-f0-9]+)") 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, ): self.context = context self.name = name self.args = args self._addr = addr @property def addr(self) -> int: return self._addr + self.context.base_address @lru_cache(maxsize=None) def get_imm(self, num: int) -> Immediate: token = self.args[num] if _INT_IMM_RE.fullmatch(token): value = parse_numeric_argument(token) return Immediate(abs_value=value, pcrel_value=value - self.addr) # resolve label correctly if _NUM_LABEL_RE.fullmatch(token): value = self.context.resolve_numerical_label(token, self.addr) else: value = self.context.resolve_label(token) # TODO: make it raise a nice error instead if value is None: raise NumberFormatException( "{} is neither a number now a known symbol".format(token) ) return Immediate(abs_value=value, pcrel_value=value - self.addr) def get_reg(self, num: int) -> str: return self.args[num] riscemu-2.2.5/riscemu/core/traps.py000066400000000000000000000060431451200553000172670ustar00rootroot00000000000000from enum import Enum from ..colors import FMT_PARSE, FMT_NONE from .privmodes import PrivModes from .csr_constants import MCAUSE_TRANSLATION from .exceptions import RiscemuBaseException from .int32 import UInt32 from .instruction import InstructionWithEncoding class CpuTrapType(Enum): TIMER = 1 SOFTWARE = 2 EXTERNAL = 3 EXCEPTION = 4 class CpuTrap(BaseException): code: int """ 31-bit value encoding the exception code in the mstatus register """ interrupt: int """ The isInterrupt bit in the mstatus register """ mtval: UInt32 """ contents of the mtval register """ type: CpuTrapType """ The type (timer, external, software) of the trap """ priv: PrivModes """ The privilege level this trap targets """ 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) self.priv = priv self.type = type @property def mcause(self): return (self.interrupt << 31) + self.code def message(self) -> str: return "" def __repr__(self): 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 ) return "{} {{priv={}, type={}, mtval={:x}}} {}".format( name, self.priv.name, self.type.name, self.mtval, self.message() ) def __str__(self): return self.__repr__() class IllegalInstructionTrap(CpuTrap): def __init__(self, ins: InstructionWithEncoding): super().__init__(2, ins.encoding, CpuTrapType.EXCEPTION) class InstructionAddressMisalignedTrap(CpuTrap): def __init__(self, addr: int): super().__init__(0, addr, CpuTrapType.EXCEPTION) class InstructionAccessFault(CpuTrap): def __init__(self, addr: int): super().__init__(1, addr, CpuTrapType.EXCEPTION) class TimerInterrupt(CpuTrap): def __init__(self): super().__init__(7, 0, CpuTrapType.TIMER) class EcallTrap(CpuTrap): def __init__(self, mode: PrivModes): super().__init__(mode.value + 8, 0, CpuTrapType.EXCEPTION) class InvalidElfException(RiscemuBaseException): def __init__(self, msg: str): super().__init__() self.msg = msg def message(self): return ( FMT_PARSE + '{}("{}")'.format(self.__class__.__name__, self.msg) + FMT_NONE ) class LoadAccessFault(CpuTrap): def __init__(self, msg, addr, size, op): super(LoadAccessFault, self).__init__(5, addr, CpuTrapType.EXCEPTION) self.msg = msg self.addr = addr self.size = size self.op = op def message(self): return "(During {} at 0x{:08x} of size {}: {})".format( self.op, self.addr, self.size, self.msg ) riscemu-2.2.5/riscemu/core/usermode_cpu.py000066400000000000000000000075671451200553000206440ustar00rootroot00000000000000""" RiscEmu (c) 2021-2022 Anton Lydike SPDX-License-Identifier: MIT This file contains the CPU logic (not the individual instruction sets). See instructions/instruction_set.py for more info on them. """ import typing from typing import List, Type from ..config import RunConfig from ..colors import FMT_CPU, FMT_NONE, FMT_ERROR from ..debug import launch_debug_session from ..syscall import SyscallInterface, get_syscall_symbols from . import ( CPU, Int32, BinaryDataMemorySection, MMU, RiscemuBaseException, LaunchDebuggerException, PrivModes, ) if typing.TYPE_CHECKING: from ..instructions import InstructionSet class UserModeCPU(CPU): """ This class represents a single CPU. It holds references to it's mmu, registers and syscall interrupt handler. It is initialized with a configuration and a list of instruction sets. """ def __init__(self, instruction_sets: List[Type["InstructionSet"]], conf: RunConfig): """ Creates a CPU instance. :param instruction_sets: A list of instruction set classes. These must inherit from the InstructionSet class """ # setup CPU states super().__init__(MMU(), instruction_sets, conf) self.exit_code = 0 # setup syscall interface self.syscall_int = SyscallInterface() # add global syscall symbols, but don't overwrite any user-defined symbols syscall_symbols = get_syscall_symbols() syscall_symbols.update(self.mmu.global_symbols) self.mmu.global_symbols.update(syscall_symbols) self.mode = PrivModes.USER def step(self, verbose: bool = False): """ Execute a single instruction, then return. """ launch_debugger = False try: self.cycle += 1 ins = self.mmu.read_ins(self.pc) if verbose: print(FMT_CPU + " 0x{:08X}:{} {}".format(self.pc, FMT_NONE, ins)) self.pc += self.INS_XLEN self.run_instruction(ins) except RiscemuBaseException as ex: if isinstance(ex, LaunchDebuggerException): # if the debugger is active, raise the exception to if self.debugger_active: raise ex 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) self.halted = True if launch_debugger: launch_debug_session(self) def run(self, verbose: bool = 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 ) def setup_stack(self, stack_size: int = 1024 * 4) -> bool: """ Create program stack and populate stack pointer :param stack_size: the size of the required stack, defaults to 4Kib :return: """ stack_sec = BinaryDataMemorySection( bytearray(stack_size), ".stack", None, # FIXME: why does a binary data memory section require a context? "", 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)) if self.conf.verbosity > 1: print( FMT_CPU + "[CPU] Created stack of size {} at 0x{:x}".format( stack_size, stack_sec.base ) + FMT_NONE ) return True riscemu-2.2.5/riscemu/debug.py000066400000000000000000000043001451200553000162660ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ import os.path from .core import SimpleInstruction from .helpers import * if typing.TYPE_CHECKING: pass HIST_FILE = os.path.join(os.path.expanduser("~"), ".riscemu_history") def launch_debug_session(cpu: "CPU", prompt=""): if cpu.debugger_active: return import code import readline import rlcompleter # set the active debug flag cpu.debugger_active = True # setup some aliases: registers = cpu.regs regs = cpu.regs memory = cpu.mmu mem = cpu.mmu mmu = cpu.mmu # setup helper functions: def dump(what, *args, **kwargs): if what == regs: regs.dump(*args, **kwargs) else: mmu.dump(what, *args, **kwargs) def dump_stack(*args, **kwargs): mmu.dump(regs.get("sp"), *args, **kwargs) def ins(): print("Current instruction at 0x{:08X}:".format(cpu.pc)) return mmu.read_ins(cpu.pc) def run_ins(name, *args: str): if len(args) > 3: print("Invalid arg count!") return context = mmu.context_for(cpu.pc) ins = SimpleInstruction(name, tuple(args), context, cpu.pc) print(FMT_DEBUG + "Running instruction {}".format(ins) + FMT_NONE) cpu.run_instruction(ins) def cont(verbose=False): try: cpu.run(verbose) except LaunchDebuggerException: print(FMT_DEBUG + "Returning to debugger...") return def step(): try: cpu.step(verbose=True) except LaunchDebuggerException: return # collect all variables sess_vars = globals() sess_vars.update(locals()) # add tab completion readline.set_completer(rlcompleter.Completer(sess_vars).complete) readline.parse_and_bind("tab: complete") if os.path.exists(HIST_FILE): readline.read_history_file(HIST_FILE) relaunch_debugger = False try: code.InteractiveConsole(sess_vars).interact( banner=FMT_DEBUG + prompt + FMT_NONE, exitmsg="Exiting debugger", ) finally: cpu.debugger_active = False readline.write_history_file(HIST_FILE) riscemu-2.2.5/riscemu/decoder/000077500000000000000000000000001451200553000162365ustar00rootroot00000000000000riscemu-2.2.5/riscemu/decoder/__init__.py000066400000000000000000000002051451200553000203440ustar00rootroot00000000000000from .decoder import decode, RISCV_REGS from .formatter import format_ins __all__ = [ decode, RISCV_REGS, format_ins, ] riscemu-2.2.5/riscemu/decoder/__main__.py000066400000000000000000000011151451200553000203260ustar00rootroot00000000000000if __name__ == "__main__": import code import readline import rlcompleter from .decoder import * from .formats import * from .instruction_table import * from .regs import RISCV_REGS sess_vars = globals() sess_vars.update(locals()) 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="Interactive decoding session started...", exitmsg="Closing..." ) riscemu-2.2.5/riscemu/decoder/decoder.py000066400000000000000000000051301451200553000202140ustar00rootroot00000000000000from .instruction_table import * 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}" ) STATIC_INSN: Dict[int, Tuple[str, List[int], int]] = { 0x00000013: ("nop", [], 0x00000013), 0x00008067: ("ret", [], 0x00008067), 0xFE010113: ("addi", [2, 2, -32], 0xFE010113), 0x02010113: ("addi", [2, 2, 32], 0x02010113), 0x00100073: ("ebreak", [], 0x00100073), 0x00000073: ("ecall", [], 0x00000073), 0x30200073: ("mret", [], 0x30200073), 0x00200073: ("uret", [], 0x00200073), 0x10200073: ("sret", [], 0x10200073), 0x10500073: ("wfi", [], 0x10500073), } def int_from_ins(insn: bytearray): return int.from_bytes(insn, "little") def name_from_insn(ins: int): opcode = op(ins) if opcode not in RV32: print_ins(ins) raise RuntimeError(f"Invalid opcode: {opcode:0x} in insn {ins:x}") dec = RV32[opcode] if isinstance(dec, str): return dec fun3 = funct3(ins) if fun3 not in dec: print_ins(ins) raise RuntimeError(f"Invalid funct3: {fun3:0x} in insn {ins:x}") dec = dec[fun3] if isinstance(dec, str): return dec if opcode == 0x1C and fun3 == 0: # we have ecall/ebreak token = imm110(ins) if token in dec: return dec[token] print_ins(ins) raise RuntimeError(f"Invalid instruction in ebreak/ecall region: {ins:x}") fun7 = funct7(ins) if opcode == 0b1011 and fun3 == 0b10: # ignore the two aq/lr bits located in the fun7 block # riscemu has no memory reordering, therefore we don't need to look at these bits ever fun7 = fun7 >> 2 if fun7 in dec: if opcode == 0x0C or (opcode == 0x04 and fun3 == 5): dec = dec[fun7] return dec print("unknown instruction?!") return dec[fun7] print_ins(ins) raise RuntimeError(f"Invalid instruction: {ins:x}") def decode(ins: Union[bytearray, bytes]) -> Tuple[str, List[int], int]: insn = int_from_ins(ins) if insn & 3 != 3: print_ins(insn) raise RuntimeError("Not a RV32 instruction!") if insn in STATIC_INSN: return STATIC_INSN[insn] opcode = op(insn) if opcode not in INSTRUCTION_ARGS_DECODER: print_ins(insn) raise RuntimeError("No instruction decoder found for instruction") return name_from_insn(insn), INSTRUCTION_ARGS_DECODER[opcode](insn), insn riscemu-2.2.5/riscemu/decoder/formats.py000066400000000000000000000043531451200553000202700ustar00rootroot00000000000000from typing import Dict, Callable, List, Union from .regs import RISCV_REGS def op(ins: int): return (ins >> 2) & 31 def rd(ins: int): return (ins >> 7) & 31 def funct3(ins: int): return (ins >> 12) & 7 def rs1(ins: int): return (ins >> 15) & 31 def rs2(ins: int): return (ins >> 20) & 31 def funct7(ins: int): return ins >> 25 def imm110(ins: int): return ins >> 20 def imm3112(ins: int): return ins >> 12 def imm_i(ins: int): return sign_extend(imm110(ins), 12) def imm_s(ins: int): num = (funct7(ins) << 5) + rd(ins) return sign_extend(num, 12) def imm_b(ins: int): lower = rd(ins) higher = funct7(ins) num = ( (lower & 0b11110) + ((higher & 0b0111111) << 5) + ((lower & 1) << 11) + ((higher >> 6) << 12) ) return sign_extend(num, 13) def imm_u(ins: int): return sign_extend(imm3112(ins), 20) 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, ) def sign_extend(num, bits): sign_mask = 1 << (bits - 1) return (num & (sign_mask - 1)) - (num & sign_mask) def decode_i(ins: int) -> List[int]: return [rd(ins), rs1(ins), imm_i(ins)] def decode_b(ins: int) -> List[int]: return [rs1(ins), rs2(ins), imm_b(ins)] def decode_u(ins: int) -> List[int]: return [rd(ins), imm_u(ins)] def decode_r(ins: int) -> List[int]: return [rd(ins), rs1(ins), rs2(ins)] def decode_s(ins: int) -> List[int]: return [rs2(ins), rs1(ins), imm_s(ins)] def decode_j(ins: int) -> List[int]: return [rd(ins), imm_j(ins)] def decode_i_shamt(ins: int) -> List[int]: if funct3(ins) in (1, 5): return [rd(ins), rs1(ins), rs2(ins)] return decode_i(ins) def decode_i_unsigned(ins: int) -> List[int]: return [rd(ins), rs1(ins), imm110(ins)] INSTRUCTION_ARGS_DECODER: Dict[int, Callable[[int], List[int]]] = { 0x00: decode_i, 0x04: decode_i_shamt, 0x05: decode_u, 0x08: decode_s, 0x0C: decode_r, 0x0D: decode_u, 0x18: decode_b, 0x19: decode_i, 0x1B: decode_j, 0x1C: decode_i_unsigned, 0b1011: decode_r, } riscemu-2.2.5/riscemu/decoder/formatter.py000066400000000000000000000025051451200553000206150ustar00rootroot00000000000000from .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 def int_to_hex(num: int): if num < 0: return f"-0x{-num:x}" return f"0x{num:x}" def format_ins(ins: int, name: str, fmt: str = "int"): opcode = op(ins) if fmt == "hex": fmt = int_to_hex else: fmt = str if opcode not in INSTRUCTION_ARGS_DECODER: return f"{name} " decoder = INSTRUCTION_ARGS_DECODER[opcode] if name in ("ecall", "ebreak", "mret", "sret", "uret"): return name if opcode in (0x8, 0x0): r1, r2, imm = decoder(ins) return f"{name:<7} {RISCV_REGS[r1]}, {imm}({RISCV_REGS[r2]})" elif decoder in (decode_i, decode_i_unsigned, decode_b, decode_i_shamt, decode_s): r1, r2, imm = decoder(ins) r1, r2 = RISCV_REGS[r1], RISCV_REGS[r2] return f"{name:<7} {r1}, {r2}, {fmt(imm)}" elif decoder in (decode_r,): rd, rs1, rs2 = [RISCV_REGS[x] for x in decoder(ins)] return f"{name:<7} {rd}, {rs1}, {rs2}" elif decoder in (decode_j, decode_u): r1, imm = decoder(ins) return f"{name:<7} {RISCV_REGS[r1]}, {fmt(imm)}" return f"{name} " riscemu-2.2.5/riscemu/decoder/instruction_table.py000066400000000000000000000034621451200553000223450ustar00rootroot00000000000000from collections import defaultdict from .formats import * tbl = lambda: defaultdict(tbl) RV32 = tbl() RV32[0x1B] = "jal" RV32[0x0D] = "lui" RV32[0x05] = "auipc" RV32[0x19][0] = "jalr" RV32[0x04][0] = "addi" RV32[0x04][1] = "slli" RV32[0x04][2] = "slti" RV32[0x04][3] = "sltiu" RV32[0x04][4] = "xori" RV32[0x04][5][0x00] = "srli" RV32[0x04][5][0x20] = "srai" RV32[0x04][6] = "ori" RV32[0x04][7] = "andi" RV32[0x18][0] = "beq" RV32[0x18][1] = "bne" RV32[0x18][4] = "blt" RV32[0x18][5] = "bge" RV32[0x18][6] = "bltu" RV32[0x18][7] = "bgeu" RV32[0x00][0] = "lb" RV32[0x00][1] = "lh" RV32[0x00][2] = "lw" RV32[0x00][4] = "lbu" RV32[0x00][5] = "lhu" 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][0][0] = "ecall" RV32[0x1C][0][1] = "ebreak" 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][5][32] = "sra" RV32[0x0C][6][0] = "or" RV32[0x0C][7][0] = "and" # rv32m RV32[0x0C][0][1] = "mul" RV32[0x0C][1][1] = "mulh" RV32[0x0C][2][1] = "mulhsu" RV32[0x0C][3][1] = "mulhu" RV32[0x0C][4][1] = "div" RV32[0x0C][5][1] = "divu" RV32[0x0C][6][1] = "rem" RV32[0x0C][7][1] = "remu" # rv32a RV32[0b1011][0b10][0b00010] = "lr.w" RV32[0b1011][0b10][0b00011] = "sc.w" RV32[0b1011][0b10][0b00001] = "amoswap.w" RV32[0b1011][0b10][0b00000] = "amoadd.w" RV32[0b1011][0b10][0b00100] = "amoxor.w" RV32[0b1011][0b10][0b01100] = "amoand.w" RV32[0b1011][0b10][0b01000] = "amoor.w" RV32[0b1011][0b10][0b10000] = "amomin.w" RV32[0b1011][0b10][0b10100] = "amomax.w" RV32[0b1011][0b10][0b11000] = "amominu.w" RV32[0b1011][0b10][0b11100] = "amomaxu.w" riscemu-2.2.5/riscemu/decoder/regs.py000066400000000000000000000005251451200553000175520ustar00rootroot00000000000000RISCV_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", ] riscemu-2.2.5/riscemu/helpers.py000066400000000000000000000064111451200553000166470ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ from math import log10, ceil from typing import Iterable, Iterator, TypeVar, Generic, List, Optional from .core import Int32, UInt32 from .core.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) def parse_numeric_argument(arg: str) -> int: """ parse hex or int strings """ try: 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), ) 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)] def apply_highlight(item, ind, hi_ind): """ applies some highlight such as underline to item if ind == hi_ind """ if ind == hi_ind: return FMT_UNDERLINE + FMT_ORANGE + item + FMT_NONE return item def highlight_in_list(items, hi_ind, joiner=" "): 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": 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": 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((repr(chr(b))[1:-1] for b in byte_arr), highlight, "") T = TypeVar("T") class Peekable(Generic[T], Iterator[T]): def __init__(self, iterable: Iterable[T]): self.iterable = iter(iterable) self.cache: List[T] = list() def __iter__(self) -> Iterator[T]: return self def __next__(self) -> T: if self.cache: return self.cache.pop() return next(self.iterable) def peek(self) -> Optional[T]: try: if self.cache: return self.cache[0] pop = next(self.iterable) self.cache.append(pop) return pop except StopIteration: return None def push_back(self, item: T): self.cache = [item] + self.cache def is_empty(self) -> bool: return self.peek() is None 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] riscemu-2.2.5/riscemu/instructions/000077500000000000000000000000001451200553000173755ustar00rootroot00000000000000riscemu-2.2.5/riscemu/instructions/RV32A.py000066400000000000000000000061611451200553000205500ustar00rootroot00000000000000from .instruction_set import InstructionSet, Instruction from riscemu.core.exceptions import INS_NOT_IMPLEMENTED from ..core import Int32, UInt32 class RV32A(InstructionSet): """ The RV32A instruction set. Currently, load-reserved and store conditionally are not supported due to limitations in the way the MMU is implemented. Maybe a later implementation will add support for this? """ def instruction_lr_w(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) def instruction_sc_w(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) def instruction_amoswap_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) if dest == "zero": self.mmu.write(addr.unsigned_value, val.to_bytes()) else: old = Int32(self.mmu.read(addr.unsigned_value, 4)) self.mmu.write(addr.unsigned_value, val.to_bytes()) self.regs.set(dest, old) def instruction_amoadd_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr.unsigned_value, 4)) self.mmu.write(addr.unsigned_value, (old + val).to_bytes(4)) self.regs.set(dest, old) def instruction_amoand_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr.unsigned_value, 4)) self.mmu.write(addr.unsigned_value, (old & val).to_bytes(4)) self.regs.set(dest, old) def instruction_amoor_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr.unsigned_value, 4)) self.mmu.write(addr.unsigned_value, (old | val).to_bytes(4)) self.regs.set(dest, old) def instruction_amoxor_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr.unsigned_value, 4)) self.mmu.write(addr.unsigned_value, (old ^ val).to_bytes(4)) self.regs.set(dest, old) def instruction_amomax_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr.unsigned_value, 4)) self.mmu.write(addr.unsigned_value, max(old, val).to_bytes(4)) self.regs.set(dest, old) def instruction_amomaxu_w(self, ins: "Instruction"): val: UInt32 dest, addr, val = self.parse_rd_rs_rs(ins) old = UInt32(self.mmu.read(addr.unsigned_value, 4)) self.mmu.write(addr.unsigned_value, max(old, val.unsigned()).to_bytes()) self.regs.set(dest, old) def instruction_amomin_w(self, ins: "Instruction"): dest, addr, val = self.parse_rd_rs_rs(ins) old = Int32(self.mmu.read(addr.unsigned_value, 4)) self.mmu.write(addr.unsigned_value, min(old, val).to_bytes(4)) self.regs.set(dest, old) def instruction_amominu_w(self, ins: "Instruction"): val: UInt32 dest, addr, val = self.parse_rd_rs_rs(ins) old = UInt32(self.mmu.read(addr.unsigned_value, 4)) self.mmu.write(addr.unsigned_value, min(old, val.unsigned()).to_bytes(4)) self.regs.set(dest, old) riscemu-2.2.5/riscemu/instructions/RV32D.py000066400000000000000000000010771451200553000205540ustar00rootroot00000000000000""" 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 .float_base import FloatArithBase from riscemu.core import INS_NOT_IMPLEMENTED, Float32, Int32, UInt32, Float64 class RV32D(FloatArithBase[Float64]): flen = 64 _float_cls = Float64 riscemu-2.2.5/riscemu/instructions/RV32F.py000066400000000000000000000114761451200553000205620ustar00rootroot00000000000000""" 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 .instruction_set import Instruction from .float_base import FloatArithBase from riscemu.core import Float32, Int32, UInt32 class RV32F(FloatArithBase[Float32]): flen = 32 _float_cls = Float32 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(int(self.regs.get_f(rs).value))) 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(int(self.regs.get_f(rs).value))) 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).bytes[-4:])) 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(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(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).to_bytes())) riscemu-2.2.5/riscemu/instructions/RV32I.py000066400000000000000000000225451451200553000205640ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ from .instruction_set import InstructionSet, ASSERT_LEN from ..colors import FMT_DEBUG, FMT_NONE from ..syscall import Syscall from ..core import Instruction, Int32, UInt32, UserModeCPU, LaunchDebuggerException class RV32I(InstructionSet): """ The RV32I instruction set. Some instructions are missing, such as fence, fence.i, rdcycle, rdcycleh, rdtime, rdtimeh, rdinstret, rdinstreth All atomic read/writes are also not implemented yet See https://maxvytech.com/images/RV32I-11-2018.pdf for a more detailed overview """ def instruction_lb(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, UInt32.sign_extend(self.mmu.read(addr.unsigned_value, 1), 8)) def instruction_lh(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, UInt32.sign_extend(self.mmu.read(addr.unsigned_value, 2), 16)) def instruction_lw(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, UInt32(self.mmu.read(addr.unsigned_value, 4))) def instruction_lbu(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, UInt32(self.mmu.read(addr.unsigned_value, 1))) def instruction_lhu(self, ins: "Instruction"): rd, addr = self.parse_mem_ins(ins) self.regs.set(rd, UInt32(self.mmu.read(addr.unsigned_value, 2))) 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"): 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"): 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.abs_value & 0b11111)) 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).shift_right_logical(self.regs.get(src2) & 0b11111) ) 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).shift_right_logical(imm.abs_value & 0b11111) ) 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.abs_value & 0b11111)) def instruction_add(self, ins: "Instruction"): dst, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(dst, rs1 + rs2) def instruction_addi(self, ins: "Instruction"): dst, rs1, imm = self.parse_rd_rs_imm(ins) self.regs.set(dst, rs1 + imm.abs_value) def instruction_sub(self, ins: "Instruction"): dst, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(dst, rs1 - rs2) def instruction_lui(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) self.regs.set(reg, ins.get_imm(1).abs_value << 12) def instruction_auipc(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) imm = ins.get_imm(1).abs_value << 12 self.regs.set(reg, imm + self.cpu.pc) def instruction_xor(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(rd, rs1 ^ rs2) def instruction_xori(self, ins: "Instruction"): rd, rs1, imm = self.parse_rd_rs_imm(ins) self.regs.set(rd, rs1 ^ imm.abs_value) def instruction_or(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(rd, rs1 | rs2) def instruction_ori(self, ins: "Instruction"): rd, rs1, imm = self.parse_rd_rs_imm(ins) self.regs.set(rd, rs1 | imm.abs_value) def instruction_and(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(rd, rs1 & rs2) def instruction_andi(self, ins: "Instruction"): rd, rs1, imm = self.parse_rd_rs_imm(ins) self.regs.set(rd, rs1 & imm.abs_value) def instruction_slt(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(rd, UInt32(rs1 < rs2)) def instruction_slti(self, ins: "Instruction"): rd, rs1, imm = self.parse_rd_rs_imm(ins) self.regs.set(rd, UInt32(rs1 < imm)) def instruction_sltu(self, ins: "Instruction"): dst, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(dst, UInt32(rs1.unsigned_value < rs2.unsigned_value)) def instruction_sltiu(self, ins: "Instruction"): dst, rs1, imm = self.parse_rd_rs_imm(ins) self.regs.set(dst, UInt32(rs1.unsigned_value < imm.abs_value.unsigned_value)) def instruction_beq(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 == rs2: self.cpu.pc += dst.pcrel_value.value - 4 def instruction_bne(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 != rs2: self.cpu.pc += dst.pcrel_value.value - 4 def instruction_blt(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 < rs2: self.cpu.pc += dst.pcrel_value.value - 4 def instruction_bge(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 >= rs2: self.cpu.pc += dst.pcrel_value.value - 4 def instruction_bltu(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1.unsigned_value < rs2.unsigned_value: self.cpu.pc += dst.pcrel_value.value - 4 def instruction_bgeu(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1.unsigned_value >= rs2.unsigned_value: self.cpu.pc += dst.pcrel_value.value - 4 def instruction_j(self, ins: "Instruction"): ASSERT_LEN(ins.args, 1) addr = ins.get_imm(0) self.cpu.pc += addr.pcrel_value.value - 4 def instruction_jal(self, ins: "Instruction"): reg = "ra" # default register is ra if len(ins.args) == 1: addr = ins.get_imm(0) else: ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) addr = ins.get_imm(1) self.regs.set(reg, UInt32(self.cpu.pc)) self.cpu.pc += addr.pcrel_value.value - 4 def instruction_jalr(self, ins: "Instruction"): ASSERT_LEN(ins.args, 3) reg = ins.get_reg(0) base = ins.get_reg(1) addr = ins.get_imm(2).abs_value.value self.regs.set(reg, Int32(self.cpu.pc)) self.cpu.pc = self.regs.get(base).unsigned_value + addr def instruction_ret(self, ins: "Instruction"): ASSERT_LEN(ins.args, 0) self.cpu.pc = self.regs.get("ra").unsigned_value def instruction_ecall(self, ins: "Instruction"): self.instruction_scall(ins) def instruction_ebreak(self, ins: "Instruction"): self.instruction_sbreak(ins) 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) self.cpu.syscall_int.handle_syscall(syscall) 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.cpu.pc - 1) + FMT_NONE ) raise LaunchDebuggerException() def instruction_nop(self, ins: "Instruction"): ASSERT_LEN(ins.args, 0) pass def instruction_li(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) immediate = ins.get_imm(1).abs_value self.regs.set(reg, Int32(immediate)) def instruction_la(self, ins: "Instruction"): ASSERT_LEN(ins.args, 2) reg = ins.get_reg(0) immediate = ins.get_imm(1).abs_value self.regs.set(reg, Int32(immediate)) 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)) riscemu-2.2.5/riscemu/instructions/RV32M.py000066400000000000000000000024541451200553000205650ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ from .instruction_set import * from riscemu.core.exceptions import INS_NOT_IMPLEMENTED class RV32M(InstructionSet): """ The RV32M Instruction set, containing multiplication and division instructions """ def instruction_mul(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(rd, rs1 * rs2) def instruction_mulh(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(rd, (rs1 * rs2) >> 32) def instruction_mulhsu(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) def instruction_mulhu(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) def instruction_div(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(rd, rs1 // rs2) def instruction_divu(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False) self.regs.set(rd, rs1 // rs2) def instruction_rem(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set(rd, rs1 % rs2) def instruction_remu(self, ins: "Instruction"): rd, rs1, rs2 = self.parse_rd_rs_rs(ins, signed=False) self.regs.set(rd, rs1 % rs2) riscemu-2.2.5/riscemu/instructions/RV_Debug.py000066400000000000000000000035711451200553000214120ustar00rootroot00000000000000from typing import Union from .instruction_set import InstructionSet, Instruction from ..core import BaseFloat, Int32, Float32 class RV_Debug(InstructionSet): def instruction_print(self, ins: Instruction): reg = ins.get_reg(0) if len(ins.args) == 2: msg = ins.args[1] print(msg.format(reg, self.regs.get(reg))) else: print("register {} contains value {}".format(reg, self.regs.get(reg))) def instruction_printf(self, ins: Instruction): fmt_str = ins.args[0] regs = tuple(self.smart_get_reg(x) for x in ins.args[1:]) print(fmt_str.format(*regs)) 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_float_s(self, ins: Instruction): reg = ins.get_reg(0) print( "register {} contains value {}".format( reg, Float32.bitcast(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) ) ) def smart_get_reg(self, reg_name: str) -> Union[Int32, BaseFloat]: if reg_name[0] == "f": return self.regs.get_f(reg_name) return self.regs.get(reg_name) riscemu-2.2.5/riscemu/instructions/Zicsr.py000066400000000000000000000061111451200553000210400ustar00rootroot00000000000000from typing import Tuple from .instruction_set import InstructionSet, Instruction from ..core import Int32, UInt32 from ..core.csr_constants import CSR_NAME_TO_ADDR from ..helpers import parse_numeric_argument class Zicsr(InstructionSet): def instruction_csrrw(self, ins: Instruction): rd, new_val, csr = self._parse_csr_ins(ins) if rd != "zero": self.regs.set(rd, self.cpu.csr.get(csr)) self.cpu.csr.set(csr, new_val) def instruction_csrrwi(self, ins: Instruction): rd, new_val, csr = self.parse_csr_imm_ins(ins) if rd != "zero": self.regs.set(rd, self.cpu.csr.get(csr)) self.cpu.csr.set(csr, new_val) def instruction_csrrs(self, ins: Instruction): rd, bitmask, csr = self._parse_csr_ins(ins) val = self.cpu.csr.get(csr) if rd != "zero": self.regs.set(rd, val) if bitmask != 0: self.cpu.csr.set(csr, val | bitmask) def instruction_csrrsi(self, ins: Instruction): rd, bitmask, csr = self.parse_csr_imm_ins(ins) val = self.cpu.csr.get(csr) if rd != "zero": self.regs.set(rd, val) if bitmask != 0: self.cpu.csr.set(csr, val | bitmask) def instruction_csrrc(self, ins: Instruction): rd, bitmask, csr = self._parse_csr_ins(ins) val = self.cpu.csr.get(csr) if rd != "zero": self.regs.set(rd, val) if bitmask != 0: self.cpu.csr.set(csr, val & ~bitmask) def instruction_csrrci(self, ins: Instruction): rd, bitmask, csr = self.parse_csr_imm_ins(ins) val = self.cpu.csr.get(csr) if rd != "zero": self.regs.set(rd, val) if bitmask != 0: self.cpu.csr.set(csr, val & ~bitmask) def instruction_rdtime(self, ins: Instruction): rd = ins.get_reg(0) self.regs.set(rd, self.cpu.rtclock.get_low32()) def instruction_rdtimeh(self, ins: Instruction): rd = ins.get_reg(0) self.regs.set(rd, self.cpu.rtclock.get_hi32()) # FIXME: rdclycle[h] and rdinstret[h] are not provided yet def _parse_csr_ins(self, ins: Instruction) -> Tuple[str, UInt32, int]: assert len(ins.args) == 3 rd = ins.get_reg(0) rs = self.regs.get(ins.get_reg(1)).unsigned() return rd, rs, self._ins_get_csr_addr(ins) def parse_csr_imm_ins(self, ins: Instruction) -> Tuple[str, UInt32, int]: assert len(ins.args) == 3 rd = ins.get_reg(0) # make sure we only have 5 bit immediate values here rs = ins.get_imm(1).abs_value.unsigned() if rs > 0b11111: print(f"Warning! more than 5bit immediate value in csr ins {ins}!") rs = rs & 0b11111 return rd, rs, self._ins_get_csr_addr(ins) @staticmethod def _ins_get_csr_addr(ins: Instruction) -> int: # TODO: somehow handle elf instructions at some point? if isinstance(ins.args[2], str) and ins.args[2].lower() in CSR_NAME_TO_ADDR: return CSR_NAME_TO_ADDR[ins.args[2].lower()] return ins.get_imm(2).abs_value.unsigned_value riscemu-2.2.5/riscemu/instructions/__init__.py000066400000000000000000000012221451200553000215030ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT This package holds all instruction sets, available to the processor """ from .instruction_set import InstructionSet, Instruction from .RV32M import RV32M from .RV32I import RV32I from .RV32A import RV32A from .RV32F import RV32F from .RV32D import RV32D from .RV_Debug import RV_Debug from .Zicsr import Zicsr InstructionSetDict = { v.__name__: v for v in [RV32I, RV32M, RV32A, RV32F, RV32D, Zicsr, RV_Debug] } __all__ = [ "Instruction", "InstructionSet", "InstructionSetDict", "RV32I", "RV32M", "RV32A", "RV32F", "RV32D", "Zicsr", "RV_Debug", ] riscemu-2.2.5/riscemu/instructions/float_base.py000066400000000000000000000266221451200553000220560ustar00rootroot00000000000000from typing import ClassVar, Generic, TypeVar, Tuple, Iterable, Callable, Type from .instruction_set import InstructionSet, Instruction from riscemu.core import BaseFloat, CPU, INS_NOT_IMPLEMENTED, UInt32 _FloatT = TypeVar("_FloatT", bound=BaseFloat) class FloatArithBase(Generic[_FloatT], InstructionSet): flen: ClassVar[int] _float_cls: ClassVar[Type[BaseFloat]] def __init__(self, cpu: CPU): assert cpu.regs.flen >= self.flen, "{} implies cpu flen of at least {}".format( self.__class__.__name__, self.flen ) super().__init__(cpu) def base_fmadd(self, ins: Instruction): """ :Format: | fmadd.{s,d,q} rd,rs1,rs2,rs3 :Description: | Perform 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 base_fmsub(self, ins: Instruction): """ :Format: | fmsub.{s,d,q} rd,rs1,rs2,rs3 :Description: | Perform fused multiply subtraction. :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 base_fnmsub(self, ins: Instruction): """ :Format: | fnmsub.{s,d,q} rd,rs1,rs2,rs3 :Description: | Perform fused negated 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 base_fnmadd(self, ins: Instruction): """ :Format: | fnmadd.{s,d,q} rd,rs1,rs2,rs3 :Description: | Perform single-precision fused negated 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 base_fadd(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 base_fsub(self, ins: Instruction): """ :Format: | fsub.{s,d,q} rd,rs1,rs2 :Description: | Perform single-precision floating-point subtraction. :Implementation: | f[rd] = f[rs1] - f[rs2] """ rd, rs1, rs2 = self.parse_rd_rs_rs(ins) self.regs.set_f( rd, rs1 - rs2, ) def base_fmul(self, ins: Instruction): """ :Format: | fmul.{s,d,q} rd,rs1,rs2 :Description: | Perform 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 base_fdiv(self, ins: Instruction): """ :Format: | fdiv.{s,d,q} rd,rs1,rs2 :Description: | Perform 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 base_fsqrt(self, ins: Instruction): """ :Format: | fsqrt.{s,d,q} 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._float_cls.bitcast(self.regs.get_f(rs)) ** 0.5) def base_fsgnj(self, ins: Instruction): """ :Format: | fsgnj.{s,d,q} 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 base_fsgnjn(self, ins: Instruction): """ :Format: | fsgnjn.{s,d,q} 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 base_fsgnjx(self, ins: Instruction): """ :Format: | fsgnjx.{s,d,q} 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 base_fmin(self, ins: Instruction): """ :Format: | fmin.{s,d,q} rd,rs1,rs2 :Description: | Write the smaller of 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, min( rs1, rs2, ), ) def base_fmax(self, ins: Instruction): """ :Format: | fmax.{s,d,q} rd,rs1,rs2 :Description: | Write the larger of 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, max( rs1, rs2, ), ) def base_feq(self, ins: Instruction): """ :Format: | feq.{s,d,q} 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, UInt32(rs1 == rs2)) def base_flt(self, ins: Instruction): """ :Format: | flt.{s,d,q} 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, UInt32(rs1 < rs2)) def base_fle(self, ins: Instruction): """ :Format: | fle.{s,d,q} 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, UInt32(rs1 <= rs2)) def base_fclass(self, ins: Instruction): """ :Format: | fclass.{s,d,q} 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 base_load(self, ins: Instruction): """ :Format: | fl{w,d,q} rd,offset(rs1) :Description: | Load a 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) bytes = self.mmu.read(addr.value, self.flen // 8) self.regs.set_f(rd, self._float_cls(bytes)) def base_save(self, ins: Instruction): """:Format: | fs{w,d,q} rs2,offset(rs1) :Description: | Store a 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._float_cls.bitcast(self.regs.get_f(rs)) self.mmu.write(addr.value, self.flen // 8, bytearray(val.bytes)) def get_instructions(self) -> Iterable[Tuple[str, Callable[[Instruction], None]]]: yield from super().get_instructions() qual = {32: "s", 64: "d", 128: "q"}.get(self.flen) load_save_qual = {32: "w", 64: "d", 128: "q"}.get(self.flen) yield from ( ("fmadd." + qual, self.base_fmadd), ("fmsub." + qual, self.base_fmsub), ("fnmsub." + qual, self.base_fnmsub), ("fnmadd." + qual, self.base_fnmadd), ("fadd." + qual, self.base_fadd), ("fsub." + qual, self.base_fnmadd), ("fmul." + qual, self.base_fmul), ("fdiv." + qual, self.base_fdiv), ("fsqrt." + qual, self.base_fsqrt), ("fsgnj." + qual, self.base_fsgnj), ("fsgnjn." + qual, self.base_fsgnjn), ("fsgnjx." + qual, self.base_fsgnjx), ("fmin." + qual, self.base_fmin), ("fmax." + qual, self.base_fmax), ("feq." + qual, self.base_feq), ("flt." + qual, self.base_flt), ("fle." + qual, self.base_fle), ("fl" + load_save_qual, self.base_load), ("fs" + load_save_qual, self.base_save), ) 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, _FloatT, _FloatT]: assert len(ins.args) == 3 return ( ins.get_reg(0), self._float_cls.bitcast(self.regs.get_f(ins.get_reg(1))), self._float_cls.bitcast(self.regs.get_f(ins.get_reg(2))), ) def parse_rd_rs_rs_rs( self, ins: Instruction ) -> Tuple[str, _FloatT, _FloatT, _FloatT]: assert len(ins.args) == 4 return ( ins.get_reg(0), self._float_cls.bitcast(self.regs.get_f(ins.get_reg(1))), self._float_cls.bitcast(self.regs.get_f(ins.get_reg(2))), self._float_cls.bitcast(self.regs.get_f(ins.get_reg(3))), ) riscemu-2.2.5/riscemu/instructions/instruction_set.py000066400000000000000000000103521451200553000232040ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ from typing import Tuple, Callable, Dict, Union, Iterable from abc import ABC from ..core.exceptions import ASSERT_LEN from ..core import Instruction, Int32, UInt32, Immediate, CPU, Registers class InstructionSet(ABC): """ Represents a collection of instructions Each instruction set has to inherit from this class. Each instruction should be it's own method: instruction_(self, ins: LoadedInstruction) instructions containing a dot '.' should replace it with an underscore. """ 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]]: """ 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()} def get_instructions(self) -> Iterable[Tuple[str, Callable[[Instruction], None]]]: """ Returns a list of all valid instruction names included in this instruction set converts underscores in names to dots """ for member in dir(self): if member.startswith("instruction_"): yield member[12:].replace("_", "."), getattr(self, member) def parse_mem_ins(self, ins: "Instruction") -> Tuple[str, UInt32]: """ parses rd, imm(rs) argument format and returns (rd, imm+rs1) (so a register and address tuple for memory instructions) """ assert len(ins.args) == 3 # handle rd, rs1, imm rd = ins.get_reg(0) rs = self.regs.get(ins.get_reg(1)).unsigned() imm = ins.get_imm(2) return rd, rs + imm.abs_value.unsigned() def parse_rd_rs_rs( self, ins: "Instruction", signed=True ) -> Tuple[str, Int32, Int32]: """ Assumes the command is in 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.regs.get(ins.get_reg(1))), Int32(self.regs.get(ins.get_reg(2))), ) else: return ( ins.get_reg(0), UInt32(self.regs.get(ins.get_reg(1))), UInt32(self.regs.get(ins.get_reg(2))), ) def parse_rd_rs_imm(self, ins: "Instruction") -> Tuple[str, Int32, Immediate]: """ Assumes the command is in rd, rs, imm format Returns the name of rd, the value in rs and the immediate imm """ return ( ins.get_reg(0), Int32(self.regs.get(ins.get_reg(1))), ins.get_imm(2), ) def parse_rs_rs_imm(self, ins: "Instruction") -> Tuple[Int32, Int32, Immediate]: """ Assumes the command is in rs1, rs2, imm format Returns the values in rs1, rs2 and the immediate imm """ return ( Int32(self.regs.get(ins.get_reg(0))), Int32(self.regs.get(ins.get_reg(1))), ins.get_imm(2), ) def get_reg_content(self, ins: "Instruction", ind: int) -> Int32: """ get the register name from ins and then return the register contents """ return self.regs.get(ins.get_reg(ind)) @property def pc(self) -> int: """ shorthand for self.cpu.pc """ return self.cpu.pc @pc.setter def pc(self, val: Union[int, Int32]): if isinstance(val, Int32): val = val.unsigned_value self.cpu.pc = val @property def mmu(self): return self.cpu.mmu @property def regs(self) -> Registers: return self.cpu.regs def __repr__(self): return "InstructionSet[{}] with {} instructions".format( self.__class__.__name__, len(list(self.get_instructions())) ) riscemu-2.2.5/riscemu/interactive.py000066400000000000000000000017521451200553000175250ustar00rootroot00000000000000import sys from riscemu.config import RunConfig from riscemu.core import InstructionMemorySection, SimpleInstruction, Program if __name__ == "__main__": from core.usermode_cpu import UserModeCPU from .instructions import InstructionSetDict cpu = UserModeCPU(list(InstructionSetDict.values()), RunConfig(verbosity=4)) 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, ) ) cpu.load_program(program) cpu.setup_stack() cpu.launch() sys.exit(cpu.exit_code) riscemu-2.2.5/riscemu/libc/000077500000000000000000000000001451200553000155425ustar00rootroot00000000000000riscemu-2.2.5/riscemu/libc/README.md000066400000000000000000000012361451200553000170230ustar00rootroot00000000000000# 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! riscemu-2.2.5/riscemu/libc/crt0.s000066400000000000000000000005311451200553000165750ustar00rootroot00000000000000// 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 riscemu-2.2.5/riscemu/libc/stdlib.s000066400000000000000000000106461451200553000172160ustar00rootroot00000000000000// 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 riscemu-2.2.5/riscemu/libc/string.s000066400000000000000000000112171451200553000172360ustar00rootroot00000000000000// 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 occurrence of c in str // returns a pointer to the first occurrence, 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 riscemu-2.2.5/riscemu/parser.py000066400000000000000000000115731451200553000165060ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ import re from io import IOBase from typing import Dict, Tuple, Iterable, Callable, List, TextIO from .assembler import MemorySectionType, ParseContext, AssemblerDirectives from .colors import FMT_PARSE from .helpers import Peekable from .tokenizer import Token, TokenType, tokenize from .core import ( Program, T_ParserOpts, ProgramLoader, SimpleInstruction, ParseException, ) def parse_instruction(token: Token, args: Tuple[str], context: ParseContext): if context.section is None: 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() ) 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): # 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)) 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, } def parse_tokens(name: str, tokens_iter: Iterable[Token]) -> Program: """ Convert a token stream into a parsed program :param name: the programs name :param tokens_iter: the programs content, tokenized :return: a parsed program """ context = ParseContext(name) for token, args in composite_tokenizer(Peekable[Token](tokens_iter)): if token.type not in PARSERS: raise ParseException("Unexpected token type: {}, {}".format(token, args)) PARSERS[token.type](token, args, context) return context.finalize() 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)) The first token is either a pseudo_op, label, or instruction name. The token list are all remaining tokens before a newline is encountered :param tokens_iter: An iterator over tokens :return: An iterator over a slightly more structured representation of the tokens """ tokens: Peekable[Token] = Peekable[Token](tokens_iter) while not tokens.is_empty(): token = next(tokens) if token.type in ( TokenType.PSEUDO_OP, TokenType.LABEL, TokenType.INSTRUCTION_NAME, ): yield token, tuple(take_arguments(tokens)) def take_arguments(tokens: Peekable[Token]) -> Iterable[str]: """ Consumes (argument comma)* and yields argument.value until newline is reached If an argument is not followed by either a newline or a comma, a parse exception is raised The newline at the end is consumed :param tokens: A Peekable iterator over some Tokens """ while True: if tokens.peek().type == TokenType.ARGUMENT: yield next(tokens).value elif tokens.peek().type == TokenType.NEWLINE: next(tokens) break elif tokens.peek().type == TokenType.COMMA: next(tokens) else: break # raise ParseException("Expected newline, instead got {}".format(tokens.peek())) class AssemblyFileLoader(ProgramLoader): """ This class loads assembly files written by hand. It understands some assembler directives and supports most pseudo instructions. It does very little verification of source correctness. It also supports numbered jump targets and properly supports local and global scope (globl assembly directive) The AssemblyFileLoader loads .asm and .s files by default, and acts as a weak fallback to all other filetypes. """ is_binary = False source: TextIO def parse(self) -> Program: with self.source as f: return parse_tokens(self.filename, tokenize(f)) @classmethod def can_parse(cls, source_name: str) -> float: """ Parse a string assembly file. It also acts as a weak fallback if no other loaders want to take the file. """ # gcc recognizes these line endings as assembly. So we will do too. if source_name == "-" or source_name.split(".")[-1].lower() in ("asm", "s"): return 1 return 0.01 @classmethod def get_options(cls, argv: List[str]) -> Tuple[List[str], T_ParserOpts]: return argv, {} riscemu-2.2.5/riscemu/priv/000077500000000000000000000000001451200553000156115ustar00rootroot00000000000000riscemu-2.2.5/riscemu/priv/CSR.py000066400000000000000000000110061451200553000166100ustar00rootroot00000000000000from typing import Dict, Union, Callable, Optional from collections import defaultdict from core.privmodes import PrivModes from core.traps import InstructionAccessFault from ..colors import FMT_CSR, FMT_NONE from core.csr_constants import CSR_NAME_TO_ADDR, MSTATUS_LEN_2, MSTATUS_OFFSETS from ..core import UInt32 class CSR: """ This holds all Control and Status Registers (CSR) """ regs: Dict[int, UInt32] """ All Control and Status Registers are stored here """ virtual_regs: Dict[int, Callable[[], UInt32]] """ list of virtual CSR registers, with values computed on read """ listeners: Dict[int, Callable[[UInt32, UInt32], None]] mstatus_cache: Dict[str, UInt32] mstatus_cache_dirty = True def __init__(self): self.regs = defaultdict(lambda: UInt32(0)) self.listeners = defaultdict(lambda: (lambda x, y: None)) self.virtual_regs = dict() self.mstatus_cache = dict() # TODO: implement write masks (bitmasks which control writeable bits in registers def set(self, addr: Union[str, int], val: Union[int, UInt32]): addr = self._name_to_addr(addr) if addr is None: return val = UInt32(val) self.listeners[addr](self.regs[addr], val) if addr == 0x300: self.mstatus_cache_dirty = True self.regs[addr] = val def get(self, addr: Union[str, int]) -> UInt32: addr = self._name_to_addr(addr) if addr is None: raise RuntimeError(f"Invalid CSR name: {addr}!") if addr in self.virtual_regs: return self.virtual_regs[addr]() return self.regs[addr] 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)) return self.listeners[addr] = listener # mstatus properties def set_mstatus(self, name: str, val: UInt32): """ Set mstatus bits using this helper. mstatus is a 32 bit register, holding various machine status flags Setting them by hand is super painful, so this helper allows you to set specific bits. Please make sure your supplied value has the correct width! :param name: :param val: :return: """ size = 2 if name in MSTATUS_LEN_2 else 1 off = MSTATUS_OFFSETS[name] mask = (2**size - 1) << off old_val = self.get("mstatus") erased = old_val & (~mask) new_val = erased | (val << off) self.set("mstatus", new_val) def get_mstatus(self, name) -> UInt32: if not self.mstatus_cache_dirty and name in self.mstatus_cache: return self.mstatus_cache[name] 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 if self.mstatus_cache_dirty: self.mstatus_cache = dict(name=val) else: self.mstatus_cache[name] = val return val def callback(self, addr: Union[str, int]): def inner(func: Callable[[UInt32, UInt32], None]): self.set_listener(addr, func) return func return inner def assert_can_read(self, mode: PrivModes, addr: int): if (addr >> 8) & 3 > mode.value: raise InstructionAccessFault(addr) def assert_can_write(self, mode: PrivModes, addr: int): if (addr >> 8) & 3 > mode.value or addr >> 10 == 11: raise InstructionAccessFault(addr) def _name_to_addr(self, addr: Union[str, int]) -> Optional[int]: if isinstance(addr, str): if addr not in CSR_NAME_TO_ADDR: print("Unknown CSR register {}".format(addr)) return None return CSR_NAME_TO_ADDR[addr] return addr def virtual_register(self, addr: Union[str, int]): addr = self._name_to_addr(addr) if addr is None: print("unknown csr address name: {}".format(addr)) def inner(func: Callable[[], UInt32]): self.virtual_regs[addr] = func return func return inner def dump_mstatus(self): print(FMT_CSR + "[CSR] dumping mstatus:") i = 0 for name in MSTATUS_OFFSETS: print(" {:<5} {}".format(name, self.get_mstatus(name)), end="") if i % 6 == 5: print() i += 1 print(FMT_NONE) riscemu-2.2.5/riscemu/priv/ElfLoader.py000066400000000000000000000100571451200553000200230ustar00rootroot00000000000000import os.path from io import IOBase, RawIOBase from typing import List from core.traps import * from .types import ElfMemorySection from ..helpers import FMT_PARSE, FMT_NONE, FMT_GREEN, FMT_BOLD from ..core import MemoryFlags, Program, ProgramLoader, T_ParserOpts FMT_ELF = FMT_GREEN + FMT_BOLD if typing.TYPE_CHECKING: from elftools.elf.elffile import ELFFile from elftools.elf.sections import Section, SymbolTableSection INCLUDE_SEC = (".text", ".stack", ".bss", ".sdata", ".sbss") class ElfBinaryFileLoader(ProgramLoader): """ Loads compiled elf binaries (checks for the magic sequence 7f45 4c46) This loader respects local and global symbols. """ is_binary = True program: Program def __post_init__(self): self.program = Program(self.filename) @classmethod def can_parse(cls, source_name: str) -> float: if source_name == "-" or not os.path.isfile(source_name): return 0 with open(source_name, "rb") as f: if f.read(4) == b"\x7f\x45\x4c\x46": return 1 return 0 @classmethod def get_options(cls, argv: List[str]) -> [List[str], T_ParserOpts]: return argv, {} def parse(self) -> Program: try: from elftools.elf.elffile import ELFFile from elftools.elf.sections import Section, SymbolTableSection self._read_elf(ELFFile(self.source)) 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 ) raise e return self.program 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": 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) continue if sec.name not in INCLUDE_SEC: continue 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",) 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 ) return ElfMemorySection( data, sec.name, self.program.context, owner, sec.header.sh_addr, flags ) def _parse_symtab(self, symtab: "SymbolTableSection"): for sym in symtab.iter_symbols(): if not sym.name: 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": self.program.global_labels.add(sym.name) print( FMT_PARSE + "LOADED GLOBAL SYMBOL {}: {}".format(sym.name, sym.entry.st_value) + FMT_NONE ) 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 ) raise RuntimeError("Cannot load elf with overlapping sections!") self.program.add_section(new_sec) riscemu-2.2.5/riscemu/priv/ImageLoader.py000066400000000000000000000054461451200553000203450ustar00rootroot00000000000000""" Loads a memory image with debug information into memory """ import os.path from io import IOBase from typing import List, Iterable from .ElfLoader import ElfMemorySection from .types import MemoryImageDebugInfos from ..assembler import INSTRUCTION_SECTION_NAMES from ..colors import FMT_NONE, FMT_PARSE from ..core import MemoryFlags, ProgramLoader, Program, T_ParserOpts class MemoryImageLoader(ProgramLoader): is_binary = True @classmethod def can_parse(cls, source_name: str) -> float: if source_name.split(".")[-1] == "img": return 1 return 0 @classmethod def get_options(cls, argv: List[str]) -> [List[str], T_ParserOpts]: return argv, {} def parse(self) -> Iterable[Program]: if "debug" not in self.options: yield self.parse_no_debug() return with open(self.options.get("debug"), "r") as debug_file: debug_info = MemoryImageDebugInfos.load(debug_file.read()) with self.source as source_file: data: bytearray = bytearray(source_file.read()) for name, sections in debug_info.sections.items(): program = Program(name) for sec_name, (start, size) in sections.items(): if program.base is None: program.base = start # 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), ) ) program.context.labels.update(debug_info.symbols.get(name, dict())) program.global_labels.update(debug_info.globals.get(name, set())) yield program def parse_no_debug(self) -> Program: print( FMT_PARSE + "[MemoryImageLoader] Warning: loading memory image without debug information!" + FMT_NONE ) with self.source 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) ) ) return p @classmethod def instantiate( cls, source_name: str, source: IOBase, options: T_ParserOpts ) -> "ProgramLoader": if os.path.isfile(source_name + ".dbg"): return MemoryImageLoader( source_name, source, dict(**options, debug=source_name + ".dbg") ) return MemoryImageLoader(source_name, source, options) riscemu-2.2.5/riscemu/priv/PrivCPU.py000066400000000000000000000205301451200553000174530ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ import sys import time from core.usermode_cpu import * from .CSR import CSR from .ElfLoader import ElfBinaryFileLoader from core.traps import * from .ImageLoader import MemoryImageLoader from .PrivMMU import PrivMMU from .PrivRV32I import PrivRV32I from core.privmodes import PrivModes from ..IO.TextIO import TextIO from ..instructions import RV32A, RV32M from ..core import Program, UInt32 if typing.TYPE_CHECKING: pass class PrivCPU(CPU): """ This is a CPU that has different modes, instruction sets and registers. It should support M and U Mode, but no U-Mode Traps. This is meant to emulate whole operating systems. """ csr: CSR """ Reference to the control and status registers """ TIME_RESOLUTION_NS: int = 10000000 """ controls the resolution of the time csr register (in nanoseconds) """ pending_traps: List[CpuTrap] """ A list of traps which are pending to be handled """ def __init__(self, conf): super().__init__(PrivMMU(), [PrivRV32I, RV32M, RV32A], conf) # start in machine mode self.mode: PrivModes = PrivModes.MACHINE self.pending_traps: List[CpuTrap] = list() self.exit_code = 0 self._time_start = 0 self._time_timecmp = UInt32(0) self._time_interrupt_enabled = False # performance counters self._perf_counters = list() # add TextIO io = TextIO(0xFF0000, 64) self.mmu.load_section(io, True) # init csr self._init_csr() self.TIME_RESOLUTION_NS = int(self.TIME_RESOLUTION_NS * conf.slowdown) def run(self, verbose=False): if self.pc <= 0: return False launch_debug = False try: while not self.halted: self.step(verbose) except RiscemuBaseException as ex: if isinstance(ex, LaunchDebuggerException): launch_debug = True self.pc += self.INS_XLEN if self.halted: print() print( FMT_CPU + "[CPU] System halted with code {}".format(self.exit_code) + FMT_NONE ) sys.exit(self.exit_code) elif launch_debug: launch_debug_session(self) if not self.debugger_active: self.run(verbose) else: print() 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 ) 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": 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 # TODO: set correct value 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 # CSR write callbacks: @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") def mtimecmp(old: UInt32, new: UInt32): self._time_timecmp = (self.csr.get("mtimecmph") << 32) + new self._time_interrupt_enabled = True @self.csr.callback("mtimecmph") def mtimecmph(old: UInt32, new: UInt32): self._time_timecmp = (new << 32) + self.csr.get("mtimecmp") self._time_interrupt_enabled = True # virtual CSR registers: @self.csr.virtual_register("time") def get_time(): return UInt32( time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start ) @self.csr.virtual_register("timeh") def get_timeh(): return UInt32( (time.perf_counter_ns() // self.TIME_RESOLUTION_NS - self._time_start) >> 32 ) # add minstret and mcycle counters def _handle_trap(self, trap: CpuTrap): # implement trap handling! self.pending_traps.append(trap) def step(self, verbose=True): try: self.cycle += 1 if self.cycle % 20 == 0: self._timer_step() 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) ) 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 ) breakpoint() if self.conf.debug_on_exception: raise LaunchDebuggerException() self.pc += self.INS_XLEN 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 ): 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")): return # select best interrupt # FIXME: actually select based on the official ranking trap = self.pending_traps.pop() # use the most recent trap if self.conf.verbosity > 0: print(FMT_CPU + "[CPU] taking trap {}!".format(trap) + FMT_NONE) self.regs.dump_reg_a() if trap.priv != PrivModes.MACHINE: 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.mode = trap.priv 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.record_perf_profile() if len(self._perf_counters) > 100: self.show_perf() def show_perf(self): timed = 0 cycled = 0 cps_list = list() print(FMT_CPU + "[CPU] Performance overview:") for time_ns, cycle in self._perf_counters: if cycled == 0: cycled = cycle timed = time_ns continue cps = (cycle - cycled) / (time_ns - timed) * 1000000000 cycled = cycle timed = time_ns cps_list.append(cps) print( " on average {:.0f} instructions/s".format(sum(cps_list) / len(cps_list)) + FMT_NONE ) self._perf_counters = list() def record_perf_profile(self): self._perf_counters.append((time.perf_counter_ns(), self.cycle)) riscemu-2.2.5/riscemu/priv/PrivMMU.py000066400000000000000000000032251451200553000174640ustar00rootroot00000000000000from .types import ElfMemorySection from core.mmu import * import typing if typing.TYPE_CHECKING: pass 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) if existing_sec is not None: 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 ) # get sec succeeding empty space at addr sec_after = next((sec for sec in self.sections if sec.base > addr), None) # calc start and end of "free" space prev_sec_end = 0 if sec_before is None else sec_before.end next_sec_start = 0x7FFFFFFF if sec_after is None else sec_after.base # start at the end of the prev section, or current address - 0xFFFF (aligned to 16 byte boundary) start = max(prev_sec_end, align_addr(addr - 0xFFFF, 16)) # 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), ) self.sections.append(sec) self._update_state() return sec def global_instruction_context(self) -> InstructionContext: context = InstructionContext() context.global_symbol_dict = self.global_symbols return context riscemu-2.2.5/riscemu/priv/PrivRV32I.py000066400000000000000000000137451451200553000176430ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ from ..instructions.RV32I import * from riscemu.core.exceptions import INS_NOT_IMPLEMENTED from riscemu.core.traps import * from riscemu.core.privmodes import PrivModes from ..colors import FMT_CPU, FMT_NONE import typing if typing.TYPE_CHECKING: from riscemu.priv.PrivCPU import PrivCPU class PrivRV32I(RV32I): cpu: "PrivCPU" """ This is an extension of RV32I, written for the PrivCPU class """ def instruction_csrrw(self, ins: "Instruction"): rd, rs, csr_addr = self.parse_crs_ins(ins) old_val = None 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": 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"): rd, rs, csr_addr = self.parse_crs_ins(ins) if rs != "zero": # oh no, this should not happen! INS_NOT_IMPLEMENTED(ins) 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"): INS_NOT_IMPLEMENTED(ins) def instruction_csrrsi(self, ins: "Instruction"): INS_NOT_IMPLEMENTED(ins) 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": 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"): INS_NOT_IMPLEMENTED(ins) def instruction_mret(self, ins: "Instruction"): if self.cpu.mode != PrivModes.MACHINE: print("MRET not inside machine level code!") raise IllegalInstructionTrap(ins) # restore mie mpie = self.cpu.csr.get_mstatus("mpie") self.cpu.csr.set_mstatus("mie", mpie) # restore priv mpp = self.cpu.csr.get_mstatus("mpp") self.cpu.mode = PrivModes(mpp) # restore pc mepc = self.cpu.csr.get("mepc") self.cpu.pc = (mepc - self.cpu.INS_XLEN).abs_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 ) if self.cpu.conf.verbosity > 1: self.regs.dump_reg_a() def instruction_uret(self, ins: "Instruction"): raise IllegalInstructionTrap(ins) def instruction_sret(self, ins: "Instruction"): raise IllegalInstructionTrap(ins) def instruction_scall(self, ins: "Instruction"): """ Overwrite the scall from userspace RV32I """ raise EcallTrap(self.cpu.mode) def instruction_beq(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 == rs2: self.pc += dst.abs_value - 4 def instruction_bne(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 != rs2: self.pc += dst.abs_value - 4 def instruction_blt(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 < rs2: self.pc += dst.abs_value - 4 def instruction_bge(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1 >= rs2: self.pc += dst.abs_value - 4 def instruction_bltu(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1.unsigned_value < rs2.unsigned_value: self.pc += dst.abs_value - 4 def instruction_bgeu(self, ins: "Instruction"): rs1, rs2, dst = self.parse_rs_rs_imm(ins) if rs1.unsigned_value >= rs2.unsigned_value: self.pc += dst.abs_value - 4 # technically deprecated def instruction_j(self, ins: "Instruction"): raise NotImplementedError("Should never be reached!") 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) ): print( FMT_CPU + "Jumping from 0x{:x} to {} (0x{:x})".format( self.pc, self.mmu.translate_address(self.pc + addr.pcrel_value), self.pc + addr.pcrel_value, ) + FMT_NONE ) self.regs.dump_reg_a() self.regs.set(reg, Int32(self.pc)) self.pc += addr.pcrel_value - 4 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.abs_value - 4 def instruction_sbreak(self, ins: "Instruction"): raise LaunchDebuggerException() 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]: ASSERT_LEN(ins.args, 3) addr = self.regs.get(ins.get_reg(1)) + ins.get_imm(2).abs_value reg = ins.get_reg(0) return reg, addr riscemu-2.2.5/riscemu/priv/__init__.py000066400000000000000000000007071451200553000177260ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT The priv Module holds everything necessary for emulating privileged risc-v assembly Running priv is only preferable to the normal userspace emulator, if you actually want to emulate the whole system. 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. """ riscemu-2.2.5/riscemu/priv/__main__.py000066400000000000000000000032511451200553000177040ustar00rootroot00000000000000from riscemu import RunConfig from riscemu.core import Program from .PrivCPU import PrivCPU import sys if __name__ == "__main__": import argparse 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( "-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, ) args = parser.parse_args() 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), ) argv, opts = loader.get_options(sys.argv) program = loader.instantiate(source_path, opts).parse() if isinstance(program, Program): cpu.load_program(program) else: program_iter = program for program in program_iter: cpu.load_program(program) cpu.launch(verbose=args.verbose > 4) riscemu-2.2.5/riscemu/priv/types.py000066400000000000000000000121341451200553000173300ustar00rootroot00000000000000import json from collections import defaultdict from dataclasses import dataclass from functools import lru_cache 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.core.traps import ( InstructionAccessFault, InstructionAddressMisalignedTrap, LoadAccessFault, ) from riscemu.core import ( Instruction, InstructionContext, T_RelativeAddress, MemoryFlags, T_AbsoluteAddress, BinaryDataMemorySection, Immediate, ) @dataclass(frozen=True) class ElfInstruction(Instruction): name: str args: Tuple[int] encoded: int def get_imm(self, num: int) -> Immediate: return Immediate(self.args[num], self.args[num]) def get_reg(self, num: int) -> str: return RISCV_REGS[self.args[num]] def __repr__(self) -> str: if self.name == "jal" and self.args[0] == 0: return "j {}".format(self.args[1]) 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: return "li {}, {}".format(self.get_reg(0), self.args[2]) 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, ): 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 ) raise InstructionAccessFault(offset + self.base) if offset % 4 != 0: raise InstructionAddressMisalignedTrap(offset + self.base) 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" ) self.read_ins.cache_clear() return super(ElfMemorySection, self).write(offset, size, data) @property def end(self): return self.size + self.base class MemoryImageDebugInfos: VERSION = "1.0.0" """ Schema version """ base: T_AbsoluteAddress = 0 """ The base address where the image starts. Defaults to zero. """ sections: Dict[str, Dict[str, Tuple[int, int]]] """ This dictionary maps a program and section to (start address, section length) """ symbols: Dict[str, Dict[str, int]] """ This dictionary maps a program and a symbol to a value """ globals: Dict[str, Set[str]] """ 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, ): self.sections = sections self.symbols = symbols self.globals = globals for name in globals: globals[name] = set(globals[name]) self.base = base def serialize(self) -> str: def serialize(obj: any) -> str: if isinstance(obj, defaultdict): return json.dumps(dict(obj), default=serialize) if isinstance(obj, (set, tuple)): return json.dumps(list(obj), default=serialize) return "<>".format( getattr(obj, "__qualname__", "{unknown}") ) return json.dumps( dict( sections=self.sections, symbols=self.symbols, globals=self.globals, base=self.base, VERSION=self.VERSION, ), default=serialize, ) @classmethod def load(cls, serialized_str: str) -> "MemoryImageDebugInfos": json_obj: dict = json.loads(serialized_str) if "VERSION" not in json_obj: raise RuntimeError("Unknown MemoryImageDebugInfo version!") version: str = json_obj.pop("VERSION") # compare major version 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 ) ) return MemoryImageDebugInfos(**json_obj) @classmethod def builder(cls) -> "MemoryImageDebugInfos": return MemoryImageDebugInfos( defaultdict(dict), defaultdict(dict), defaultdict(set) ) riscemu-2.2.5/riscemu/py.typed000066400000000000000000000000001451200553000163160ustar00rootroot00000000000000riscemu-2.2.5/riscemu/riscemu_main.py000066400000000000000000000273621451200553000176700ustar00rootroot00000000000000import argparse import sys from dataclasses import dataclass from io import IOBase, RawIOBase, TextIOBase from typing import Type, Dict, List, Optional, Union import importlib_resources from . import __version__, __copyright__ from .core import CPU, ProgramLoader, Program, UserModeCPU from .instructions import InstructionSet, InstructionSetDict from .config import RunConfig from .helpers import FMT_GRAY, FMT_NONE from .parser import AssemblyFileLoader from .instructions.float_base import FloatArithBase @dataclass class RiscemuSource: name: str stream: Union[TextIOBase, RawIOBase] def get_score_for(self, loader: ProgramLoader) -> float: if loader.is_binary: if isinstance(self.stream, TextIOBase): return 0 elif isinstance(self.stream, RawIOBase): return 0 return loader.can_parse(self.name) 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[Union[str, RiscemuSource]] 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( "--flen", type=int, help="hardware FLEN, either 32 or 64. Defaults to 64", nargs="?", default=64, ) 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}) if args.flen not in (32, 64): raise ValueError("Cannot have flen other than 32 or 64!") # create RunConfig self.cfg = self.create_config(args) # set input files self.input_files.extend(args.files) # get selected ins sets self.selected_ins_sets.extend( self.available_ins_sets[name] for name, selected in args.ins.items() if selected ) # remove floating point isas if they do not meet the flen requirements for isa in tuple(self.selected_ins_sets): if issubclass(isa, FloatArithBase) and isa.flen > args.flen: self.selected_ins_sets.remove(isa) # 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. """ for file in importlib_resources.files("riscemu.libc").iterdir(): if file.name.lower().endswith(".s"): source = RiscemuSource(file.name, file.open("r")) self.input_files.append(source) 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"], flen=args.flen, ) 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: max_bid = -1 bidder = None # get best-fit loader: for loader in self.available_file_loaders: if isinstance(path, RiscemuSource): score = path.get_score_for(loader) else: score = loader.can_parse(path) if score > max_bid: max_bid = score bidder = loader if max_bid <= 0: raise RuntimeError( f"Cannot load {path}! No loader for this file type available." ) if isinstance(path, RiscemuSource): stream = path.stream source_name = path.name elif path == "-": stream: IOBase = sys.stdin source_name = "" else: source_name = path stream: IOBase = open(path, "rb" if bidder.is_binary else "r") programs = bidder.instantiate(source_name, stream, {}).parse() if isinstance(programs, Program): programs = [programs] for p in programs: self.cpu.mmu.load_program(p) if self.cfg.verbosity > 2: print( FMT_GRAY + "[Startup] Loaded {} with loader {}".format( source_name, bidder.__name__ ) + FMT_NONE ) 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() if self.cfg.verbosity > 3: print( FMT_GRAY + "[Startup] Startup complete, the following sections were loaded" ) for sec in self.cpu.mmu.sections: print(" {}".format(sec.debug_str())) print(FMT_NONE) # 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) riscemu-2.2.5/riscemu/syscall.py000066400000000000000000000203471451200553000166630ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ import sys from dataclasses import dataclass from math import log2, ceil from typing import Dict, IO, Union from .core import ( BinaryDataMemorySection, MemoryFlags, Int32, CPU, InvalidSyscallException, ) from .colors import FMT_SYSCALL, FMT_NONE SYSCALLS = { 63: "read", 64: "write", 93: "exit", 192: "mmap2", 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 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", } """All available file open modes""" @dataclass(frozen=True) class Syscall: """ Represents a syscall """ id: int """The syscall number (e.g. 64 - write)""" cpu: CPU """The CPU object that created the syscall""" @property def name(self): return SYSCALLS.get(self.id, "unknown") def __repr__(self): return "Syscall(id={}, name={})".format(self.id, self.name) def ret(self, code: Union[int, Int32]): self.cpu.regs.set("a0", Int32(code)) def get_syscall_symbols(): """ Generate global syscall symbols (such as SCALL_READ, SCALL_EXIT etc) :return: dictionary of all syscall symbols (SCALL_ -> id) """ items: Dict[str, int] = { ("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} if getattr(self, scall.name): getattr(self, scall.name)(scall) else: raise InvalidSyscallException(scall) def read(self, scall: Syscall): """ 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 occurred """ 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") 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 ) return scall.ret(-1) def write(self, scall: Syscall): """ 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 occurred """ 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 ) return scall.ret(-1) self.open_files[fileno].write(data.decode("ascii")) return scall.ret(size) def open(self, scall: Syscall): """ open syscall (1024): read path of a2 bytes from addr a1, in mode a0 returns the file no in a0 modes: - 0: read - 1: write (truncate) - 2: read/write (no truncate) - 3: only create - 4: append Requires running with flag scall-fs """ # 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 ) 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_st = OPEN_MODES.get( mode, ) if mode_st == -1: 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") fileno = self.next_open_handle self.next_open_handle += 1 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 ) return scall.ret(-1) print( FMT_SYSCALL + "[Syscall] open: opened fd {} to {}!".format(fileno, path) + FMT_NONE ) return scall.ret(fileno) def close(self, scall: Syscall): """ close syscall (1025): closes file no a0 return -1 if an error was encountered, otherwise returns 0 """ 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 ) return scall.ret(-1) self.open_files[fileno].close() print(FMT_SYSCALL + "[Syscall] close: closed fd {}!".format(fileno) + FMT_NONE) del self.open_files[fileno] return scall.ret(0) def exit(self, scall: Syscall): """ 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 = prot = either PROT_READ or PROT_READ | PROT_WRITE flags = MAP_PRIVATE | MAP_ANONYMOUS fd = off_t = """ addr = scall.cpu.regs.get("a0").unsigned_value size = scall.cpu.regs.get("a1").unsigned_value prot = scall.cpu.regs.get("a2").unsigned_value flags = scall.cpu.regs.get("a3").unsigned_value # error out if prot is not 1 or 3: # 1 = PROT_READ # 3 = PROT_READ | PROT_WRITE if prot != 1 and prot != 3: return scall.ret(-1) # round size up to multiple of 4096 size = 4096 * ceil(size / 4096) section = BinaryDataMemorySection( bytearray(size), ".data.runtime-allocated", None, "system", base=addr, flags=MemoryFlags(read_only=prot != 3, executable=False), ) # try to insert section if scall.cpu.mmu.load_section(section, addr != 0): return scall.ret(section.base) # if that failed, and we tried to force an address, # try again at any address elif addr != 0: section.base = 0 if scall.cpu.mmu.load_section(section): return scall.ret(section.base) # if that didn't work, return error return scall.ret(-1) def __repr__(self): return "{}(\n\tfiles={}\n)".format(self.__class__.__name__, self.open_files) riscemu-2.2.5/riscemu/tokenizer.py000066400000000000000000000063211451200553000172170ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT """ import re from dataclasses import dataclass from enum import Enum, auto from typing import List, Iterable from riscemu.decoder import RISCV_REGS from riscemu.core.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]*)\)$" ) REGISTER_NAMES = RISCV_REGS class TokenType(Enum): COMMA = auto() ARGUMENT = auto() PSEUDO_OP = auto() INSTRUCTION_NAME = auto() NEWLINE = auto() LABEL = auto() @dataclass(frozen=True) class Token: type: TokenType value: str def __str__(self): if self.type == TokenType.NEWLINE: return "\\n" if self.type == TokenType.COMMA: return ", " return "{}({})".format(self.type.name[0:3], self.value) 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") if not line: continue parts = list(part for part in split_whitespace_respecting_quotes(line) if part) yield from parse_line(parts) yield NEWLINE def parse_line(parts: List[str]) -> Iterable[Token]: if len(parts) == 0: return () first_token = parts[0] if first_token[0] == ".": yield Token(TokenType.PSEUDO_OP, first_token) elif first_token[-1] == ":": yield Token(TokenType.LABEL, first_token) yield from parse_line(parts[1:]) return else: yield Token(TokenType.INSTRUCTION_NAME, first_token) for part in parts[1:]: if part == ",": yield COMMA continue yield from parse_arg(part) def parse_arg(arg: str) -> Iterable[Token]: comma = arg[-1] == "," arg = arg[:-1] if comma else arg mem_match_result = re.match(MEMORY_ADDRESS_PATTERN, arg) if mem_match_result: register = mem_match_result.group(2).lower() immediate = mem_match_result.group(1) yield Token(TokenType.ARGUMENT, register) yield Token(TokenType.ARGUMENT, immediate) else: yield Token(TokenType.ARGUMENT, arg) if comma: yield COMMA def print_tokens(tokens: Iterable[Token]): for token in tokens: print(token, end="\n" if token == NEWLINE else "") print("", flush=True, end="") def split_whitespace_respecting_quotes(line: str) -> Iterable[str]: quote = "" part = "" for c in line: if c == quote: yield part part = "" quote = "" continue if quote != "": part += c continue if c in "\"'": if part: yield part quote = c part = "" continue if c in " \t\n": if part: yield part part = "" continue part += c if part: yield part riscemu-2.2.5/riscemu/tools/000077500000000000000000000000001451200553000157715ustar00rootroot00000000000000riscemu-2.2.5/riscemu/tools/riscemu000077500000000000000000000000541451200553000173650ustar00rootroot00000000000000#!/usr/bin/env bash python3 -m riscemu "$@" riscemu-2.2.5/snitch/000077500000000000000000000000001451200553000144525ustar00rootroot00000000000000riscemu-2.2.5/snitch/__init__.py000066400000000000000000000000001451200553000165510ustar00rootroot00000000000000riscemu-2.2.5/snitch/__main__.py000066400000000000000000000011771451200553000165520ustar00rootroot00000000000000""" RiscEmu (c) 2021 Anton Lydike SPDX-License-Identifier: MIT This file holds the logic for starting the emulator from the CLI """ import sys from .xssr import Xssr_pseudo from .frep import FrepEnabledCpu, Xfrep from riscemu.riscemu_main import RiscemuMain class SnitchMain(RiscemuMain): def instantiate_cpu(self): self.cpu = FrepEnabledCpu(self.selected_ins_sets, self.cfg) self.configure_cpu() def register_all_isas(self): super().register_all_isas() self.available_ins_sets.update({"Xssr": Xssr_pseudo, "Xfrep": Xfrep}) if __name__ == "__main__": SnitchMain().run_from_cli(sys.argv) riscemu-2.2.5/snitch/frep.py000066400000000000000000000067771451200553000160010ustar00rootroot00000000000000from typing import List, Type, Union, Set, Literal from riscemu.colors import FMT_CPU, FMT_NONE from riscemu.config import RunConfig from riscemu.core import UserModeCPU from riscemu.instructions import InstructionSet, Instruction, RV32F, RV32D from dataclasses import dataclass from snitch.regs import StreamingRegs @dataclass(frozen=True) class FrepState: rep_count: int ins_count: int mode: Literal["inner", "outer"] class FrepEnabledCpu(UserModeCPU): repeat: Union[FrepState, None] allowed_ins: Set[str] def __init__(self, instruction_sets: List[Type["InstructionSet"]], conf: RunConfig): super().__init__(instruction_sets, conf) self.regs = StreamingRegs( mem=self.mmu, infinite_regs=conf.unlimited_registers, flen=conf.flen ) self.repeats = None # only floating point instructions are allowed inside an frep! self.allowed_ins = set(x for x, y in RV32F(self).get_instructions()) if conf.flen > 32: self.allowed_ins.union(x for x, y in RV32D(self).get_instructions()) def step(self, verbose: bool = False): if self.repeats is None: super().step(verbose=verbose) return # get the spec spec: FrepState = self.repeats self.repeats = None instructions = [ self.mmu.read_ins(self.pc + i * self.INS_XLEN) for i in range(spec.ins_count) ] for ins in instructions: if ins.name not in self.allowed_ins: # TODO: wrap in a nicer error type raise RuntimeError( "Forbidden instruction inside frep loop: {}".format(ins) ) if verbose: print( FMT_CPU + "┌────── floating point repetition ({}) {} times".format( spec.mode, spec.rep_count + 1 ) ) for i, ins in enumerate(instructions): print( FMT_CPU + "│ 0x{:08X}:{} {}".format( self.pc + i * self.INS_XLEN, FMT_NONE, ins ) ) print(FMT_CPU + "└────── end of floating point repetition" + FMT_NONE) pc = self.pc if spec.mode == "outer": for _ in range(spec.rep_count + 1): for ins in instructions: self.run_instruction(ins) elif spec.mode == "inner": for ins in instructions: for _ in range(spec.rep_count + 1): self.run_instruction(ins) else: raise RuntimeError(f"Unknown frep mode: {spec.mode}") self.cycle += (spec.rep_count + 1) * spec.ins_count self.pc = pc + (spec.ins_count * self.INS_XLEN) class Xfrep(InstructionSet): def instruction_frep_o(self, ins: Instruction): self.frep(ins, "outer") def instruction_frep_i(self, ins: Instruction): self.frep(ins, "inner") def frep(self, ins: Instruction, mode: Literal["inner", "outer"]): assert isinstance(self.cpu, FrepEnabledCpu) assert len(ins.args) == 4 assert ins.get_imm(2).abs_value.value == 0, "staggering not supported yet" assert ins.get_imm(3).abs_value.value == 0, "staggering not supported yet" self.cpu.repeats = FrepState( rep_count=self.regs.get(ins.get_reg(0)).unsigned_value, ins_count=ins.get_imm(1).abs_value.value, mode=mode, ) riscemu-2.2.5/snitch/regs.py000066400000000000000000000043721451200553000157720ustar00rootroot00000000000000from typing import Dict, List, Tuple from riscemu.core import Registers, MMU, BaseFloat from dataclasses import dataclass from enum import Enum class StreamMode(Enum): READ = 1 WRITE = 2 # READWRITE = 3 @dataclass class StreamDef: base: int = 0 """ Base address to read from """ bound: int = 0 """ How many elements are in here """ stride: int = 0 """ stride = register width """ mode: StreamMode = StreamMode.READ """ Differentiate between read/write """ dim: int = 0 """ Supports nested loops """ # internal: pos: int = 0 """ Next element in stream """ class StreamingRegs(Registers): mem: MMU dm_by_id: List[StreamDef] streams: Dict[str, StreamDef] enabled: bool def __init__( self, mem: MMU, xssr_regs: Tuple[str] = ("ft0", "ft1", "ft2"), infinite_regs: bool = False, flen: int = 64, ): self.mem = mem self.enabled = False self.streams = dict() self.dm_by_id = [] for reg in xssr_regs: stream_def = StreamDef() self.dm_by_id.append(stream_def) self.streams[reg] = stream_def super().__init__(infinite_regs=infinite_regs, flen=flen) def get_f(self, reg) -> "BaseFloat": if not self.enabled or reg not in self.streams: return super().get_f(reg) # do the streaming stuff: stream = self.streams[reg] # TODO: Implement other modes assert stream.mode is StreamMode.READ # TODO: Check overflow # TODO: repetition addr = stream.base + (stream.pos * stream.stride) val = self._float_type(self.mem.read(addr, self.flen // 8)) # increment pos stream.pos += 1 return val def set_f(self, reg, val: "BaseFloat") -> bool: if not self.enabled or reg not in self.streams: return super().set_f(reg, val) stream = self.streams[reg] assert stream.mode is StreamMode.WRITE addr = stream.base + (stream.pos * stream.stride) data = val.bytes self.mem.write(addr + (self.flen // 8) - len(data), len(data), bytearray(data)) stream.pos += 1 return True riscemu-2.2.5/snitch/xssr.py000066400000000000000000000027031451200553000160250ustar00rootroot00000000000000from riscemu.instructions.instruction_set import InstructionSet, Instruction from .regs import StreamingRegs, StreamDef, StreamMode class Xssr_pseudo(InstructionSet): def instruction_ssr_enable(self, ins: Instruction): self._stream.enabled = True def instruction_ssr_disable(self, ins: Instruction): self._stream.enabled = False def instruction_ssr_configure(self, ins: Instruction): dm = ins.get_imm(0).abs_value.value bound = ins.get_imm(1).abs_value.value stride = ins.get_imm(2).abs_value.value self._stream.dm_by_id[dm].bound = bound self._stream.dm_by_id[dm].stride = stride def instruction_ssr_read(self, ins: Instruction): base_pointer = ins.get_reg(0) dm = ins.get_imm(1).abs_value.value dim = ins.get_imm(2).abs_value.value self._stream.dm_by_id[dm].base = self.regs.get(base_pointer).value self._stream.dm_by_id[dm].dim = dim self._stream.dm_by_id[dm].mode = StreamMode.READ def instruction_ssr_write(self, ins: Instruction): base_pointer = ins.get_reg(0) dm = ins.get_imm(1).abs_value.value dim = ins.get_imm(2).abs_value.value self._stream.dm_by_id[dm].base = self.regs.get(base_pointer).value self._stream.dm_by_id[dm].dim = dim self._stream.dm_by_id[dm].mode = StreamMode.WRITE @property def _stream(self) -> StreamingRegs: return self.cpu.regs # type: ignore riscemu-2.2.5/sphinx-docs/000077500000000000000000000000001451200553000154215ustar00rootroot00000000000000riscemu-2.2.5/sphinx-docs/.gitignore000066400000000000000000000000711451200553000174070ustar00rootroot00000000000000build source/riscemu*.rst source/modules.rst source/help riscemu-2.2.5/sphinx-docs/Makefile000066400000000000000000000011761451200553000170660ustar00rootroot00000000000000# Minimal makefile for Sphinx documentation # # You can set these variables from the command line, and also # from the environment for the first two. SPHINXOPTS ?= SPHINXBUILD ?= sphinx-build SOURCEDIR = source BUILDDIR = build # Put it first so that "make" without argument is like "make help". help: @$(SPHINXBUILD) -M help "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) .PHONY: help Makefile # Catch-all target: route all unknown targets to Sphinx using the new # "make mode" option. $(O) is meant as a shortcut for $(SPHINXOPTS). %: Makefile @$(SPHINXBUILD) -M $@ "$(SOURCEDIR)" "$(BUILDDIR)" $(SPHINXOPTS) $(O) riscemu-2.2.5/sphinx-docs/make.bat000066400000000000000000000014371451200553000170330ustar00rootroot00000000000000@ECHO OFF pushd %~dp0 REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set SOURCEDIR=source set BUILDDIR=build if "%1" == "" goto help %SPHINXBUILD% >NUL 2>NUL if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) %SPHINXBUILD% -M %1 %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% goto end :help %SPHINXBUILD% -M help %SOURCEDIR% %BUILDDIR% %SPHINXOPTS% %O% :end popd riscemu-2.2.5/sphinx-docs/requirements.txt000066400000000000000000000000241451200553000207010ustar00rootroot00000000000000sphinx recommonmark riscemu-2.2.5/sphinx-docs/source/000077500000000000000000000000001451200553000167215ustar00rootroot00000000000000riscemu-2.2.5/sphinx-docs/source/conf.py000066400000000000000000000051031451200553000202170ustar00rootroot00000000000000import os import sys import subprocess # Configuration file for the Sphinx documentation builder. # # This file only contains a selection of the most common options. For a full # list see the documentation: # https://www.sphinx-doc.org/en/master/usage/configuration.html # -- Path setup -------------------------------------------------------------- # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # # import os # 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"]) # -- Project information ----------------------------------------------------- project = "RiscEmu" copyright = "2022, Anton Lydike" author = "Anton Lydike" # The full version, including alpha/beta/rc tags 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"] # autodoc options autodoc_default_options = { "members": True, "member-order": "bysource", "special-members": "__init__", } # Add any paths that contain templates here, relative to this directory. templates_path = ["_templates"] # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This pattern also affects html_static_path and html_extra_path. # Markdown support from recommonmark.parser import CommonMarkParser # The suffix of source filenames. source_suffix = [".rst", ".md"] source_parsers = { ".md": CommonMarkParser, } # -- Options for HTML output ------------------------------------------------- # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # html_theme = "alabaster" 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"] sys.path.insert(0, os.path.abspath("../../")) if os.getenv("READTHEDOCS", False): import sphinx_rtd_theme extensions.append("sphinx_rtd_theme") html_theme = "sphinx_rtd_theme" riscemu-2.2.5/sphinx-docs/source/index.rst000066400000000000000000000032041451200553000205610ustar00rootroot00000000000000.. RiscEmu documentation master file, created by sphinx-quickstart on Thu Apr 22 14:39:35 2021. You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. RiscEmu Documentation! ====================== Check this project out on github_. .. _github: https://github.com/antonlydike/riscemu Help: ===== .. toctree:: :maxdepth: 1 :glob: help/* Package structure: ================== .. toctree:: :maxdepth: 5 riscemu Other links: ============ * :ref:`genindex` * :ref:`modindex` * :ref:`search` License: ======== MIT License Copyright (c) 2021 Anton Lydike Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. riscemu-2.2.5/test/000077500000000000000000000000001451200553000141415ustar00rootroot00000000000000riscemu-2.2.5/test/__init__.py000066400000000000000000000001271451200553000162520ustar00rootroot00000000000000from .test_tokenizer import * from .test_helpers import * from .test_integers import * riscemu-2.2.5/test/filecheck/000077500000000000000000000000001451200553000160565ustar00rootroot00000000000000riscemu-2.2.5/test/filecheck/.gitignore000066400000000000000000000000331451200553000200420ustar00rootroot00000000000000.lit_test_times.txt Output riscemu-2.2.5/test/filecheck/csr.asm000066400000000000000000000016201451200553000173460ustar00rootroot00000000000000.text .globl main main: // check that less that 1000 ticks passed since start csrrs a0, zero, time li a1, 1000 bge a0, a1, fail print a0, "time passed since launch: {1} ticks" // CHECK: time passed since launch: // check that timeh is empty csrrs a0, zero, timeh bne a0, zero, fail // check that some ammount of cycles has passed so far: csrrs a0, zero, cycle beq a0, zero, fail print a0, "cycles passed: {1}" // CHECK-NEXT: cycles passed: // check that cycleh is zero csrrs a0, zero, cycleh bne a0, zero, fail // check instret and instreth csrrs a0, zero, instret beq a0, zero, fail print a0, "instret is: {1}" // CHECK-NEXT: instret is: csrrs a0, zero, instreth bne a0, zero, fail // CHECK-NEXT: Success! print a0, "Success!" ret fail: li a0, -1 print a0, "Failure!" ret riscemu-2.2.5/test/filecheck/fibs.asm000066400000000000000000000016271451200553000175110ustar00rootroot00000000000000// RUN: python3 -m riscemu -v -o ignore_exit_code,libc %s | filecheck %s .data fibs: .space 1024 .text // make main global so it can be picked up by the crt0.s .globl main 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 riscemu-2.2.5/test/filecheck/hello-world.asm000066400000000000000000000010631451200553000210100ustar00rootroot00000000000000// 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 riscemu-2.2.5/test/filecheck/libc/000077500000000000000000000000001451200553000167675ustar00rootroot00000000000000riscemu-2.2.5/test/filecheck/libc/test-string.s000066400000000000000000000054071451200553000214440ustar00rootroot00000000000000// 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 riscemu-2.2.5/test/filecheck/lit.cfg000066400000000000000000000004371451200553000173330ustar00rootroot00000000000000import lit.formats import os config.test_source_root = os.path.dirname(__file__) xdsl_src = os.path.dirname(os.path.dirname(config.test_source_root)) config.name = "riscemu" config.test_format = lit.formats.ShTest(preamble_commands=[f"cd {xdsl_src}"]) config.suffixes = ['.asm', '.s'] riscemu-2.2.5/test/filecheck/p2align.asm000066400000000000000000000005551451200553000201210ustar00rootroot00000000000000// RUN: riscemu -o libc %s | filecheck %s .data .space 8 .globl main .text text_start: .p2align 8 main: la a0, main la a1, text_start sub a0, a0, a1 print a0 // align to 2**8 bytes, so 256 // we have 8 bytes of padding at the front, so we should see 248 bytes between text_start and main // CHECK: register a0 contains value 248 li a0, 0 ret riscemu-2.2.5/test/filecheck/rv32f-conv.asm000066400000000000000000000021241451200553000204640ustar00rootroot00000000000000// RUN: python3 -m riscemu -v %s -o libc | filecheck %s .text .globl main main: // test fcvt.s.wu li a1, -2 fcvt.s.wu fa0, a1 print.float.s fa0 // CHECK: register fa0 contains value 4294967296.0 li a1, 2 fcvt.s.wu fa0, a1 print.float fa0 // CHECK-NEXT: register fa0 contains value 2.0 // test fcvt.s.w li a1, -2 fcvt.s.w fa0, a1 print.float.s fa0 // CHECK: register fa0 contains value -2.0 li a1, 2 fcvt.s.w fa0, a1 print.float.s fa0 // CHECK-NEXT: register fa0 contains value 2.0 // test fmv.s.x li a1, 2 fcvt.s.w fa0, a1 fmv.x.w a1, fa0 print a1 // CHECK-NEXT: register a1 contains value 1073741824 li a1, -2 fcvt.s.w fa0, a1 fmv.x.w a1, fa0 print a1 // CHECK-NEXT: register a1 contains value 3221225472 // test fmv.w.x li a1, 1073741824 fmv.w.x fa0, a1 print.float.s fa0 // CHECK-NEXT: register fa0 contains value 2.0 li a1, 3221225472 fmv.w.x fa0, a1 print.float.s fa0 // CHECK-NEXT: register fa0 contains value -2.0 ret // CHECK-NEXT: [CPU] Program exited with code 0 riscemu-2.2.5/test/filecheck/snitch/000077500000000000000000000000001451200553000173465ustar00rootroot00000000000000riscemu-2.2.5/test/filecheck/snitch/frep_only.asm000066400000000000000000000010611451200553000220430ustar00rootroot00000000000000// RUN: python3 -m snitch %s -o libc -v | filecheck %s .text .globl main main: // load constants li t0, 0 fcvt.s.w ft0, t0 li t0, 1 fcvt.s.w ft1, t0 // repeat 100 times li t0, 99 frep.i t0, 1, 0, 0 fadd.s ft0, ft0, ft1 // add one // print result to stdout printf "100 * 1 = {:f32}", ft0 // CHECK: 100 * 1 = 100.0 // return 0 li a0, 0 ret // CHECK-NEXT: [CPU] Program exited with code 0 riscemu-2.2.5/test/filecheck/snitch/ssr_frep.asm000066400000000000000000000035041451200553000216750ustar00rootroot00000000000000// RUN: python3 -m snitch %s -o libc -v --flen 32 | filecheck %s .data vec0: .word 0x0, 0x3f800000, 0x40000000, 0x40400000, 0x40800000, 0x40a00000, 0x40c00000, 0x40e00000, 0x41000000, 0x41100000 vec1: .word 0x0, 0x3f800000, 0x40000000, 0x40400000, 0x40800000, 0x40a00000, 0x40c00000, 0x40e00000, 0x41000000, 0x41100000 dest: .space 40 expected: .word 0x0, 0x3e800000, 0x3f800000, 0x40100000, 0x40800000, 0x40c80000, 0x41100000, 0x41440000, 0x41800000, 0x41a20000 .text .globl main main: // ssr config ssr.configure 0, 10, 4 ssr.configure 1, 10, 4 ssr.configure 2, 10, 4 // ft0 streams from vec0 la a0, vec0 ssr.read a0, 0, 0 // ft1 streams from vec1 la a0, vec1 ssr.read a0, 1, 0 // ft2 streams to dest la a0, dest ssr.write a0, 2, 0 li a0, 9 // some constant to divide by li t0, 4 fcvt.s.w ft3, t0 ssr.enable frep.o a0, 2, 0, 0 fmul.s ft4, ft0, ft1 // ft3 = vec0[i] * vec1[i] fdiv.s ft2, ft4, ft3 // dest[i] = ft3 / 4 // stop ssr ssr.disable // check values were written correctly: la t0, dest la t1, expected li a0, 36 loop: add s0, t0, a0 add s1, t1, a0 // load vec0, vec1 and dest elements flw ft0, 0(s0) flw ft1, 0(s1) // assert ft0 == ft1 (expected[i] == dest[i]) feq.s s0, ft0, ft1 beq zero, s0, fail addi a0, a0, -4 bge a0, zero loop li a0, 0 ret fail: printf "Assertion failure: {} != {} (at {})", ft0, ft1, a0 li a0, -1 ret // CHECK: [CPU] Program exited with code 0 riscemu-2.2.5/test/filecheck/snitch/ssr_only.asm000066400000000000000000000031151451200553000217200ustar00rootroot00000000000000// RUN: python3 -m snitch %s -o libc -v --flen 32 | filecheck %s .data vec0: .word 0x0, 0x3f800000, 0x40000000, 0x40400000, 0x40800000, 0x40a00000, 0x40c00000, 0x40e00000, 0x41000000, 0x41100000 vec1: .word 0x0, 0x3f800000, 0x40000000, 0x40400000, 0x40800000, 0x40a00000, 0x40c00000, 0x40e00000, 0x41000000, 0x41100000 dest: .space 40 .text .globl main main: // ssr config ssr.configure 0, 10, 4 ssr.configure 1, 10, 4 ssr.configure 2, 10, 4 la a0, vec0 ssr.read a0, 0, 0 la a0, vec1 ssr.read a0, 1, 0 la a0, dest ssr.write a0, 2, 0 ssr.enable // set up loop li a0, 10 loop: fadd.s ft2, ft0, ft1 addi a0, a0, -1 bne a0, zero, loop // end of loop: ssr.disable // check values were written correctly: la t0, vec0 la t1, vec1 la t2, dest li a0, 36 loop2: add s0, t0, a0 add s1, t1, a0 add s2, t2, a0 // load vec0, vec1 and dest elements flw ft0, 0(s0) flw ft1, 0(s1) flw ft2, 0(s2) // assert ft2 == ft1 + ft2 fadd.s ft3, ft1, ft0 feq.s s0, ft2, ft3 beq zero, s0, fail addi a0, a0, -4 bne a0, zero, loop2 ret fail: printf "failed {} + {} != {} (at {})", ft0, ft1, ft2, a0 li a0, -1 ret // CHECK: [CPU] Program exited with code 0 riscemu-2.2.5/test/test_RV32F.py000066400000000000000000000062121451200553000163550ustar00rootroot00000000000000from typing import Union from riscemu.instructions.RV32F import RV32F from riscemu.core import CPU, Float32, Int32, SimpleInstruction, Registers, BaseFloat def is_close(a0: Union[float, int, BaseFloat], a1: Union[float, int, BaseFloat]): """ Compares if two numbers are close to 7 digits. This should be close enough to catch any real erros but ignore floating point rounding issues. """ diff = abs(float(a0 - a1)) mag = max(abs(float(a0)), abs(float(a1))) return (mag / 1e7) > diff class MockInstruction(SimpleInstruction): ... class MockRegisters(Registers): ... class MockCPU(CPU): def __init__(self, flen: int = 32): self.regs = MockRegisters(True, flen) def run(self, verbose: bool = False): assert False def step(self, verbose: bool = False): assert False def test_fcvt_instructions(): cpu = MockCPU() ins = MockInstruction("fcvt.s.w", ("fa0", "a0"), None, None) cpu.regs.set("a0", Int32(42)) RV32F(cpu).instruction_fcvt_s_w(ins) assert 42.0 == cpu.regs.get_f("fa0") ins = MockInstruction("fcvt.w.s", ("a1", "fa1"), None, None) cpu.regs.set_f("fa1", Float32(42.0)) RV32F(cpu).instruction_fcvt_w_s(ins) assert Int32(42) == cpu.regs.get("a1") def test_single_precision_on_flen64(): cpu = MockCPU(flen=64) cpu.regs.set_f("ft0", Float32(100)) cpu.regs.set_f("ft1", Float32(3)) # instruction doing ft2 <- ft1 ft2 ins = MockInstruction("", ("ft2", "ft0", "ft1"), None, None) # div RV32F(cpu).base_fdiv(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), (100.0 / 3)) # multiplication RV32F(cpu).base_fmul(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), (100.0 * 3)) # fadd RV32F(cpu).base_fadd(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), (100.0 + 3)) # fsub RV32F(cpu).base_fsub(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), (100.0 - 3)) # fmin RV32F(cpu).base_fmin(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), min(100.0, 3)) # fmax RV32F(cpu).base_fmax(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), max(100.0, 3)) def test_single_precision_on_flen32(): cpu = MockCPU(flen=32) cpu.regs.set_f("ft0", Float32(100)) cpu.regs.set_f("ft1", Float32(3)) # instruction doing ft2 <- ft1 ft2 ins = MockInstruction("", ("ft2", "ft0", "ft1"), None, None) # div RV32F(cpu).base_fdiv(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), (100.0 / 3)) # multiplication RV32F(cpu).base_fmul(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), (100.0 * 3)) # fadd RV32F(cpu).base_fadd(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), (100.0 + 3)) # fsub RV32F(cpu).base_fsub(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), (100.0 - 3)) # fmin RV32F(cpu).base_fmin(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), min(100.0, 3)) # fmax RV32F(cpu).base_fmax(ins) assert is_close(Float32.bitcast(cpu.regs.get_f("ft2")), max(100.0, 3)) riscemu-2.2.5/test/test_float_impl.py000066400000000000000000000021661451200553000177050ustar00rootroot00000000000000import math from riscemu.core import Float32, Float64 # pi encoded as a 32bit little endian float PI_BYTES_LE = b"\xdb\x0fI@" def test_float_serialization(): assert Float32(PI_BYTES_LE) == Float32(math.pi) assert Float32(math.pi).bytes == PI_BYTES_LE def test_float_bitcast(): f32_pi = Float32(math.pi) f64_pi32 = Float64.bitcast(f32_pi) assert f32_pi.bytes == Float32.bitcast(f64_pi32).bytes f64_pi = Float64(math.pi) f32_pi64 = Float32.bitcast(f64_pi) assert f64_pi.bytes[-4:] == f32_pi64.bytes assert Float64.bitcast(f32_pi64).bytes[:4] == b"\x00\x00\x00\x00" def test_random_float_ops32(): val = Float32(5) assert val**2 == 25 assert val // 2 == 2 assert val * 3 == 15 assert val - 2 == 3 assert val * val == 25 assert Float32(9) ** 0.5 == 3 def test_random_float_ops64(): val = Float64(5) assert val**2 == 25 assert val // 2 == 2 assert val * 3 == 15 assert val - 2 == 3 assert val * val == 25 assert Float64(9) ** 0.5 == 3 def test_float_from_raw_bytes_conversion(): assert Float32.from_bytes(b"\x00\x00\xa0@") == Float32(5.0) riscemu-2.2.5/test/test_helpers.py000066400000000000000000000006131451200553000172140ustar00rootroot00000000000000from riscemu.helpers import * def test_align_address(): assert align_addr(3, 1) == 3 assert align_addr(3, 2) == 4 assert align_addr(3, 4) == 4 assert align_addr(3, 8) == 8 assert align_addr(8, 8) == 8 def test_parse_numeric(): assert parse_numeric_argument("13") == 13 assert parse_numeric_argument("0x100") == 256 assert parse_numeric_argument("-13") == -13 riscemu-2.2.5/test/test_instruction.py000066400000000000000000000030361451200553000201350ustar00rootroot00000000000000from riscemu.core import InstructionContext, SimpleInstruction, NumberFormatException import pytest def test_int_and_hex_immediates(): ctx = InstructionContext() ins = SimpleInstruction("addi", ("a0", "a1", "100"), ctx, 0x100) ins_hex = SimpleInstruction("addi", ("a0", "a1", "0x10"), ctx, 0x100) assert ins.get_reg(0) == "a0" assert ins.get_reg(1) == "a1" assert ins.get_imm(2).abs_value == 100 assert ins.get_imm(2).pcrel_value == 100 - 0x100 assert ins_hex.get_imm(2).abs_value == 0x10 assert ins_hex.get_imm(2).pcrel_value == 0x10 - 0x100 def test_label_immediates(): ctx = InstructionContext() ctx.labels["test"] = 100 ins = SimpleInstruction("addi", ("a0", "a1", "test"), ctx, 0x100) assert ins.get_reg(0) == "a0" assert ins.get_reg(1) == "a1" assert ins.get_imm(2).abs_value == 100 assert ins.get_imm(2).pcrel_value == 100 - 0x100 def test_numerical_labels(): ctx = InstructionContext() ctx.numbered_labels["1"] = [0x100 - 4, 0x100 + 16] ins = SimpleInstruction("addi", ("a0", "a1", "1b"), ctx, 0x100) assert ins.get_reg(0) == "a0" assert ins.get_reg(1) == "a1" assert ins.get_imm(2).abs_value == 0x100 - 4 assert ins.get_imm(2).pcrel_value == -4 def test_invalid_immediate_val(): ctx = InstructionContext() ctx.labels["test"] = 100 ins = SimpleInstruction("addi", ("a0", "a1", "test2"), ctx, 0x100) with pytest.raises( NumberFormatException, match="test2 is neither a number now a known symbol" ): ins.get_imm(2) riscemu-2.2.5/test/test_integers.py000066400000000000000000000007231451200553000173740ustar00rootroot00000000000000from riscemu.core import Int32, UInt32 def test_logical_right_shift(): a = Int32(100) assert a.shift_right_logical(0) == a assert a.shift_right_logical(10) == 0 assert a.shift_right_logical(1) == 100 >> 1 a = Int32(-100) assert a.shift_right_logical(0) == a assert a.shift_right_logical(1) == 2147483598 assert a.shift_right_logical(10) == 4194303 assert a.shift_right_logical(31) == 1 assert a.shift_right_logical(32) == 0 riscemu-2.2.5/test/test_isa.py000066400000000000000000000044251451200553000163330ustar00rootroot00000000000000from riscemu.colors import FMT_ERROR, FMT_NONE, FMT_BOLD, FMT_GREEN from riscemu.instructions import InstructionSet from riscemu.core import Instruction, CPU from riscemu.decoder import RISCV_REGS FMT_SUCCESS = FMT_GREEN + FMT_BOLD def assert_equals(ins: Instruction, cpu: CPU): a, b = (get_arg_from_ins(ins, i, cpu) for i in (0, 2)) return a == b def assert_equals_mem(ins: Instruction, cpu: CPU): a, b = (get_arg_from_ins(ins, i, cpu) for i in (0, 2)) a = cpu.mmu.read_int(a) return a == b def assert_in(ins: Instruction, cpu: CPU): a = get_arg_from_ins(ins, 0, cpu) others = [get_arg_from_ins(ins, i, cpu) for i in range(2, len(ins.args))] return a in others def _not(func): def test(ins: Instruction, cpu: CPU): return not func(ins, cpu) return test def get_arg_from_ins(ins: Instruction, num: int, cpu: CPU): a = ins.args[num] if a in RISCV_REGS: return cpu.regs.get(a) return ins.get_imm(num) assert_ops = { "==": assert_equals, "!=": _not(assert_equals), "in": assert_in, "not_in": _not(assert_in), } class Z_test(InstructionSet): def __init__(self, cpu: "CPU"): print( "[Test] loading testing ISA, this is only meant for running testcases and is not part of the RISC-V ISA!" ) self.failed = False super().__init__(cpu) def instruction_assert(self, ins: Instruction): if len(ins.args) < 3: print( FMT_ERROR + "[Test] Unknown assert statement: {}".format(ins) + FMT_NONE ) return op = ins.args[1] if op not in assert_ops: print( FMT_ERROR + "[Test] Unknown operation statement: {} in {}".format(op, ins) + FMT_NONE ) return if assert_ops[op](ins, self.cpu): print(FMT_SUCCESS + "[TestCase] 🟢 passed assertion {}".format(ins)) else: print(FMT_ERROR + "[TestCase] 🔴 failed assertion {}".format(ins)) self.cpu.halted = True self.failed = True def instruction_fail(self, ins: Instruction): print(FMT_ERROR + "[TestCase] 🔴 reached fail instruction! {}".format(ins)) self.cpu.halted = True self.failed = True riscemu-2.2.5/test/test_mstatus.py000066400000000000000000000011721451200553000172530ustar00rootroot00000000000000from riscemu.core.csr import MStatusRegister def test_mstatus_bits(): status = MStatusRegister() status.mpie = 1 assert "{:032b}".format(int(status.state)) == "00000000000000000000000010000000" status.mpie = 0 assert "{:032b}".format(int(status.state)) == "00000000000000000000000000000000" status.mpp = 3 assert "{:032b}".format(int(status.state)) == "00000000000000000001100000000000" status.sd = 1 assert "{:032b}".format(int(status.state)) == "10000000000000000001100000000000" status.mpp = 1 assert "{:032b}".format(int(status.state)) == "10000000000000000000100000000000" riscemu-2.2.5/test/test_regs.py000066400000000000000000000014651451200553000165200ustar00rootroot00000000000000import pytest from riscemu.core.registers import Registers from riscemu.core import Float32 def test_float_regs(): r = Registers() # uninitialized register is zero assert r.get_f("fs0") == 0 # get/set val = Float32(3.14) r.set_f("fs0", val) assert r.get_f("fs0") == val def test_float_regs_flen64(): r = Registers(flen=64) # uninitialized register is zero assert r.get_f("fs0") == 0 # get/set val = Float32(3.14) r.set_f("fs0", val) assert Float32.bitcast(r.get_f("fs0")) == val def test_unlimited_regs_works(): r = Registers(infinite_regs=True) r.get("infinite") r.get_f("finfinite") def test_unknown_reg_fails(): r = Registers(infinite_regs=False) with pytest.raises(RuntimeError, match="Invalid register: az1"): r.get("az1") riscemu-2.2.5/test/test_tokenizer.py000066400000000000000000000107041451200553000175660ustar00rootroot00000000000000from unittest import TestCase from riscemu.tokenizer import ( tokenize, print_tokens, Token, TokenType, NEWLINE, COMMA, split_whitespace_respecting_quotes, ) def ins(name: str) -> Token: return Token(TokenType.INSTRUCTION_NAME, name) def arg(name: str) -> Token: return Token(TokenType.ARGUMENT, name) def op(name: str) -> Token: return Token(TokenType.PSEUDO_OP, name) def lbl(name: str) -> Token: return Token(TokenType.LABEL, name) class TestTokenizer(TestCase): def test_instructions(self): program = ["li a0, 144", "divi a0, a0, 12", "xori a1, a0, 12"] tokens = [ ins("li"), arg("a0"), COMMA, arg("144"), NEWLINE, ins("divi"), arg("a0"), COMMA, arg("a0"), COMMA, arg("12"), NEWLINE, ins("xori"), arg("a1"), COMMA, arg("a0"), COMMA, arg("12"), NEWLINE, ] self.assertEqual(list(tokenize(program)), tokens) def test_comments(self): parsed_res = [ins("li"), arg("a0"), COMMA, arg("144"), NEWLINE] for c in ("#", "//", ";"): lines = [c + " this is a comment", "li a0, 144"] self.assertEqual(list(tokenize(lines)), parsed_res) def test_pseudo_ins(self): parsed_res = [ Token(TokenType.PSEUDO_OP, ".section"), Token(TokenType.ARGUMENT, ".text"), NEWLINE, Token(TokenType.PSEUDO_OP, ".type"), Token(TokenType.ARGUMENT, "init"), COMMA, Token(TokenType.ARGUMENT, "@function"), NEWLINE, ] input_program = [".section .text", ".type init, @function"] self.assertEqual(list(tokenize(input_program)), parsed_res) def test_full_program(self): program = """ # a hashtag comment ; semicolon comment followed by an empty line .section .text // double slash comment addi sp, sp, -32 sw s0, 0(ra) section: sub s0, s0, s0 """ tokens = [ op(".section"), arg(".text"), NEWLINE, ins("addi"), arg("sp"), COMMA, arg("sp"), COMMA, arg("-32"), NEWLINE, ins("sw"), arg("s0"), COMMA, arg("ra"), arg("0"), NEWLINE, lbl("section:"), NEWLINE, ins("sub"), arg("s0"), COMMA, arg("s0"), COMMA, arg("s0"), NEWLINE, ] self.assertEqual(list(tokenize(program.splitlines())), tokens) def test_split_whitespace_respecting_quotes_single(self): self.assertEqual(list(split_whitespace_respecting_quotes("test")), ["test"]) def test_split_whitespace_respecting_quotes_empty(self): self.assertEqual(list(split_whitespace_respecting_quotes("")), []) def test_split_whitespace_respecting_quotes_two_parts(self): self.assertEqual( list(split_whitespace_respecting_quotes("test 123")), ["test", "123"] ) def test_split_whitespace_respecting_quotes_whole_quoted(self): self.assertEqual( list(split_whitespace_respecting_quotes("'test 123'")), ["test 123"] ) def test_split_whitespace_respecting_quotes_double_quotes(self): self.assertEqual( list(split_whitespace_respecting_quotes('"test 123"')), ["test 123"] ) def test_split_whitespace_respecting_quotes_quoted_then_normal(self): self.assertEqual( list(split_whitespace_respecting_quotes('"test 123" abc')), ["test 123", "abc"], ) def test_split_whitespace_respecting_quotes_quoted_sorrounded(self): self.assertEqual( list(split_whitespace_respecting_quotes('hello "test 123" abc')), ["hello", "test 123", "abc"], ) def test_split_whitespace_respecting_quotes_weird_spaces(self): self.assertEqual( list(split_whitespace_respecting_quotes('hello "test 123"\tabc')), ["hello", "test 123", "abc"], ) def test_split_whitespace_respecting_quotes_quotes_no_spaces(self): self.assertEqual( list(split_whitespace_respecting_quotes('hello"test 123"abc')), ["hello", "test 123", "abc"], )