Repository: cvut/qtrvsim Branch: master Commit: 4798b0b7d13b Files: 380 Total size: 1.7 MB Directory structure: gitextract_up_kab8w/ ├── .github/ │ └── workflows/ │ ├── debug.yml │ ├── docs.yml │ └── release.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── Makefile ├── README.md ├── cmake/ │ ├── AddFileHashes.cmake │ ├── BuildType.cmake │ ├── BundleMacOS.cmake │ ├── CopyrightTools.cmake │ ├── FindElfUtils.cmake │ ├── FindLibElf.cmake │ ├── FindPythonInterp.cmake │ ├── GPL-3.0-or-later.cmake │ ├── LibElfinSettings.cmake │ ├── PackageTools.cmake │ └── TestingTools.cmake ├── data/ │ ├── cz.cvut.edu.comparch.qtrvsim.metainfo.xml.in │ ├── gui.desktop.in │ ├── icons/ │ │ └── macos/ │ │ └── gui.icns │ └── wasm/ │ ├── browserconfig.xml │ ├── index.html │ └── manifest.json ├── default.nix ├── docs/ │ ├── developer/ │ │ ├── build&deploy/ │ │ │ ├── building-wasm.md │ │ │ └── release.md │ │ ├── coreview-graphics/ │ │ │ └── using-drawio-diagram.md │ │ ├── debuging/ │ │ │ ├── logging.md │ │ │ └── sanitizers.md │ │ └── need-to-know.md │ └── user/ │ ├── SUMMARY.md │ ├── basics/ │ │ ├── basics_of_user_interface.md │ │ ├── first_launch.md │ │ ├── getting_started.md │ │ ├── menus_and_the_toolbar.md │ │ └── writing_programs.md │ ├── book.toml │ ├── introduction.md │ └── reference/ │ ├── advanced_configuration/ │ │ └── environment_variables.md │ ├── advanced_configuration.md │ └── external_toolchains.md ├── external/ │ ├── compiler/ │ │ ├── compile.sh │ │ └── config │ └── svgscene/ │ ├── CMakeLists.txt │ ├── LICENSE │ ├── README.md │ └── src/ │ ├── example/ │ │ ├── main.cpp │ │ ├── mainwindow.cpp │ │ ├── mainwindow.h │ │ └── mainwindow.ui │ └── svgscene/ │ ├── components/ │ │ ├── groupitem.cpp │ │ ├── groupitem.h │ │ ├── hyperlinkitem.cpp │ │ ├── hyperlinkitem.h │ │ ├── simpletextitem.cpp │ │ └── simpletextitem.h │ ├── graphicsview/ │ │ ├── svggraphicsview.cpp │ │ └── svggraphicsview.h │ ├── polyfills/ │ │ └── qt5/ │ │ ├── qstringview.h │ │ └── qwheelevent.h │ ├── svgdocument.cpp │ ├── svgdocument.h │ ├── svggraphicsscene.cpp │ ├── svggraphicsscene.h │ ├── svghandler.cpp │ ├── svghandler.h │ ├── svgmetadata.cpp │ ├── svgmetadata.h │ ├── svgspec.h │ └── utils/ │ ├── logging.h │ └── memory_ownership.h ├── extras/ │ ├── building/ │ │ └── build-wasm.sh │ ├── core_graphics/ │ │ └── diagram.drawio │ ├── crosscompiling/ │ │ ├── shell-mips-elf.nix │ │ ├── shell-riscv-elf-with-newlib.nix │ │ └── shell-riscv-elf.nix │ └── packaging/ │ ├── _tools/ │ │ ├── add-to-changelog.sh │ │ └── git-archive-submodules.sh │ ├── add-to-changelog.sh │ ├── appimage/ │ │ └── appimage.yml.in │ ├── arch/ │ │ └── PKGBUILD.in │ ├── deb/ │ │ ├── debian/ │ │ │ ├── changelog │ │ │ ├── compat │ │ │ ├── control.in │ │ │ ├── copyright │ │ │ ├── docs │ │ │ ├── rules │ │ │ └── source/ │ │ │ └── format │ │ └── dsc.in │ ├── flatpak/ │ │ └── cz.cvut.edu.comparch.qtrvsim.json │ ├── mingw/ │ │ └── cmake-i686-w64-mingw32.conf │ ├── nix/ │ │ └── qtrvsim.nix │ └── rpm/ │ └── spec.in ├── qtlogging.ini ├── src/ │ ├── assembler/ │ │ ├── CMakeLists.txt │ │ ├── fixmatheval.cpp │ │ ├── fixmatheval.h │ │ ├── messagetype.h │ │ ├── simpleasm.cpp │ │ └── simpleasm.h │ ├── cli/ │ │ ├── CMakeLists.txt │ │ ├── chariohandler.cpp │ │ ├── chariohandler.h │ │ ├── main.cpp │ │ ├── msgreport.cpp │ │ ├── msgreport.h │ │ ├── reporter.cpp │ │ ├── reporter.h │ │ ├── tracer.cpp │ │ └── tracer.h │ ├── common/ │ │ ├── CMakeLists.txt │ │ ├── containers/ │ │ │ └── cvector.h │ │ ├── endian.h │ │ ├── logging.h │ │ ├── logging_format_colors.h │ │ ├── math/ │ │ │ └── bit_ops.h │ │ ├── memory_ownership.h │ │ ├── polyfills/ │ │ │ ├── CMakeLists.txt │ │ │ ├── byteswap.h │ │ │ ├── clz32.h │ │ │ ├── endian_detection.h │ │ │ ├── mulh64.h │ │ │ ├── mulh64.test.cpp │ │ │ ├── mulh64.test.h │ │ │ ├── qstring_hash.h │ │ │ └── qt5/ │ │ │ ├── qfontmetrics.h │ │ │ ├── qlinef.h │ │ │ └── qtableview.h │ │ ├── string_utils.h │ │ └── type_utils/ │ │ └── lens.h │ ├── gui/ │ │ ├── CMakeLists.txt │ │ ├── dialogs/ │ │ │ ├── about/ │ │ │ │ ├── aboutdialog.cpp │ │ │ │ └── aboutdialog.h │ │ │ ├── gotosymbol/ │ │ │ │ ├── gotosymboldialog.cpp │ │ │ │ ├── gotosymboldialog.h │ │ │ │ └── gotosymboldialog.ui │ │ │ ├── new/ │ │ │ │ ├── NewDialog.ui │ │ │ │ ├── NewDialogCache.ui │ │ │ │ ├── newdialog.cpp │ │ │ │ └── newdialog.h │ │ │ └── savechanged/ │ │ │ ├── savechangeddialog.cpp │ │ │ └── savechangeddialog.h │ │ ├── extprocess.cpp │ │ ├── extprocess.h │ │ ├── fontsize.cpp │ │ ├── fontsize.h │ │ ├── graphicsview.cpp │ │ ├── graphicsview.h │ │ ├── helper/ │ │ │ └── async_modal.h │ │ ├── hinttabledelegate.cpp │ │ ├── hinttabledelegate.h │ │ ├── main.cpp │ │ ├── mainwindow/ │ │ │ ├── MainWindow.ui │ │ │ ├── mainwindow.cpp │ │ │ └── mainwindow.h │ │ ├── qhtml5file.h │ │ ├── qhtml5file_html5.cpp │ │ ├── resources/ │ │ │ ├── icons/ │ │ │ │ ├── gui.icns │ │ │ │ └── icons.qrc │ │ │ └── samples/ │ │ │ ├── samples.qrc │ │ │ ├── simple-lw-sw-ia.S │ │ │ ├── template-os.S │ │ │ └── template.S │ │ ├── statictable.cpp │ │ ├── statictable.h │ │ ├── textsignalaction.cpp │ │ ├── textsignalaction.h │ │ ├── ui/ │ │ │ ├── hexlineedit.cpp │ │ │ ├── hexlineedit.h │ │ │ ├── pow2spinbox.cpp │ │ │ └── pow2spinbox.h │ │ ├── widgets/ │ │ │ ├── hidingtabwidget.cpp │ │ │ └── hidingtabwidget.h │ │ └── windows/ │ │ ├── cache/ │ │ │ ├── cachedock.cpp │ │ │ ├── cachedock.h │ │ │ ├── cacheview.cpp │ │ │ └── cacheview.h │ │ ├── coreview/ │ │ │ ├── components/ │ │ │ │ ├── cache.cpp │ │ │ │ ├── cache.h │ │ │ │ ├── value_handlers.cpp │ │ │ │ └── value_handlers.h │ │ │ ├── data.h │ │ │ ├── scene.cpp │ │ │ ├── scene.h │ │ │ └── schemas/ │ │ │ └── schemas.qrc │ │ ├── csr/ │ │ │ ├── csrdock.cpp │ │ │ └── csrdock.h │ │ ├── editor/ │ │ │ ├── editordock.cpp │ │ │ ├── editordock.h │ │ │ ├── editortab.cpp │ │ │ ├── editortab.h │ │ │ ├── highlighterasm.cpp │ │ │ ├── highlighterasm.h │ │ │ ├── highlighterc.cpp │ │ │ ├── highlighterc.h │ │ │ ├── linenumberarea.cpp │ │ │ ├── linenumberarea.h │ │ │ ├── srceditor.cpp │ │ │ └── srceditor.h │ │ ├── lcd/ │ │ │ ├── lcddisplaydock.cpp │ │ │ ├── lcddisplaydock.h │ │ │ ├── lcddisplayview.cpp │ │ │ └── lcddisplayview.h │ │ ├── memory/ │ │ │ ├── memorydock.cpp │ │ │ ├── memorydock.h │ │ │ ├── memorymodel.cpp │ │ │ ├── memorymodel.h │ │ │ ├── memorytableview.cpp │ │ │ └── memorytableview.h │ │ ├── messages/ │ │ │ ├── messagesdock.cpp │ │ │ ├── messagesdock.h │ │ │ ├── messagesmodel.cpp │ │ │ ├── messagesmodel.h │ │ │ ├── messagesview.cpp │ │ │ └── messagesview.h │ │ ├── peripherals/ │ │ │ ├── peripheralsdock.cpp │ │ │ ├── peripheralsdock.h │ │ │ ├── peripheralsview.cpp │ │ │ ├── peripheralsview.h │ │ │ └── peripheralsview.ui │ │ ├── predictor/ │ │ │ ├── predictor_bht_dock.cpp │ │ │ ├── predictor_bht_dock.h │ │ │ ├── predictor_btb_dock.cpp │ │ │ ├── predictor_btb_dock.h │ │ │ ├── predictor_info_dock.cpp │ │ │ └── predictor_info_dock.h │ │ ├── program/ │ │ │ ├── programdock.cpp │ │ │ ├── programdock.h │ │ │ ├── programmodel.cpp │ │ │ ├── programmodel.h │ │ │ ├── programtableview.cpp │ │ │ └── programtableview.h │ │ ├── registers/ │ │ │ ├── registersdock.cpp │ │ │ └── registersdock.h │ │ ├── terminal/ │ │ │ ├── terminaldock.cpp │ │ │ └── terminaldock.h │ │ └── tlb/ │ │ ├── tlbdock.cpp │ │ ├── tlbdock.h │ │ ├── tlbview.cpp │ │ └── tlbview.h │ ├── machine/ │ │ ├── CMakeLists.txt │ │ ├── bitfield.h │ │ ├── config_isa.h │ │ ├── core/ │ │ │ └── core_state.h │ │ ├── core.cpp │ │ ├── core.h │ │ ├── core.test.cpp │ │ ├── core.test.h │ │ ├── csr/ │ │ │ ├── address.h │ │ │ ├── controlstate.cpp │ │ │ └── controlstate.h │ │ ├── execute/ │ │ │ ├── alu.cpp │ │ │ ├── alu.h │ │ │ ├── alu.test.cpp │ │ │ ├── alu.test.h │ │ │ ├── alu_op.h │ │ │ └── mul_op.h │ │ ├── instruction.cpp │ │ ├── instruction.h │ │ ├── instruction.test.cpp │ │ ├── instruction.test.h │ │ ├── machine.cpp │ │ ├── machine.h │ │ ├── machineconfig.cpp │ │ ├── machineconfig.h │ │ ├── machinedefs.h │ │ ├── memory/ │ │ │ ├── address.h │ │ │ ├── address_range.h │ │ │ ├── address_with_mode.h │ │ │ ├── backend/ │ │ │ │ ├── aclintmswi.cpp │ │ │ │ ├── aclintmswi.h │ │ │ │ ├── aclintmtimer.cpp │ │ │ │ ├── aclintmtimer.h │ │ │ │ ├── aclintsswi.cpp │ │ │ │ ├── aclintsswi.h │ │ │ │ ├── backend_memory.h │ │ │ │ ├── lcddisplay.cpp │ │ │ │ ├── lcddisplay.h │ │ │ │ ├── memory.cpp │ │ │ │ ├── memory.h │ │ │ │ ├── memory.test.cpp │ │ │ │ ├── memory.test.h │ │ │ │ ├── peripheral.cpp │ │ │ │ ├── peripheral.h │ │ │ │ ├── peripspiled.cpp │ │ │ │ ├── peripspiled.h │ │ │ │ ├── serialport.cpp │ │ │ │ └── serialport.h │ │ │ ├── cache/ │ │ │ │ ├── cache.cpp │ │ │ │ ├── cache.h │ │ │ │ ├── cache.test.cpp │ │ │ │ ├── cache.test.h │ │ │ │ ├── cache_policy.cpp │ │ │ │ ├── cache_policy.h │ │ │ │ └── cache_types.h │ │ │ ├── frontend_memory.cpp │ │ │ ├── frontend_memory.h │ │ │ ├── memory_bus.cpp │ │ │ ├── memory_bus.h │ │ │ ├── memory_utils.h │ │ │ ├── tlb/ │ │ │ │ ├── tlb.cpp │ │ │ │ ├── tlb.h │ │ │ │ ├── tlb_policy.cpp │ │ │ │ └── tlb_policy.h │ │ │ └── virtual/ │ │ │ ├── page_table_walker.cpp │ │ │ ├── page_table_walker.h │ │ │ ├── sv32.h │ │ │ └── virtual_address.h │ │ ├── pipeline.h │ │ ├── predictor.cpp │ │ ├── predictor.h │ │ ├── predictor_types.h │ │ ├── programloader.cpp │ │ ├── programloader.h │ │ ├── programloader.test.cpp │ │ ├── programloader.test.h │ │ ├── register_value.h │ │ ├── registers.cpp │ │ ├── registers.h │ │ ├── registers.test.cpp │ │ ├── registers.test.h │ │ ├── simulator_exception.cpp │ │ ├── simulator_exception.h │ │ ├── symboltable.cpp │ │ ├── symboltable.h │ │ ├── tests/ │ │ │ ├── data/ │ │ │ │ └── cache_test_performance_data.h │ │ │ └── utils/ │ │ │ └── integer_decomposition.h │ │ └── utils.h │ ├── os_emulation/ │ │ ├── CMakeLists.txt │ │ ├── ossyscall.cpp │ │ ├── ossyscall.h │ │ ├── posix_polyfill.h │ │ ├── syscall_nr.h │ │ ├── syscallent.h │ │ └── target_errno.h │ └── project_info.h.in └── tests/ ├── cli/ │ ├── asm_error/ │ │ └── program.S │ ├── modifiers/ │ │ ├── program.S │ │ └── stdout.txt │ ├── modifiers-pcrel/ │ │ ├── program.S │ │ └── stdout.txt │ ├── stalls/ │ │ ├── program.S │ │ └── stdout.txt │ └── virtual_memory/ │ ├── dtlb/ │ │ ├── program.S │ │ └── stdout.txt │ ├── exec/ │ │ ├── program.S │ │ └── stdout.txt │ ├── itlb/ │ │ ├── program.S │ │ └── stdout.txt │ ├── memrw/ │ │ ├── program.S │ │ └── stdout.txt │ └── template/ │ ├── program.S │ └── stdout.txt ├── riscv-official/ │ ├── .gitignore │ ├── README.md │ ├── code/ │ │ ├── constants.py │ │ ├── helpers.py │ │ ├── myparse.py │ │ ├── selftesting.py │ │ └── testing.py │ ├── env/ │ │ └── p/ │ │ ├── BackUp/ │ │ │ └── riscv_test.h │ │ ├── link.ld │ │ └── riscv_test.h │ ├── isa/ │ │ ├── .gitignore │ │ ├── Makefile │ │ ├── selftests/ │ │ │ ├── Makefile │ │ │ ├── options_test.sh │ │ │ └── tests/ │ │ │ ├── addi-fail32.S │ │ │ ├── addi-fail64.S │ │ │ ├── simple-fail32.S │ │ │ ├── simple-fail64.S │ │ │ ├── simple-pass32.S │ │ │ └── simple-pass64.S │ │ └── toolchain_setup │ └── qtrvsim_tester.py └── stud-support/ ├── build_tests.py └── run_tests.py ================================================ FILE CONTENTS ================================================ ================================================ FILE: .github/workflows/debug.yml ================================================ name: Debug on: push: paths-ignore: - "**/*.md" - "docs/**" pull_request: paths-ignore: - "**/*.md" - "docs/**" merge_group: paths-ignore: - "**/*.md" - "docs/**" workflow_dispatch: jobs: build: name: ${{ matrix.config.name }} env: QtLatestVersion: &QtLatest "6.10.1" QtBrewVersion: &QtBrew "6.9.3" QtMinimalVersion: &QtMinimal "5.12.0" needs: [build-riscv-tests, build-stud-support-tests] # Avoid duplicate builds on PR from the same repository if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: # Linux testing - { name: "Ubuntu latest (GCC) + packaged Qt6", os: ubuntu-latest, build_type: "Debug", cc: "gcc", cxx: "g++", build_system: "Unix Makefiles", qt_version: "native", qt_install_command: "sudo apt-get update && sudo apt-get install qt6-base-dev", } - { name: "Ubuntu latest (GCC) + packaged Qt5", os: ubuntu-latest, build_type: "Debug", cc: "gcc", cxx: "g++", build_system: "Unix Makefiles", qt_version: "native", qt_install_command: "sudo apt-get update && sudo apt-get install qtbase5-dev", } - { name: "Ubuntu latest (GCC) + latest Qt6", os: ubuntu-latest, build_type: "Debug", cc: "gcc", cxx: "g++", build_system: "Unix Makefiles", qt_version: *QtLatest, } - { name: "Ubuntu oldest (GCC) + minimal supported Qt", os: ubuntu-22.04, build_type: "Debug", cc: "gcc", cxx: "g++", build_system: "Unix Makefiles", qt_version: *QtMinimal, } # macOS testing - { name: "macOS latest ARM64 (Clang) + Qt6", os: macos-latest, build_type: "Debug", cc: "clang", cxx: "clang++", build_system: "Unix Makefiles", qt_version: *QtBrew, # Cached aqt is faster that brew. } - { name: "macOS latest AMD64 (Clang) + Qt6", os: macos-15-intel, build_type: "Debug", cc: "clang", cxx: "clang++", build_system: "Unix Makefiles", qt_version: *QtBrew, # Cached aqt is faster that brew. } - { name: "macOS oldest AMD64 (Clang) + minimal supported Qt", os: macos-15-intel, build_type: "Debug", cc: "clang", cxx: "clang++", build_system: "Unix Makefiles", qt_version: *QtMinimal, # Cached aqt is faster that brew. } # Windows testing - { name: "Windows latest (Clang) + Qt6", os: windows-latest, build_type: "Debug", # on Windows, msbuild does not support Clang with GNU-like interface # and NMake does not support parallel builds cc: "clang", cxx: "clang++", build_system: "Ninja", qt_version: *QtLatest, qt_arch: "win64_msvc2022_64", } - { name: "Windows oldest (Clang) + minimal supported Qt", os: windows-2022, build_type: "Debug", # on Windows, msbuild does not support Clang with GNU-like interface # and NMake does not support parallel builds cc: "clang", cxx: "clang++", build_system: "Ninja", qt_version: *QtMinimal, qt_arch: "win64_msvc2017_64", } - { name: "Windows 2022 (MinGW) + Qt5", os: windows-2022, build_type: "Debug", cc: "gcc", cxx: "g++", build_system: "Ninja", # Older Qt releases do not have 64bit mingw release qt_version: "5.12.9", qt_arch: "win64_mingw73", } steps: - uses: actions/checkout@v4 - run: git submodule update --recursive --init - name: Ccache uses: hendrikmuhs/ccache-action@v1 with: key: ${{ github.ref_name }}-${{ matrix.config.name }} - name: Install specified Qt version if: matrix.config.qt_version != 'native' uses: jurplel/install-qt-action@v4 with: version: ${{ matrix.config.qt_version }} cache: true cache-key-prefix: ${{ runner.os }}-${{ matrix.config.qt_version }}-Qt arch: ${{ matrix.config.qt_arch }} dir: ${{ github.workspace }}/Qt - name: Install native Qt by package manager if: matrix.config.qt_version == 'native' run: ${{ matrix.config.qt_install_command }} - name: Setup Ninja if: matrix.config.build_system == 'Ninja' uses: seanmiddleditch/gha-setup-ninja@v6 - name: Create Build Environment run: cmake -E make_directory ${{ github.workspace }}/build - name: Configure CMake shell: bash working-directory: ${{github.workspace}}/build run: 'cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} -DCMAKE_C_COMPILER=${{ matrix.config.cc }} -DCMAKE_CXX_COMPILER=${{ matrix.config.cxx }} -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DFORCE_COLORED_OUTPUT=true -G "${{ matrix.config.build_system }}"' - name: Build working-directory: ${{ github.workspace }}/build run: cmake --build . -j4 - name: Deploy on Windows if: runner.os == 'Windows' working-directory: ${{ github.workspace }}/build/target shell: bash run: 'windeployqt "${{ github.workspace }}/build/target/qtrvsim_gui.exe"' - name: Test working-directory: ${{ github.workspace }}/build shell: bash run: ctest --output-on-failure --verbose - name: Get official RISC-V tests if: ${{ steps.cache-tests.outputs.cache-hit != 'true' }} uses: actions/download-artifact@v4 with: name: riscv-official-tests path: tests/riscv-official/isa - name: Official RISC-V tests (single cycle) # The testing python script does not support Ubuntu 18 if: matrix.config.os != 'ubuntu-18.04' working-directory: ${{ github.workspace }}/tests/riscv-official #run: python qtrvsim_tester.py --no-64 ${{ github.workspace }}/build/target/qtrvsim_cli run: python qtrvsim_tester.py -M -A ${{ github.workspace }}/build/target/qtrvsim_cli - name: Official RISC-V tests (pipelined) # The testing python script does not support Ubuntu 18 if: matrix.config.os != 'ubuntu-18.04' working-directory: ${{ github.workspace }}/tests/riscv-official #run: python qtrvsim_tester.py --no-64 ${{ github.workspace }}/build/target/qtrvsim_cli run: python qtrvsim_tester.py -M -A --pipeline ${{ github.workspace }}/build/target/qtrvsim_cli - name: Official RISC-V tests (single cycle, cached) # The testing python script does not support Ubuntu 18 if: matrix.config.os != 'ubuntu-18.04' working-directory: ${{ github.workspace }}/tests/riscv-official #run: python qtrvsim_tester.py --no-64 ${{ github.workspace }}/build/target/qtrvsim_cli run: python qtrvsim_tester.py -M -A --cache ${{ github.workspace }}/build/target/qtrvsim_cli - name: Official RISC-V tests (pipelined, cached) # The testing python script does not support Ubuntu 18 if: matrix.config.os != 'ubuntu-18.04' working-directory: ${{ github.workspace }}/tests/riscv-official #run: python qtrvsim_tester.py --no-64 ${{ github.workspace }}/build/target/qtrvsim_cli run: python qtrvsim_tester.py -M -A --pipeline --cache ${{ github.workspace }}/build/target/qtrvsim_cli - name: Get stud-support tests uses: actions/download-artifact@v4 with: name: stud-support-tests path: tests/stud-support/elfs - name: Stud-support tests (single cycle) if: matrix.config.os != 'ubuntu-18.04' working-directory: ${{ github.workspace }}/tests/stud-support run: python run_tests.py --qtrvsim-cli ${{ github.workspace }}/build/target/qtrvsim_cli --build-dir elfs --stud-support-path . - name: Stud-support tests (pipelined) if: matrix.config.os != 'ubuntu-18.04' working-directory: ${{ github.workspace }}/tests/stud-support run: python run_tests.py --qtrvsim-cli ${{ github.workspace }}/build/target/qtrvsim_cli --build-dir elfs --stud-support-path . --pipeline - name: Stud-support tests (single cycle, cached) if: matrix.config.os != 'ubuntu-18.04' working-directory: ${{ github.workspace }}/tests/stud-support run: python run_tests.py --qtrvsim-cli ${{ github.workspace }}/build/target/qtrvsim_cli --build-dir elfs --stud-support-path . --cache - name: Stud-support tests (pipelined, cached) if: matrix.config.os != 'ubuntu-18.04' working-directory: ${{ github.workspace }}/tests/stud-support run: python run_tests.py --qtrvsim-cli ${{ github.workspace }}/build/target/qtrvsim_cli --build-dir elfs --stud-support-path . --pipeline --cache - name: Store created artifacts uses: actions/upload-artifact@v4 with: name: target-${{ runner.name }} path: ${{ github.workspace }}/build/target build-emscripten: name: ${{ matrix.config.name }} # Avoid duplicate builds on PR from the same repository if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ${{ matrix.config.os }} strategy: fail-fast: false matrix: config: - { name: "WASM Linux", os: ubuntu-latest, build_type: Release, qt_arch: wasm_32, emsdk_version: 1.39.8, qt_version: 5.15.2, } steps: - uses: actions/checkout@v4 with: submodules: recursive - name: Ccache uses: hendrikmuhs/ccache-action@v1.2.11 # https://github.com/hendrikmuhs/ccache-action/issues/181 with: key: ${{ github.ref_name }}-${{ matrix.config.name }} - name: Setup EMSDK cache id: cache-system-libraries uses: actions/cache@v4 with: path: "emsdk-cache" key: ${{ runner.os }}-${{ matrix.config.emsdk_version }}-${{ matrix.config.qt_version }}-emsdk - name: Setup emsdk uses: mymindstorm/setup-emsdk@v14 with: version: ${{ matrix.config.emsdk_version }} actions-cache-folder: "emsdk-cache" - name: Install Qt uses: jurplel/install-qt-action@v3 with: version: ${{ matrix.config.qt_version }} cache: true cache-key-prefix: "wasm-qt" arch: ${{ matrix.config.qt_arch }} dir: ${{ github.workspace }}/Qt - name: Create Build Environment run: cmake -E make_directory ${{github.workspace}}/build - name: Configure CMake working-directory: ${{github.workspace}}/build run: "emcmake cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} -DCMAKE_PREFIX_PATH=$Qt5_DIR -DCMAKE_FIND_ROOT_PATH=$Qt5_DIR -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DFORCE_COLORED_OUTPUT=true -Wno-dev" - name: Build working-directory: ${{ github.workspace }}/build run: cmake --build . -j4 - name: Store created artifacts uses: actions/upload-artifact@v4 with: name: target-wasm-${{ runner.os }}-qt${{ matrix.config.qt_version }} path: ${{ github.workspace }}/build/target build-riscv-tests: name: Build official RISC-V tests # Avoid duplicate builds on PR from the same repository if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-22.04 steps: - uses: actions/checkout@v4 - run: git submodule update --recursive --init - name: Get cache key uses: mathiasvr/command-output@v2.0.0 id: subtree-hash with: run: git rev-parse HEAD:tests/riscv-official/ - name: Cache id: cache-tests uses: actions/cache@v4 with: path: ${{ github.workspace }}/tests/riscv-official/isa key: riscv-tests-${{ steps.subtree-hash.outputs.stdout }} - name: Toolchain for official RISC-V tests if: ${{ steps.cache-tests.outputs.cache-hit != 'true' }} working-directory: ${{ github.workspace }}/tests/riscv-official # Version 14 is needed to correctly process newer parts of RV run: | ### Ubuntu 22 already provides those # echo "deb http://apt.llvm.org/focal/ llvm-toolchain-focal-14 main" | sudo tee -a /etc/apt/sources.list && # wget -O - https://apt.llvm.org/llvm-snapshot.gpg.key | sudo apt-key add - && sudo apt-get update && sudo apt-get install clang-14 lldb-14 lld-14 - name: Build official RISC-V tests if: ${{ steps.cache-tests.outputs.cache-hit != 'true' }} working-directory: ${{ github.workspace }}/tests/riscv-official/isa env: RISCV_COMPILER: clang-14 USE_CLANG_OPTS: true RISCV_OBJDUMP_CMD: llvm-objdump-14 run: make - name: Store created artifacts # Use of tar significantly improves performance as artifact upload has significant per file penalty uses: actions/upload-artifact@v4 with: name: riscv-official-tests path: tests/riscv-official/isa build-stud-support-tests: name: Build stud-support tests # Avoid duplicate builds on PR from the same repository if: github.event_name != 'pull_request' || github.event.pull_request.head.repo.full_name != github.repository runs-on: ubuntu-22.04 env: STUD_SUPPORT_REV: 53c684c05dc990e6043fcc3c9a346b2173610731 steps: - uses: actions/checkout@v4 with: submodules: true - name: Cache stud-support tests id: cache-stud-support uses: actions/cache@v4 with: path: ${{ github.workspace }}/tests/stud-support/elfs key: stud-support-tests-${{ env.STUD_SUPPORT_REV }} - name: Checkout stud-support if: ${{ steps.cache-stud-support.outputs.cache-hit != 'true' }} run: | git clone https://gitlab.fel.cvut.cz/b35apo/stud-support.git stud-support cd stud-support git checkout ${{ env.STUD_SUPPORT_REV }} - name: Install RISC-V toolchain for stud-support if: ${{ steps.cache-stud-support.outputs.cache-hit != 'true' }} run: | sudo apt-get update && sudo apt-get install -y gcc-riscv64-unknown-elf - name: Build stud-support tests if: ${{ steps.cache-stud-support.outputs.cache-hit != 'true' }} working-directory: ${{ github.workspace }}/tests/stud-support run: python build_tests.py --stud-support-path ../../stud-support - name: Store stud-support artifacts uses: actions/upload-artifact@v4 with: name: stud-support-tests path: tests/stud-support/elfs ================================================ FILE: .github/workflows/docs.yml ================================================ name: Docs on: push: branches: - master paths: - "docs/user/**" - ".github/workflows/docs.yml" workflow_dispatch: permissions: contents: read pages: write id-token: write concurrency: group: "pages" cancel-in-progress: true jobs: build: runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v4 - name: Setup mdBook uses: peaceiris/actions-mdbook@v2 with: mdbook-version: "latest" - name: Build the book run: mdbook build docs/user - name: Setup Pages uses: actions/configure-pages@v5 - name: Prepare artifact with /manual subdirectory run: | rm -rf pages mkdir -p pages/manual cp -a docs/user/book/. pages/manual/ - name: Upload artifact uses: actions/upload-pages-artifact@v3 with: path: pages - name: Create ZIP for Comparch deployment run: | cd docs/user/book zip -r ../../../manual.zip . - name: Deploy to Comparch run: | curl -X POST -H "Authorization: Bearer ${{ secrets.COMPARCH_DOCS_TOKEN }}" -F "file=@manual.zip" http://eval.comparch.edu.cvut.cz:1111/github-actions/update-manual deploy: environment: name: github-pages url: ${{ steps.deployment.outputs.page_url }} runs-on: ubuntu-latest needs: build steps: - name: Deploy to GitHub Pages id: deployment uses: actions/deploy-pages@v4 ================================================ FILE: .github/workflows/release.yml ================================================ name: Release on: workflow_dispatch jobs: build: name: ${{ matrix.config.name }} runs-on: ${{ matrix.config.os }} strategy: fail-fast: true matrix: config: - { name: "OBS bundle (Ubuntu Latest GCC)", os: ubuntu-latest, build_type: "Release", cc: "gcc", cxx: "g++" } steps: - uses: actions/checkout@v4 # CMake will not configure without Qt installed - name: Install Qt run: sudo apt-get update && sudo apt-get install qtbase5-dev - name: Create Build Environment run: cmake -E make_directory ${{ github.workspace }}/build - name: Configure CMake shell: bash working-directory: ${{ github.workspace }}/build env: CC: ${{ matrix.config.cc }} CXX: ${{ matrix.config.cxx }} run: "cmake $GITHUB_WORKSPACE -DCMAKE_BUILD_TYPE=${{ matrix.config.build_type }} -DDEV_MODE=true" - name: Create OBS bundle working-directory: ${{ github.workspace }}/build run: make open_build_service_bundle - name: Store OBS bundle uses: actions/upload-artifact@v4 with: name: obs-package-bundle path: ${{ github.workspace }}/build/target/pkg ================================================ FILE: .gitignore ================================================ .* !.gitignore !.gitmodules !.github # Qt stuff *.pro.user* *.qbs.user* # Common test directory test_dir # Build directory build /CMakeFiles/ compile_commands.json /src/project_info.h # Local settings template .dev-config.mk ================================================ FILE: .gitmodules ================================================ [submodule "riscv-tests"] path = tests/riscv-official/riscv-tests url = https://github.com/riscv-software-src/riscv-tests.git [submodule "external/libelfin"] path = external/libelfin url = https://github.com/mortbopet/libelfin ================================================ FILE: CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) cmake_policy(VERSION 3.10) project(QtRVSim LANGUAGES C CXX VERSION 0.9.8 DESCRIPTION "RISC-V CPU simulator for education purposes") set(KAREL_KOCI "Karel Koci ") set(PAVEL_PISA "Pavel Pisa ") set(JAKUB_DUPAK "Jakub Dupak ") set(MAX_HOLLMANN "Max Hollmann ") set(PROJECT_HOMEPAGE_URL "https://github.com/cvut/qtrvsim") set(GENERIC_NAME "RISC-V CPU simulator") set(LICENCE "GPL-3.0-or-later") set(LONG_DESCRIPTION "RISC-V CPU simulator for education purposes with pipeline and cache visualization.") string(TIMESTAMP YEAR "%Y") include(cmake/CopyrightTools.cmake) copyright( "Copyright (c) 2017-2019 ${KAREL_KOCI}" "Copyright (c) 2019-${YEAR} ${PAVEL_PISA}" "Copyright (c) 2020-${YEAR} ${JAKUB_DUPAK}" "Copyright (c) 2020-2021 ${MAX_HOLLMANN}") include(cmake/GPL-3.0-or-later.cmake) # ============================================================================= # Configurable options # ============================================================================= set(DEV_MODE false CACHE BOOL "Enable developer options in this CMake, like packaging.\ They should be ignored, when user just wants to build this project.") set(FORCE_ELFLIB_STATIC false CACHE BOOL "Use included statically linked libelf even if system one is available.") set(SANITIZERS "address,undefined" CACHE STRING "Runtime sanitizers to use in debug builds. Column separated subset of {address, memory, undefined, thread} or none. Memory and address cannot be used at the same time.") set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}/target" CACHE STRING "Absolute path to place executables to.") set(PACKAGE_OUTPUT_PATH "${EXECUTABLE_OUTPUT_PATH}/pkg" CACHE STRING "Absolute path to place generated package files.") set(FORCE_COLORED_OUTPUT false CACHE BOOL "Always produce ANSI-colored output (GNU/Clang only).") set(USE_ALTERNATE_LINKER "" CACHE STRING "Use alternate linker. Leave empty for system default; alternatives are 'gold', 'lld', 'bfd', 'mold'") set(QT_VERSION_MAJOR "auto" CACHE STRING "Qt major version to use. 5|6|auto") # ============================================================================= # Generated variables # ============================================================================= if (NOT "${QT_VERSION_MAJOR}" MATCHES "5|6|auto") message(FATAL_ERROR "Invalid value for QT_VERSION_MAJOR: ${QT_VERSION_MAJOR} (expected 5, 6 or auto)") endif () if (${CMAKE_SYSTEM_NAME} MATCHES "Emscripten") set(WASM true) else () set(WASM false) endif () set(CXX_TEST_PATH ${EXECUTABLE_OUTPUT_PATH}) set(C_TEST_PATH ${EXECUTABLE_OUTPUT_PATH}) macro(set_alternate_linker linker) find_program(LINKER_EXECUTABLE ld.${USE_ALTERNATE_LINKER} ${USE_ALTERNATE_LINKER}) if (LINKER_EXECUTABLE) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang" AND "${CMAKE_CXX_COMPILER_VERSION}" VERSION_LESS 12.0.0) add_link_options("-ld-path=${USE_ALTERNATE_LINKER}") else () add_link_options("-fuse-ld=${USE_ALTERNATE_LINKER}") endif () else () set(USE_ALTERNATE_LINKER "" CACHE STRING "Use alternate linker" FORCE) endif () endmacro() if (NOT "${USE_ALTERNATE_LINKER}" STREQUAL "") set_alternate_linker(${USE_ALTERNATE_LINKER}) endif () # I don't want to relly on the assumption, that this file is invoked as root # project. Therefore I propagate the information to all subprojects # MAIN_PROJECT_*. Lowercase and uppercase are used for executable names and # C defines, respectively. set(MAIN_PROJECT_NAME "${PROJECT_NAME}") set(MAIN_PROJECT_VERSION "${PROJECT_VERSION}") set(MAIN_PROJECT_APPID "cz.cvut.edu.comparch.qtrvsim") set(MAIN_PROJECT_ORGANIZATION "FEE CTU") set(MAIN_PROJECT_HOMEPAGE_URL "${PROJECT_HOMEPAGE_URL}") string(TOLOWER "${PROJECT_NAME}" MAIN_PROJECT_NAME_LOWER) string(TOUPPER "${PROJECT_NAME}" MAIN_PROJECT_NAME_UPPER) # ============================================================================= # CMake config and tools # ============================================================================= list(APPEND CMAKE_MODULE_PATH "${PROJECT_SOURCE_DIR}/cmake") if (CMAKE_VERSION VERSION_LESS "3.7.0") set(CMAKE_INCLUDE_CURRENT_DIR ON) endif () include(cmake/BuildType.cmake) set(CMAKE_INCLUDE_CURRENT_DIR ON) # ============================================================================= # Build options # - common to all subdirs # ============================================================================= set(CMAKE_CXX_STANDARD 17) set(CMAKE_CXX_STANDARD_REQUIRED ON) # MSVC is not supported, Clang+MSVC currently does not seem to support sanitizers in with debug CRT, # MinGW does not support sanitizers at all; all together, just skip sanitizers on Windows and re-investigate # in a year or two whether Clang sanitizer support has improved if (NOT "${SANITIZERS}" MATCHES "none" AND NOT "${WASM}" AND NOT "${WIN32}") set(CMAKE_C_FLAGS_DEBUG "${CMAKE_C_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=${SANITIZERS} -g -g3 -ggdb") set(CMAKE_CXX_FLAGS_DEBUG "${CMAKE_CXX_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=${SANITIZERS} -g -g3 -ggdb") set(CMAKE_LINKER_FLAGS_DEBUG "${CMAKE_LINKER_FLAGS_DEBUG} -fno-omit-frame-pointer -fsanitize=${SANITIZERS}") endif () if (NOT "${BUILD_DEBUG}") message(STATUS "Debug prints globally suppressed.") add_definitions(-DQT_NO_DEBUG_OUTPUT=1) endif () add_compile_definitions(QT_USE_QSTRINGBUILDER) # Profiling flags if (NOT "${WASM}" AND NOT "${WIN32}") set(CMAKE_C_FLAGS_RELWITHDEBINFO "${CMAKE_C_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer") set(CMAKE_CXX_FLAGS_RELWITHDEBINFO "${CMAKE_CXX_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer") set(CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO "${CMAKE_SHARED_LINKER_FLAGS_RELWITHDEBINFO} -fno-omit-frame-pointer") endif () include_directories("src" "src/machine") if (${FORCE_COLORED_OUTPUT}) if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "GNU") add_compile_options(-fdiagnostics-color=always) elseif ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "Clang") add_compile_options(-fcolor-diagnostics) endif () endif () ## ============================================================================ ## Warning level ## ============================================================================ if (WIN32) # otherwise CRT complains that `strerror` is deprecated add_compile_definitions(_CRT_SECURE_NO_WARNINGS) endif () if (MSVC) add_compile_options(/W4 /WX) else () add_compile_options(-Wall -Wextra -Werror=switch) if (CMAKE_CXX_COMPILER_ID STREQUAL "Clang") # This is currently a wont-fix and it will be OK in cpp20. add_compile_options(-Wno-c99-designator) endif () endif () # ============================================================================= # Dependencies # ============================================================================= if ("${WASM}") message(STATUS "WASM build detected") message(STATUS "Enabled WASM exception handling") add_compile_options("-fexceptions") # Extra options for WASM linking add_link_options("-fexceptions") add_link_options("-flto") # Activate Embind C/C++ bindings # https://emscripten.org/docs/porting/connecting_cpp_and_javascript/embind.html add_link_options("--bind") # Enable C++ exception catching # https://emscripten.org/docs/optimizing/Optimizing-Code.html#c-exceptions add_link_options("SHELL:-s DISABLE_EXCEPTION_CATCHING=0") # Enable Fetch API # https://emscripten.org/docs/api_reference/fetch.html add_link_options("SHELL:-s FETCH=1") add_link_options("SHELL:-s WASM=1") # Emulate missing OpenGL ES2/ES3 features # https://emscripten.org/docs/porting/multimedia_and_graphics/OpenGL-support.html#opengl-es-2-0-3-0-emulation add_link_options("SHELL:-s FULL_ES2=1") add_link_options("SHELL:-s FULL_ES3=1") # Activate WebGL 2 (in addition to WebGL 1) # https://emscripten.org/docs/porting/multimedia_and_graphics/OpenGL-support.html#webgl-friendly-subset-of-opengl-es-2-0-3-0 add_link_options("SHELL:-s USE_WEBGL2=1") # Run static dtors at teardown # https://emscripten.org/docs/getting_started/FAQ.html#what-does-exiting-the-runtime-mean-why-don-t-atexit-s-run add_link_options("SHELL:-s ALLOW_MEMORY_GROWTH=1") # Export UTF16ToString,stringToUTF16 # Required by https://codereview.qt-project.org/c/qt/qtbase/+/286997 (since Qt 5.14) add_link_options("SHELL:-s EXTRA_EXPORTED_RUNTIME_METHODS=[\"UTF16ToString\",\"stringToUTF16\"]") # Enable demangling of C++ stack traces # https://emscripten.org/docs/porting/Debugging.html add_link_options("SHELL:-s DEMANGLE_SUPPORT=1") # Run static dtors at teardown # https://emscripten.org/docs/getting_started/FAQ.html#what-does-exiting-the-runtime-mean-why-don-t-atexit-s-run add_link_options("SHELL:-s EXIT_RUNTIME=1") # Debug build if ("${BUILD_DEBUG}") message(STATUS "Enabled WASM debug") add_link_options("SHELL:-s ERROR_ON_WASM_CHANGES_AFTER_LINK") add_link_options("SHELL:-s WASM_BIGINT") add_link_options("-O1") endif () add_definitions(-DQT_NO_DEBUG_OUTPUT=1) message(STATUS "Debug output disabled") else () # Not available for WASM enable_testing() endif () set(CMAKE_POLICY_VERSION_MINIMUM 3.5) set(CMAKE_PROJECT_libelfin_INCLUDE "${CMAKE_CURRENT_SOURCE_DIR}/cmake/LibElfinSettings.cmake") add_subdirectory("external/libelfin") # Detect Qt used qt version # Based on article https://www.steinzone.de/wordpress/how-to-support-both-qt5-and-qt6-using-cmake/ # Cannot use version-less approach due to Qt 5.9.5 support constraint. if ("${QT_VERSION_MAJOR}" STREQUAL "auto") find_package(QT NAMES Qt5 Qt6 COMPONENTS Core REQUIRED) endif () # Normally, we would use variable Qt5 or Qt6 to reference the Qt library. Here we do that through # this variable based on detected version major of Qt. set(QtLib "Qt${QT_VERSION_MAJOR}") find_package(${QtLib} REQUIRED COMPONENTS Core Widgets Gui Test OPTIONAL_COMPONENTS PrintSupport) message(STATUS "${QtLib} version: ${${QtLib}Core_VERSION}") message(STATUS "${QtLib} print support: ${${QtLib}PrintSupport_FOUND}") if ("${WASM}") string(TIMESTAMP DATE "%Y%m%d") file(READ "${${QtLib}Core_DIR}/../../../plugins/platforms/qtloader.js" QTLOADER) string(REPLACE "applicationName + \".js\"" "applicationName + \".js?v=${DATE}\"" QTLOADER "${QTLOADER}") string(REPLACE "applicationName + \".wasm\"" "applicationName + \".wasm?v=${DATE}\"" QTLOADER "${QTLOADER}") file(WRITE "${CMAKE_CURRENT_BINARY_DIR}/target/qtloader.js" "${QTLOADER}") endif () # Qt 5.9.5 is the oldest supported add_definitions(-DQT_DISABLE_DEPRECATED_BEFORE=0x050905) add_subdirectory("external/svgscene") # ============================================================================= # Sources # ============================================================================= add_subdirectory("src/common") add_subdirectory("src/machine") add_subdirectory("src/assembler") add_subdirectory("src/os_emulation") add_subdirectory("src/gui") if (NOT "${WASM}") add_subdirectory("src/cli") add_custom_target(all_unit_tests DEPENDS common_unit_tests machine_unit_tests) endif () # ============================================================================= # Installation # ============================================================================= # Prior to CMake version 3.13, installation must be performed in the subdirectory, # there the target was created. Therefore executable installation is to be found # in corresponding CMakeLists.txt. if (NOT "${WASM}") configure_file(data/gui.desktop.in "${EXECUTABLE_OUTPUT_PATH}/${MAIN_PROJECT_NAME_LOWER}.desktop") configure_file("data/${MAIN_PROJECT_APPID}.metainfo.xml.in" "${EXECUTABLE_OUTPUT_PATH}/${MAIN_PROJECT_APPID}.metainfo.xml") install(FILES "data/icons/gui.svg" DESTINATION "share/icons/hicolor/scalable/apps" RENAME "${MAIN_PROJECT_NAME_LOWER}_gui.svg") install(FILES "data/icons/48x48/gui.png" DESTINATION "share/icons/hicolor/48x48/apps" RENAME "${MAIN_PROJECT_NAME_LOWER}_gui.png") install(FILES "${EXECUTABLE_OUTPUT_PATH}/${MAIN_PROJECT_NAME_LOWER}.desktop" DESTINATION share/applications) install(FILES "${EXECUTABLE_OUTPUT_PATH}/${MAIN_PROJECT_APPID}.metainfo.xml" DESTINATION share/metainfo) endif () # ============================================================================= # Packages # ============================================================================= if ("${DEV_MODE}") # The condition prevents execution of this section during regular user installation. # It created files useless to normal users and requires additional tools (git, xz). message(STATUS "Packaging tools enabled.") set(PACKAGE_NAME "${MAIN_PROJECT_NAME_LOWER}") set(PACKAGE_VERSION "${PROJECT_VERSION}") set(PACKAGE_RELEASE "1") set(PACKAGE_SOURCE_ARCHIVE_FILE "${PACKAGE_NAME}_${PACKAGE_VERSION}.orig.tar.xz") set(PACKAGE_SOURCE_ARCHIVE_PATH "${PACKAGE_OUTPUT_PATH}/${PACKAGE_SOURCE_ARCHIVE_FILE}") set(PACKAGE_TOPLEVEL_DIR "${PACKAGE_NAME}-${PACKAGE_VERSION}") set(PACKAGE_DESCRIPTION "${PROJECT_DESCRIPTION}") set(PACKAGE_LONG_DESCRIPTION "${LONG_DESCRIPTION}") set(PACKAGE_MAINTAINER "${JAKUB_DUPAK}") set(PACKAGE_URL "${PROJECT_HOMEPAGE_URL}") set(PACKAGE_GIT "github.com:cvut/qtrvsim.git") set(PACKAGE_LICENCE "${LICENCE}") include(cmake/PackageTools.cmake) # Inject up-to-date information into package config files. package_config_file(appimage appimage.yml extras/packaging/appimage/appimage.yml.in) package_config_file(archlinux PKGBUILD extras/packaging/arch/PKGBUILD.in) package_config_file(rpm ${PACKAGE_NAME}.spec extras/packaging/rpm/spec.in) # Debian uses whole directory which has to be saved to archive and shipped. package_debian_quilt(deb ${PACKAGE_NAME}_${PACKAGE_VERSION}-${PACKAGE_RELEASE}.dsc extras/packaging/deb/dsc.in extras/packaging/deb/debian ${PACKAGE_NAME}_${PACKAGE_VERSION}-${PACKAGE_RELEASE}.debian.tar.xz) # Creates bunch of files in ${CMAKE_BINARY_DIR}/target/pkg that you can just pass to # Open Build Service and it will build all packaging. # TODO: Currently changelog is not handled automatically. add_custom_target(open_build_service_bundle DEPENDS ${PACKAGE_SOURCE_ARCHIVE_FILE} appimage archlinux deb rpm WORKING_DIRECTORY ${EXECUTABLE_OUTPUT_PATH}/pkg) endif () configure_file(src/project_info.h.in ${CMAKE_CURRENT_LIST_DIR}/src/project_info.h @ONLY) ================================================ FILE: LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: Makefile ================================================ include .dev-config.mk .dev-config.mk: [ ! -f .dev-config.mk ] && cp .dev-config.mk.default .dev-config.mk release: mkdir -p build/Release cd build/Release && cmake -DCMAKE_BUILD_TYPE=Release ../.. cd build/Release && cmake --build . debug: mkdir -p build/Debug cd build/Debug && cmake -DCMAKE_BUILD_TYPE=Debug ../.. cd build/Debug && cmake --build . ######################################################################################################################## # WASM ######################################################################################################################## wasm: bash extras/building/build-wasm.sh $(EMSCRIPTEN_VERSION) $(QT_WASM_PATH) $(EMSDK_PATH) wasm-clean: rm -rf $(WASM_BUILD_DIR) wasm-clean-deep: wasm-clean rm -rf $(EMSDK_PATH)/upstream/cache/* rm -rf $(EMSDK_PATH)/upstream/emscripten/cache/* wasm-serve: cp data/wasm/* $(WASM_BUILD_DIR)/target # Server is necessary due to CORS cd $(WASM_BUILD_DIR)/target/ && python -m http.server open localhost:8000 wasm-install-qt: python -m aqt install $(QT_WASM_VERSION) linux desktop wasm_32 --outputdir $(QT_INSTALL_DIR) .PHONY: release debug wasm wasm-clean wasm-clean-deep wasm-serve wasm-install-qt ================================================ FILE: README.md ================================================ # QtRvSim–RISC-V CPU simulator for education ![QtRvSim screenshot](data/screenshots/intro.webp) Developed by the [Computer Architectures Education](http://comparch.edu.cvut.cz) project at [Czech Technical University](http://www.cvut.cz/). **Are you using QtRvSim at your organization?** [Please, let us know in the discussion!](https://github.com/cvut/qtrvsim/discussions/136) ## Table of contents - [Try it out! (WebAssembly)](#try-it-out-webassembly) - [Build and packages](#build-and-packages) - [Build Dependencies](#build-dependencies) - [General Compilation](#general-compilation) - [Building from source on macOS](#building-from-source-on-macos) - [Download Binary Packages](#download-binary-packages) - [Nix package](#nix-package) - [Tests](#tests) - [Documentation](#documentation) - [Accepted Binary Formats](#accepted-binary-formats) - [LLVM toolchain usage](#llvm-toolchain-usage) - [GNU toolchain usage](#gnu-toolchain-usage) - [GNU 64-bit toolchain use for RV32I target](#gnu-64-bit-toolchain-use-for-rv32i-target) - [Integrated Assembler](#integrated-assembler) - [Support to call external make utility](#support-to-call-external-make-utility) - [Advanced functionalities](#advanced-functionalities) - [Peripherals](#peripherals) - [Interrupts and Control and Status Registers](#interrupts-and-control-and-status-registers) - [System Calls Support](#system-calls-support) - [Limitations of the Implementation](#limitations-of-the-implementation) - [QtRvSim limitations](#qtrvsim-limitations) - [List of Currently Supported Instructions](#list-of-currently-supported-instructions) - [Links to Resources and Similar Projects](#links-to-resources-and-similar-projects) - [Copyright](#copyright) - [License](#license) ## Try it out! (WebAssembly) QtRVSim is experimentally available for [WebAssembly](https://webassembly.org/) and it can be run in most browsers without installation. **[QtRVSim online](https://comparch.edu.cvut.cz/qtrvsim/app)** **Note, that WebAssembly version is experimental.** Please, report any difficulties via [GitHub issues](https://github.com/cvut/qtrvsim/issues/new). ## Build and packages [![Packaging status](https://repology.org/badge/vertical-allrepos/qtrvsim.svg)](https://repology.org/project/qtrvsim/versions) [![build result](https://build.opensuse.org/projects/home:jdupak/packages/qtrvsim/badge.svg?type=default)](https://build.opensuse.org/package/show/home:jdupak/qtrvsim) ### Build Dependencies - Qt 5 (minimal tested version is 5.9.5), experimental support of Qt 6 - elfutils (optional; libelf works too but there can be some problems) ### Quick Compilation on Linux On Linux, you can use a wrapper Makefile and run `make` in the project root directory. It will create a build directory and run CMake in it. Available targets are: `release` (default) and `debug`. Note for packagers: This Makefile is deleted by CMake when source archive is created to avoid any ambiguity. Packages should invoke CMake directly. ### General Compilation ```shell cmake -DCMAKE_BUILD_TYPE=Release /path/to/qtrvsim make ``` Where `/path/to/qtrvsim` is path to this project root. The built binaries are to be found in the directory `target`in the build directory (the one, where cmake was called). `-DCMAKE_BUILD_TYPE=Debug` builds development version including sanitizers. If no build type is supplied, `Debug` is the default. ### Building from source on macOS Install the latest version of **Xcode** from the App Store. Then open a terminal and execute `xcode-select --install` to install Command Line Tools. Then open Xcode, accept the license agreement and wait for it to install any additional components. After you finally see the "Welcome to Xcode" screen, from the top bar choose `Xcode -> Preferences -> Locations -> Command Line Tools` and select an SDK version. Install [Homebrew](https://brew.sh/) and use it to install Qt. (macOS builds must use the bundled libelf) ```shell brew install qt ``` Now build the project the same way as in general compilation ([above](#general-compilation)). ### Download Binary Packages - [https://github.com/cvut/qtrvsim/releases](https://github.com/cvut/qtrvsim/releases) - archives with Windows and generic GNU/Linux binaries - [https://build.opensuse.org/repositories/home:jdupak/qtrvsim](https://build.opensuse.org/repositories/home:jdupak/qtrvsim) - [https://software.opensuse.org/download.html?project=home%3Ajdupak&package=qtrvsim](https://software.opensuse.org/download.html?project=home%3Ajdupak&package=qtrvsim) - Open Build Service binary packages - [https://launchpad.net/~qtrvsimteam/+archive/ubuntu/ppa](https://launchpad.net/~qtrvsimteam/+archive/ubuntu/ppa) - Ubuntu PPA archive ```bash sudo add-apt-repository ppa:qtrvsimteam/ppa sudo apt-get update sudo apt-get install qtrvsim ``` ### Nix package QtRVSim provides a Nix package as a part of the repository. You can build and install it by a command bellow. Updates have to be done manually by checking out the git. NIXPKGS package is in PR phase. ```shell nix-env -if . ``` ### Tests Tests are managed by CTest (part of CMake). To build and run all tests, use this commands: ```bash cmake -DCMAKE_BUILD_TYPE=Release /path/to/QtRVSim make ctest ``` ## Documentation Main documentation is provided in this README and in subdirectories [`docs/user`](docs/user) and [`docs/developer`](docs/developer). The project was developed and extended as theses of Karel Kočí, Jakub Dupak and Max Hollmann. See section [Resources and Publications](#resources-and-publications) for links and references. ## Accepted Binary Formats The simulator accepts statically linked little-endian ELF executables compiled for the RISC-V target, either 32-bit (GCC options `-mabi=ilp32 -march=rv32i` to `rv32ima_zicsr`) or 64-bit (`-mabi=lp64 -march=rv64i` to `rv64ima_zicsr`). Simulation will execute them as XLEN=32 or XLEN=64 according to the ELF file header. There is even initial support for endianness selection based on the ELF file header. - 64-bit RISC-V ISA RV64IM and 32-bit RV32IM ELF executables are supported. - Compressed instructions are not yet supported. You can use compile the code for simulation using specialized RISC-V GCC/Binutils toolchain (`riscv32-elf`) or using unified Clang/LLVM toolchain with [LLD](https://lld.llvm.org/). If you have Clang installed, you don't need any additional tools. Clang can be used on Linux, Windows, macOS and others... ### LLVM toolchain usage ```shell clang --target=riscv32 -march=rv32g -nostdlib -static -fuse-ld=lld test.S -o test llvm-objdump -S test ``` ### GNU toolchain usage ```shell riscv32-elf-as test.S -o test.o riscv32-elf-ld test.o -o test riscv32-elf-objdump -S test ``` or ```shell riscv32-elf-gcc test.S -o test riscv32-elf-objdump -S test ``` ### GNU 64-bit toolchain use for RV32I target Multilib supporting 64-bit embedded toolchain can be used for to build executable ```shell riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib -o test test.c crt0local.S -lgcc ``` The global pointer and stack has to be set to setup runtime C code conformant environment. When no other C library is used then next simple `crt0local.S` can be used.
example code ```asm /* minimal replacement of crt0.o which is else provided by C library */ .globl main .globl _start .globl __start .option norelax .text __start: _start: .option push .option norelax la gp, __global_pointer$ .option pop la sp, __stack_end addi a0, zero, 0 addi a1, zero, 0 jal main quit: addi a0, zero, 0 addi a7, zero, 93 /* SYS_exit */ ecall loop: ebreak beq zero, zero, loop .bss __stack_start: .skip 4096 __stack_end: .end _start ```
## Integrated Assembler Basic integrated assembler is included in the simulator. Small subset of [GNU assembler](https://sourceware.org/binutils/docs/as/) directives is recognized as well. Next directives are recognized: `.word`, `.orig`, `.set` /`.equ`, `.ascii` and `.asciz`. Some other directives are simply ignored: `.data`, `.text`, `.globl`, `.end` and `.ent`. This allows to write code which can be compiled by both - integrated and full-featured assembler. Addresses are assigned to labels/symbols which are stored in symbol table. Addition, subtraction, multiplication, divide and bitwise and or are recognized. ## Support to call external make utility The action "Build executable by external make" call "make" program. If the action is invoked, and some source editors selected in main windows tabs then the "make" is started in the corresponding directory. Else directory of last selected editor is chosen. If no editor is open then directory of last loaded ELF executable are used as "make" start path. If even that is not an option then default directory when the emulator has been started is used. ## Advanced functionalities ### Peripherals
Emuated LCD, knobs, buttons, serial port, timer... The simulator implements emulation of two peripherals for now. The first is simple serial port (UART). It support transmission (Tx) and reception (Rx). Receiver status register (`SERP_RX_ST_REG`) implements two bits. Read-only bit 0 (`SERP_RX_ST_REG_READY`) is set to one if there is unread character available in the receiver data register (`SERP_RX_DATA_REG`). The bit 1 (`SERP_RX_ST_REG_IE`) can be written to 1 to enable interrupt request when unread character is available. The transmitter status register (`SERP_TX_ST_REG`) bit 0 (SERP_TX_ST_REG_READY) signals by value 1 that UART is ready and can accept next character to be sent. The bit 1 (`SERP_TX_ST_REG_IE`) enables generation of interrupt. The register `SERP_TX_DATA_REG` is actual Tx buffer. The LSB byte of written word is transmitted to the terminal window. Definition of peripheral base address and registers offsets (`_o`) and individual fields masks (`_m`) follows ``` #define SERIAL_PORT_BASE 0xffffc000 #define SERP_RX_ST_REG_o 0x00 #define SERP_RX_ST_REG_READY_m 0x1 #define SERP_RX_ST_REG_IE_m 0x2 #define SERP_RX_DATA_REG_o 0x04 #define SERP_TX_ST_REG_o 0x08 #define SERP_TX_ST_REG_READY_m 0x1 #define SERP_TX_ST_REG_IE_m 0x2 #define SERP_TX_DATA_REG_o 0x0c ``` The UART registers region is mirrored on the address 0xffff0000 to enable use of programs initially written for [SPIM](http://spimsimulator.sourceforge.net/) or [MARS](http://courses.missouristate.edu/KenVollmar/MARS/) emulators. The another peripheral allows to set three byte values concatenated to single word (read-only `KNOBS_8BIT` register) from user panel set by knobs and display one word in hexadecimal, decimal and binary format (`LED_LINE` register). There are two other words writable which control color of RGB LED 1 and 2 (registers `LED_RGB1` and `LED_RGB2`). ``` #define SPILED_REG_BASE 0xffffc100 #define SPILED_REG_LED_LINE_o 0x004 #define SPILED_REG_LED_RGB1_o 0x010 #define SPILED_REG_LED_RGB2_o 0x014 #define SPILED_REG_LED_KBDWR_DIRECT_o 0x018 #define SPILED_REG_KBDRD_KNOBS_DIRECT_o 0x020 #define SPILED_REG_KNOBS_8BIT_o 0x024 ``` The simple 16-bit per pixel (RGB565) framebuffer and LCD are implemented. The framebuffer is mapped into range starting at `LCD_FB_START` address. The display size is 480 x 320 pixel. Pixel format RGB565 expect red component in bits 11.. 15, green component in bits 5..10 and blue component in bits 0..4. ``` #define LCD_FB_START 0xffe00000 #define LCD_FB_END 0xffe4afff ``` The basic implementation of RISC-V Advanced Core Local Interruptor is implemented with basic support for - Machine-level Timer Device (MTIMER) - Machine-level Software Interrupt Device (MSWI) ``` #define ACLINT_MSWI 0xfffd0000 // core 0 machine SW interrupt request #define ACLINT_MTIMECMP 0xfffd4000 // core 0 compare value #define ACLINT_MTIME 0xfffdbff8 // timer base 10 MHz #define ACLINT_SSWI 0xfffd0000 // core 0 system SW interrupt request ``` More information about ACLINT can be found in [RISC-V Advanced Core Local Interruptor Specification](https://github.com/riscv/riscv-aclint/blob/main/riscv-aclint.adoc).
### Interrupts and Control and Status Registers
Implemented CSR registers and their usage List of interrupt sources: | Irq number | mie / mip Bit | Source | |-----------:|-----------------:|:---------------------------------------------| | 3 | 3 | Machine software interrupt request | | 7 | 7 | Machine timer interrupt | | 16 | 16 | There is received character ready to be read | | 17 | 17 | Serial port ready to accept character to Tx | Following Control Status registers are recognized | Number | Name | Description | |-------:|:-----------|:--------------------------------------------------------------------| | 0x300 | mstatus | Machine status register. | | 0x304 | mie | Machine interrupt-enable register. | | 0x305 | mtvec | Machine trap-handler base address. | | 0x340 | mscratch | Scratch register for machine trap handlers. | | 0x341 | mepc | Machine exception program counter. | | 0x342 | mcause | Machine trap cause. | | 0x343 | mtval | Machine bad address or instruction. | | 0x344 | mip | Machine interrupt pending. | | 0x34A | mtinsr | Machine trap instruction (transformed). | | 0x34B | mtval2 | Machine bad guest physical address. | | 0xB00 | mcycle | Machine cycle counter. | | 0xB02 | minstret | Machine instructions-retired counter. | | 0xF11 | mvendorid | Vendor ID. | | 0xF12 | marchid | Architecture ID. | | 0xF13 | mimpid | Implementation ID. | | 0xF14 | mhardid | Hardware thread ID. | `csrr`, `csrw`, `csrrs` , `csrrs` and `csrrw` are used to copy and exchange value from/to RISC-V control status registers. Sequence to enable serial port receive interrupt: Decide location of interrupt service routine the first. The address of the common trap handler is defined by `mtvec` register and then PC is set to this address when exception or interrupt is accepted. Enable bit 16 in the machine Interrupt-Enable register (`mie`). Ensure that bit 3 (`mstatus.mie` - machine global interrupt-enable) of Machine Status register is set to one. Enable interrupt in the receiver status register (bit 1 of `SERP_RX_ST_REG`). Write character to the terminal. It should be immediately consumed by the serial port receiver if interrupt is enabled in `SERP_RX_ST_REG`. CPU should report interrupt exception and when it propagates to the execution phase `PC` is set to the interrupt routine start address.
### System Calls Support
Syscall table and documentation The emulator includes support for a few Linux kernel system calls. The RV32G ilp32 ABI is used. | Register | use on input | use on output | Calling Convention | |:-----------------------------------|:----------------------|:----------------|:-------------------------------| | zero (x0) | — | - | Hard-wired zero | | ra (x1) | — | (preserved) | Return address | | sp (x2) | — | (callee saved) | Stack pointer | | gp (x3) | — | (preserved) | Global pointer | | tp (x4) | — | (preserved) | Thread pointer | | t0 .. t2 (x5 .. x7) | — | - | Temporaries | | s0/fp (x8) | — | (callee saved) | Saved register/frame pointer | | s1 (x9) | — | (callee saved) | Saved register | | a0 (x10) | 1st syscall argument | return value | Function argument/return value | | a1 (x11) | 2nd syscall argument | - | Function argument/return value | | a2 .. a5 (x12 .. x15) | syscall arguments | - | Function arguments | | a6 (x16) | - | - | Function arguments | | a7 (x17) | syscall number | - | Function arguments | | s2 .. s11 (x18 .. x27) | — | (callee saved) | Saved registers | | t3 .. t6 (x28 .. x31) | — | - | Temporaries | The all system call input arguments are passed in register. Supported syscalls: #### void [exit](http://man7.org/linux/man-pages/man2/exit.2.html)(int status) __NR_exit (93) Stop/end execution of the program. The argument is exit status code, zero means OK, other values informs about error. #### ssize_t [read](http://man7.org/linux/man-pages/man2/read.2.html)(int fd, void *buf, size_t count) __NR_read (63) Read `count` bytes from open file descriptor `fd`. The emulator maps file descriptors 0, 1 and 2 to the internal terminal/console emulator. They can be used without `open` call. If there are no more characters to read from the console, newline is appended. At most the count bytes read are stored to the memory location specified by `buf` argument. Actual number of read bytes is returned. #### ssize_t [write](http://man7.org/linux/man-pages/man2/write.2.html)(int fd, const void *buf, size_t count) __NR_write (64) Write `count` bytes from memory location `buf` to the open file descriptor `fd`. The same about console for file handles 0, 1 and 2 is valid as for `read`. #### int [close](http://man7.org/linux/man-pages/man2/close.2.html)(int fd) __NR_close (57) Close file associated to descriptor `fd` and release descriptor. #### int [openat](http://man7.org/linux/man-pages/man2/open.2.html)(int dirfd, const char *pathname, int flags, mode_t mode) __NR_openat (56) Open file and associate it with the first unused file descriptor number and return that number. If the option `OS Emulation`->`Filesystem root` is not empty then the file path `pathname` received from emulated environment is appended to the path specified by `Filesystem root`. The host filesystem is protected against attempt to traverse to random directory by use of `..` path elements. If the root is not specified then all open files are targetted to the emulated terminal. Only `TARGET_AT_FDCWD` (`dirfd` = -100) mode is supported. #### void * [brk](http://man7.org/linux/man-pages/man2/brk.2.html)(void *addr) __NR_brk (214) Set end of the area used by standard heap after end of the program data/bss. The syscall is emulated by dummy implementation. Whole address space up to 0xffff0000 is backuped by automatically attached RAM. #### int [ftruncate](http://man7.org/linux/man-pages/man2/ftruncate.2.html)(int fd, off_t length) __NR_truncate (46) Set length of the open file specified by `fd` to the new `length`. The `length` argument is 64-bit even on 32-bit system and it is passed as the lower part and the higher part in the second and third argument. #### ssize_t [readv](http://man7.org/linux/man-pages/man2/readv.2.html)(int fd, const struct iovec *iov, int iovcnt) __NR_Linux (65) The variant of `read` system call where data to read are would be stored to locations specified by `iovcnt` pairs of base address, length pairs stored in memory at address pass in `iov`. #### ssize_t [writev](http://man7.org/linux/man-pages/man2/writev.2.html)(int fd, const struct iovec *iov, int iovcnt) __NR_Linux (66) The variant of `write` system call where data to write are defined by `iovcnt` pairs of base address, length pairs stored in memory at address pass in `iov`.
## Limitations of the Implementation - See list of currently supported instructions. ### QtRvSim limitations * Only very minimal support for privileged instruction is implemented for now (mret). * Only machine mode and minimal subset of machine CSRs is implemented. * TLB and virtual memory are not implemented. * No floating point support * Memory access stall (stalling execution because of cache miss would be pretty annoying for users so difference between cache and memory is just in collected statistics) * Only limited support for interrupts and exceptions. When `ecall` instruction is recognized, small subset of the Linux kernel system calls can be emulated or simulator can be configured to continue by trap handler on `mtvec` address. ### List of Currently Supported Instructions - **RV32I**: - **LOAD**: `lw, lh, lb, lwu, lhu, lbu` - **STORE**: `sw, sh, sb, swu, shu, sbu` - **OP**: `add, sub, sll, slt, sltu, xor, srl, sra, or, and` - **MISC-MEM**: `fence, fence.i` - **OP-IMM**: `addi, sll, slti, sltiu, xori, srli, srai, ori, andi, auipc, lui` - **BRANCH**: `beq, bne, btl, bge, bltu, bgtu` - **JUMP**: `jal, jalr` - **SYSTEM**: `ecall, mret, ebreak, csrrw, csrrs, csrrc, csrrwi, csrrsi, csrrci` - **RV64I**: - **LOAD/STORE**: `lwu, ld, sd` - **OP-32**: `addw, subw, sllw, srlw, sraw, or, and` - **OP-IMM-32**: `addiw, sllw, srliw, sraiw` - **Pseudoinstructions** - **BASIC**: `nop` - **LOAD**: `la, li`, - **OP**: `mv, not, neg, negw, sext.b, sext.h, sext.w, zext.b, zext.h, zext.w, seqz, snez, sltz, slgz` - **BRANCH**: `beqz, bnez, blez, bgez, bltz, bgtz, bgt, ble, bgtu, bleu` - **JUMP**: `j, jal, jr, jalr, ret, call, tail` - **Extensions** - **RV32M/RV64M**: `mul, mulh, mulhsu, div, divu, rem, remu` - **RV64M**: `mulw, divw, divuw, remw, remuw` - **RV32A/RV64A**: `lr.w, sc.w, amoswap.w, amoadd.w, amoxor.w, amoand.w, amoor.w, amomin.w, amomax.w, amominu.w, amomaxu.w` - **RV64A**: `lr.d, sc.d, amoswap.d, amoadd.d, amoxor.d, amoand.d, amoor.d, amomin.d, amomax.d, amominu.d, amomaxu.d` - **Zicsr**: `csrrw, csrrs, csrrc, csrrwi, csrrsi, csrrci` For details about RISC-V, refer to the ISA specification: [https://riscv.org/technical/specifications/](https://riscv.org/technical/specifications/). ## Links to Resources and Similar Projects ### Resources and Publications - Computer architectures pages at Czech Technical University in Prague [https://comparch.edu.cvut.cz/](https://comparch.edu.cvut.cz/) - Dupak, J.; Pisa, P.; Stepanovsky, M.; Koci, K. [QtRVSim – RISC-V Simulator for Computer Architectures Classes](https://comparch.edu.cvut.cz/publications/ewC2022-Dupak-Pisa-Stepanovsky-QtRvSim.pdf) In: [embedded world Conference 2022](https://events.weka-fachmedien.de/embedded-world-conference). Haar: WEKA FACHMEDIEN GmbH, 2022. p. 775-778. ISBN 978-3-645-50194-1. ([Slides](https://comparch.edu.cvut.cz/slides/ewc22-qtrvsim.pdf)) Please reference above article, if you use QtRvSim in education or research related materials and publications. - [FEE CTU - B35APO - Computer Architectures](https://cw.fel.cvut.cz/wiki/courses/b35apo) - Undergraduate computer architecture class materials ( Czech) ([English](https://cw.fel.cvut.cz/wiki/courses/b35apo/en/start)) - [FEE CTU - B4M35PAP - Advanced Computer Architectures](https://cw.fel.cvut.cz/wiki/courses/b4m35pap/start) - Graduate computer architecture class materials (Czech/English) - [Graphical RISC-V Architecture Simulator - Memory Model and Project Management](https://dspace.cvut.cz/bitstream/handle/10467/94446/F3-BP-2021-Dupak-Jakub-thesis.pdf) - Jakub Dupak's thesis - Documents 2020-2021 QtMips and QtRvSim development - [Graphical CPU Simulator with Cache Visualization](https://dspace.cvut.cz/bitstream/handle/10467/76764/F3-DP-2018-Koci-Karel-diploma.pdf) - Karel Koci's thesis - Documents initial QtMips development ### Projects - **QtMips** - MIPS predecessor of this simulator [https://github.com/cvut/QtMips/](https://github.com/cvut/QtMips/) - **RARS** - RISC-V Assembler and Runtime Simulator [https://github.com/TheThirdOne/rars](https://github.com/TheThirdOne/rars) ## Copyright - Copyright (c) 2017-2019 Karel Koci - Copyright (c) 2019-2025 Pavel Pisa - Copyright (c) 2020-2025 Jakub Dupak - Copyright (c) 2020-2021 Max Hollmann ## License This project is licensed under `GPL-3.0-or-later`. The full text of the license is in the [LICENSE](LICENSE) file. The license applies to all files except for directories named `external` and files in them. Files in external directories have a separate license compatible with the projects license. > This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. > > This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. > > You should have received a copy of the GNU General Public License along with this program. If not, see [https://www.gnu.org/licenses/](https://www.gnu.org/licenses/). ![Faculty of Electrical Engineering](./data/ctu/logo-fee.svg) ![Faculty of Information Technology](./data/ctu/logo-fit.svg) ![Czech Technical University](./data/ctu/logo-ctu.svg) ================================================ FILE: cmake/AddFileHashes.cmake ================================================ # Add file hashes to placeholder location in a template file. # Used for packaging. # # Example: # cmake -DTEMPLATE=PKGBUILD.in -DOUTPUT=PKGBUILD -DFILE=qtrvsim_0.8.0.tar.xz -P AddFileHashes.cmake # # See extras/packaging/arch/PKGBUILD.in for template examples. # Note that most files are configured (injected with information) twice: # First, during configure phase, package information is added and FILE_* # placeholders are left intact. Second, after source packing, FILE_* # placeholders are resolved. file(MD5 ${FILE} FILE_MD5) file(SHA1 ${FILE} FILE_SHA1) file(SHA256 ${FILE} FILE_SHA256) file(SIZE ${FILE} FILE_SIZE) configure_file(${TEMPLATE} ${OUTPUT}) ================================================ FILE: cmake/BuildType.cmake ================================================ # Source (BSD-3) https://github.com/openchemistry/tomviz/blob/master/cmake/BuildType.cmake # Set a default build type if none was specified set(default_build_type "Release") if(NOT CMAKE_BUILD_TYPE AND NOT CMAKE_CONFIGURATION_TYPES) message(STATUS "Setting build type to '${default_build_type}' as none was specified.") set(CMAKE_BUILD_TYPE "${default_build_type}" CACHE STRING "Choose the type of build." FORCE) # Set the possible values of build type for cmake-gui set_property(CACHE CMAKE_BUILD_TYPE PROPERTY STRINGS "Debug" "Release" "MinSizeRel" "RelWithDebInfo") endif() # Set variable to detect given build type if(CMAKE_BUILD_TYPE) string(TOUPPER "${CMAKE_BUILD_TYPE}" _upper_build_type) set(BUILD_${_upper_build_type} 1) endif() ================================================ FILE: cmake/BundleMacOS.cmake ================================================ find_program(MACDEPLOYQT NAMES macdeployqt) ================================================ FILE: cmake/CopyrightTools.cmake ================================================ function(copyright_to_html OUTPUT INPUT) string(REPLACE ";" "
" TMP1 "${INPUT}") string(REGEX REPLACE "<([^<>]+@[^<>]+)>" "\\1" TMP2 ${TMP1}) set(${OUTPUT} ${TMP2} PARENT_SCOPE) endfunction() function(copyright_to_comment OUTPUT INPUT) string(REGEX REPLACE "([^;]+)" "# \\1" ${OUTPUT} "${INPUT}") string(REPLACE ";" "\n" ${OUTPUT} "${${OUTPUT}}") set(${OUTPUT} ${${OUTPUT}} PARENT_SCOPE) endfunction() macro(copyright) set(COPYRIGHT_LIST "${ARGN}") copyright_to_html(COPYRIGHT_HTML "${COPYRIGHT_LIST}") copyright_to_comment(COPYRIGHT_HASH_COMMENT "${COPYRIGHT_LIST}") endmacro() ================================================ FILE: cmake/FindElfUtils.cmake ================================================ # Source (GPLv2): https://github.com/SimonKagstrom/kcov/blob/master/cmake/FindElfutils.cmake # - Try to find libdwarf # Once done this will define # # LIBDWARF_FOUND - system has libdwarf # LIBDWARF_INCLUDE_DIRS - the libdwarf include directory # LIBDWARF_LIBRARIES - Link these to use libdwarf # LIBDWARF_DEFINITIONS - Compiler switches required for using libdwarf # # Locate libelf library at first if (NOT LIBELF_FOUND) find_package(LibElf) endif (NOT LIBELF_FOUND) if (LIBDWARF_LIBRARIES AND LIBDWARF_INCLUDE_DIRS) set(LibDwarf_FIND_QUIETLY ON) endif (LIBDWARF_LIBRARIES AND LIBDWARF_INCLUDE_DIRS) find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH ON) pkg_check_modules(PC_LIBDW QUIET libdw) endif () find_path(DWARF_INCLUDE_DIR NAMES dwarf.h HINTS ${PC_LIBDW_INCLUDE_DIRS} PATHS /usr/include /usr/local/include /opt/local/include /sw/include ENV CPATH) # PATH and INCLUDE will also work find_path(LIBDW_INCLUDE_DIR NAMES elfutils/libdw.h HINTS ${PC_LIBDW_INCLUDE_DIRS} PATHS /usr/include /usr/local/include /opt/local/include /sw/include ENV CPATH) if (DWARF_INCLUDE_DIR AND LIBDW_INCLUDE_DIR) set(LIBDWARF_INCLUDE_DIRS ${DWARF_INCLUDE_DIR} ${LIBDW_INCLUDE_DIR}) endif (DWARF_INCLUDE_DIR AND LIBDW_INCLUDE_DIR) find_library(LIBDW_LIBRARY NAMES dw HINTS ${PC_LIBDW_LIBRARY_DIRS} PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib ENV LIBRARY_PATH # PATH and LIB will also work ENV LD_LIBRARY_PATH) include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBDWARF_FOUND to TRUE # if all listed variables are TRUE find_package_handle_standard_args(ElfUtils DEFAULT_MSG LIBDW_LIBRARY LIBDW_INCLUDE_DIR) mark_as_advanced(LIBDW_INCLUDE_DIR LIBDW_LIBRARY) set(LIBDW_LIBRARIES ${LIBDW_LIBRARY}) set(LIBDW_INCLUDE_DIRS ${LIBDW_INCLUDE_DIR}) ================================================ FILE: cmake/FindLibElf.cmake ================================================ # Source (GPLv2): https://github.com/SimonKagstrom/kcov/blob/master/cmake/FindLibElf.cmake # - Try to find libelf # Once done this will define # # LIBELF_FOUND - system has libelf # LIBELF_INCLUDE_DIRS - the libelf include directory # LIBELF_LIBRARIES - Link these to use libelf # LIBELF_DEFINITIONS - Compiler switches required for using libelf # # Copyright (c) 2008 Bernhard Walle # # Redistribution and use is allowed according to the terms of the New # BSD license. # For details see the accompanying COPYING-CMAKE-SCRIPTS file. # if (LIBELF_LIBRARIES AND LIBELF_INCLUDE_DIRS) set(LibElf_FIND_QUIETLY ON) endif (LIBELF_LIBRARIES AND LIBELF_INCLUDE_DIRS) find_package(PkgConfig QUIET) if (PKG_CONFIG_FOUND) set(PKG_CONFIG_USE_CMAKE_PREFIX_PATH ON) pkg_check_modules(PC_LIBELF QUIET libelf) endif () find_path(LIBELF_INCLUDE_DIR NAMES libelf.h HINTS ${PC_LIBELF_INCLUDE_DIRS} PATHS /usr/include /usr/include/libelf /usr/local/include /usr/local/include/libelf /opt/local/include /opt/local/include/libelf /sw/include /sw/include/libelf ENV CPATH) find_library(LIBELF_LIBRARY NAMES elf HINTS ${PC_LIBELF_LIBRARY_DIRS} PATHS /usr/lib /usr/local/lib /opt/local/lib /sw/lib ENV LIBRARY_PATH ENV LD_LIBRARY_PATH) include(FindPackageHandleStandardArgs) # handle the QUIETLY and REQUIRED arguments and set LIBELF_FOUND to TRUE if all listed variables are TRUE find_package_handle_standard_args(LibElf DEFAULT_MSG LIBELF_LIBRARY LIBELF_INCLUDE_DIR) mark_as_advanced(LIBELF_INCLUDE_DIR LIBELF_LIBRARY) set(LIBELF_LIBRARIES ${LIBELF_LIBRARY}) set(LIBELF_INCLUDE_DIRS ${LIBELF_INCLUDE_DIR}) ================================================ FILE: cmake/FindPythonInterp.cmake ================================================ # Compatibility wrapper for CMake > 3.12 where FindPythonInterp is deprecated/removed # This allows external projects using the old module to work with newer CMake and Python3 find_package(Python3 COMPONENTS Interpreter QUIET) if(Python3_Interpreter_FOUND) set(PYTHONINTERP_FOUND TRUE) set(PYTHON_EXECUTABLE ${Python3_EXECUTABLE}) set(PYTHON_VERSION_STRING ${Python3_VERSION}) set(PYTHON_VERSION_MAJOR ${Python3_VERSION_MAJOR}) set(PYTHON_VERSION_MINOR ${Python3_VERSION_MINOR}) set(PYTHON_VERSION_PATCH ${Python3_VERSION_PATCH}) if(NOT PythonInterp_FIND_QUIETLY) message(STATUS "Found PythonInterp (via Python3): ${PYTHON_EXECUTABLE} (found version \"${PYTHON_VERSION_STRING}\")") endif() else() if(PythonInterp_FIND_REQUIRED) message(FATAL_ERROR "Could NOT find PythonInterp (missing: PYTHON_EXECUTABLE)") endif() endif() ================================================ FILE: cmake/GPL-3.0-or-later.cmake ================================================ # Licence related data to be included in project files set(LICENCE_IDENTIFIER "GPL-3.0-or-later") set(LICENCE_SHORT "\ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see .") set(LICENCE_SHORT_HTML "\

This program is free software: you can redistribute it and/or modify it under the terms of the \ GNU General Public License as published by the Free Software Foundation, either version 3 of the \ License, or (at your option) any later version.

\

This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; \ without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the \ GNU General Public License for more details.

\

You should have received a copy of the GNU General Public License along with this program. \ If not, see https://www.gnu.org/licenses/.

") ================================================ FILE: cmake/LibElfinSettings.cmake ================================================ if(POLICY CMP0148) cmake_policy(SET CMP0148 OLD) endif() # Libelfin requires Threads for dwarf++ library find_package(Threads REQUIRED) ================================================ FILE: cmake/PackageTools.cmake ================================================ # Support code for package handling. # Mainly intended to generate inputs for Open Build Service. # Keep file properties placeholder in configure phase, they are replaced in build phase set(FILE_NAME "\@FILE_NAME\@") set(FILE_SIZE "\@FILE_SIZE\@") set(FILE_MD5 "\@FILE_MD5\@") set(FILE_SHA1 "\@FILE_SHA1\@") set(FILE_SHA256 "\@FILE_SHA256\@") set(FILE2_NAME "\@FILE2_NAME\@") set(FILE2_SIZE "\@FILE2_SIZE\@") set(FILE2_MD5 "\@FILE2_MD5\@") set(FILE2_SHA1 "\@FILE2_SHA1\@") set(FILE2_SHA256 "\@FILE2_SHA256\@") # Source file tar generation. # Make sure that destination path exists as it wont be created automatically. file(MAKE_DIRECTORY ${PACKAGE_OUTPUT_PATH}) find_program(BASH bash REQUIRED) find_program(GIT git REQUIRED) find_program(XZ xz REQUIRED) find_program(TAR tar REQUIRED) # Command to build source archive from git HEAD. add_custom_command(OUTPUT ${PACKAGE_SOURCE_ARCHIVE_FILE} COMMAND ${BASH} ${CMAKE_SOURCE_DIR}/extras/packaging/_tools/git-archive-submodules.sh ${CMAKE_SOURCE_DIR} ${PACKAGE_OUTPUT_PATH} ${PACKAGE_SOURCE_ARCHIVE_FILE} ${PACKAGE_NAME} ${PACKAGE_VERSION} ${GIT} ${TAR} ${XZ} WORKING_DIRECTORY "${CMAKE_SOURCE_DIR}") add_custom_target(source_archive DEPENDS ${PACKAGE_SOURCE_ARCHIVE_FILE}) # Procedure adding support for individual OS distributions. # # @param target_name name of created target for simple reference (recommended: OS name) # @param config_file_name name of resulting config file # @param template path to cmake template file (relative from source dir) # # NOTE: ${PACKAGE_SOURCE_ARCHIVE_FILE} is assumed to exist in scope and be unique for project # macro(package_config_file target_name config_file_name template) # The @ONLY option disable replacement of ${} which may be used by shell as well. configure_file(${template} ${config_file_name}.in @ONLY) add_custom_command(OUTPUT ${config_file_name} COMMAND ${CMAKE_COMMAND} -DFILE="${PACKAGE_SOURCE_ARCHIVE_PATH}" -DTEMPLATE="${CMAKE_BINARY_DIR}/${config_file_name}.in" -DOUTPUT=${PACKAGE_OUTPUT_PATH}/${config_file_name} -P "${CMAKE_SOURCE_DIR}/cmake/AddFileHashes.cmake" DEPENDS source_archive) add_custom_target(${target_name} DEPENDS ${config_file_name}) endmacro() macro(package_debian_quilt target_name config_file_name template debian_dir output_archive) file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/debian) # The @ONLY option disable replacement of ${} which may be used by shell as well. configure_file(${debian_dir}/control.in ${CMAKE_BINARY_DIR}/debian/control @ONLY) file(COPY ${CMAKE_BINARY_DIR}/debian/control DESTINATION ${CMAKE_BINARY_DIR}/debian FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) file(COPY ${debian_dir}/compat DESTINATION ${CMAKE_BINARY_DIR}/debian FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) file(COPY ${debian_dir}/rules DESTINATION ${CMAKE_BINARY_DIR}/debian FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ OWNER_EXECUTE GROUP_EXECUTE GROUP_EXECUTE) file(COPY ${debian_dir}/docs DESTINATION ${CMAKE_BINARY_DIR}/debian FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) file(COPY ${debian_dir}/changelog DESTINATION ${CMAKE_BINARY_DIR}/debian FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) file(COPY ${debian_dir}/source/format DESTINATION ${CMAKE_BINARY_DIR}/debian/source FILE_PERMISSIONS OWNER_READ GROUP_READ WORLD_READ) file(ARCHIVE_CREATE OUTPUT ${PACKAGE_OUTPUT_PATH}/${output_archive} PATHS ${CMAKE_BINARY_DIR}/debian FORMAT gnutar COMPRESSION XZ) set(DEBIAN_ARCHIVE_FILE ${output_archive}) file(MD5 ${PACKAGE_OUTPUT_PATH}/${output_archive} DEBIAN_MD5) file(SHA1 ${PACKAGE_OUTPUT_PATH}/${output_archive} DEBIAN_SHA1) file(SHA256 ${PACKAGE_OUTPUT_PATH}/${output_archive} DEBIAN_SHA256) file(SIZE ${PACKAGE_OUTPUT_PATH}/${output_archive} DEBIAN_SIZE) configure_file(${template} ${config_file_name}.in @ONLY) add_custom_command(OUTPUT ${config_file_name} COMMAND ${CMAKE_COMMAND} -DFILE="${PACKAGE_SOURCE_ARCHIVE_PATH}" -DTEMPLATE="${CMAKE_BINARY_DIR}/${config_file_name}.in" -DOUTPUT=${PACKAGE_OUTPUT_PATH}/${config_file_name} -P "${CMAKE_SOURCE_DIR}/cmake/AddFileHashes.cmake" DEPENDS source_archive) add_custom_target(${target_name} DEPENDS ${config_file_name}) message(STATUS "Debian archive created") endmacro() ================================================ FILE: cmake/TestingTools.cmake ================================================ # Helper functions for integration testing # Creates a new CLI test. The test consists of two parts: the first runs the command and checks nonzero return value, # the second compares the stdout with provided files. Currently there diff is not displayed as cmake does not provide a # portable way to do that. # # TODO: show diff whenever available. # # NOTE: # If CLI was build in debug mode (which is recommended) the test will run with sanitizers and any memory errors # including memory leaks will fail the test. # # Usage: # add_cli_test( # NAME # ARGS # --asm "${CMAKE_SOURCE_DIR}/tests/cli//program.S" # # EXPECTED_OUTPUT "tests/cli//stdout.txt" # ) function(add_cli_test) cmake_parse_arguments( CLI_TEST "" "NAME;EXPECTED_OUTPUT" "ARGS" ${ARGN} ) add_custom_target( cli_test_${CLI_TEST_NAME} COMMAND ${CMAKE_COMMAND} -E make_directory "Testing" COMMAND cli ${CLI_TEST_ARGS} > "Testing/stall_test.out" COMMAND ${CMAKE_COMMAND} -E compare_files "Testing/stall_test.out" "${CMAKE_SOURCE_DIR}/${CLI_TEST_EXPECTED_OUTPUT}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}" DEPENDS cli ) add_test( NAME "cli_${CLI_TEST_NAME}" COMMAND ${CMAKE_COMMAND} --build . --target "cli_test_${CLI_TEST_NAME}" WORKING_DIRECTORY "${CMAKE_BINARY_DIR}") endfunction() ================================================ FILE: data/cz.cvut.edu.comparch.qtrvsim.metainfo.xml.in ================================================ @MAIN_PROJECT_APPID@ @GENERIC_NAME@ Czech Technical University in Prague RISC-V CPU simulator for education purposes CC0-1.0 GPL-3.0-or-later @MAIN_PROJECT_NAME_LOWER@.desktop

RISC-V processor architecture simulator for education purposes with pipeline and cache visualization.

Current project release.

GUI fix crash when no tab is selected, use bundled LibElf on macOS as Homebrew misses RISC-V

RISC-V A extension, ACLINT MTIMER, IRQ support, L2 cache, GUI editor tabs

More CSR updates, fix branch range, CLI OS emulation, coreview diagrams updates, RV64IM in CI

Support for 64-bit RV64IM target and official tests in CI

Corrected peripherals, LCD endianness and memory stalls.

Initial Flatpak release.

https://raw.githubusercontent.com/cvut/qtrvsim/master/data/screenshots/0.png https://raw.githubusercontent.com/cvut/qtrvsim/master/data/screenshots/1.png https://github.com/cvut/qtrvsim https://github.com/cvut/qtrvsim/issues 768 keyboard pointing ModernToolkit david_AT_ixit.cz workstation
================================================ FILE: data/gui.desktop.in ================================================ [Desktop Entry] Name=@PROJECT_NAME@ GenericName=@GENERIC_NAME@ Exec=@MAIN_PROJECT_NAME_LOWER@_gui Icon=@MAIN_PROJECT_NAME_LOWER@_gui Type=Application Comment=@GENERIC_NAME@ Terminal=false Categories=System;Emulator; Keywords=emulator;RISC-V;education; ================================================ FILE: data/wasm/browserconfig.xml ================================================ #da532c ================================================ FILE: data/wasm/index.html ================================================ QtRVSim: RISV-V simulator for education
Qt Qt for WebAssembly: qtrvsim_gui
================================================ FILE: data/wasm/manifest.json ================================================ { "name": "QtRVSim: RISC-V simulator for education", "short_name": "QtRVSim", "icons": [ { "src": "./android-chrome-192x192.png", "sizes": "192x192", "type": "image/png" }, { "src": "./android-chrome-512x512.png", "sizes": "512x512", "type": "image/png" } ], "theme_color": "#ffffff", "background_color": "#ffffff", "display": "standalone", "start_url": "./index.hml" } ================================================ FILE: default.nix ================================================ let pkgs = import { }; in { qtrvsim = pkgs.libsForQt5.callPackage (import extras/packaging/nix/qtrvsim.nix) {}; } ================================================ FILE: docs/developer/build&deploy/building-wasm.md ================================================ # Building and deploying WebAssembly release of the simulator ## Jump start WASM release is automatically build by the GitHub actions. Resulting files are available in `Actions` /``, section `Artifacts`, file `target-wasm-Linux-qtx.xx.x`. Download the files and skip to deploy. Details on the build process can be found in `.github/workflows/cmake.yml`. ## Jump start 2 Install `emsdk` and `aqt` (details below). Use prepared helper make in the root of the project. ```shell make wasm # build make wasm-clean # clean wasm build make wasm-clean-deap # also clean cached external libraries - when changing compile settings, of when it just does not work ;) make wasm-install-qt # install appropriate qt for wasm using aqt ``` Behavior of this commands is customized to local system in `.dev-config.local.mk`. The file is provided as a template and further changes are ignored. ## Dependencies - WASM compiler (Emscripten/EMSDK) - WASM compiled Qt - bash compatible shell (not fish, unfortunately) Steps: - Install `emsdk` toolkit. Installing just the Emscripten compiler might work, but I would discourage it. - Choose version of the toolkit and run `emsdk activate `. You can choose freely as long as all your components are compiled with the compatible version. Qt5 distributed release is build with version `1.39.8`. Some distributions provide packages like `qt5-wasm`. It is imperative to compile the project with the same toolchain, otherwise it might not link. - Source the toolkit as suggested by the output of the activate command. - Download or compile qt to wasm. Common way is to store it in `/opt/`. I use an unofficial qt downloader [`aqtinstall`](https://pypi.org/project/aqtinstall/). - `python -m aqt install 5.12.2 linux desktop wasm_32 --outputdir "/opt/qt"` ## Build - Move to build directory a build directory (I use`build/wasm`) and run (replace the paths accordingly and use your favorite buildsystem): ```shell emcmake cmake ../.. -DCMAKE_BUILD_TYPE=Release -DCMAKE_PREFIX_PATH=/opt/qt/5.13.1/wasm_32/ -DCMAKE_FIND_ROOT_PATH=/opt/qt/5.13.1/wasm_32/ -Wno-dev -G Ninja ``` - Move the resulting `.js` and `.wasm` files to `target/wasm` along with all content of `data/wasm`. It contains loader script, icons and other support files. ## Deploy - Move the contents of `target/wasm` to a webserver. Done. - Note: the webserver must use https due to CORS. ## Tricks ### Fish and co. If you wish to use bash incompatible shell. Run bash, source the file, and the run the shell with the environment of the bash. ================================================ FILE: docs/developer/build&deploy/release.md ================================================ # Release process - Check version in root `CMakeLists.txt` - Update debian changelog - Make sure, CMake was configured with flag `-DDEV_MODE=true` to enable packaging tools. Without this flag, CMake will not try to do anything, that would require an external tool. - Run `make open_build_service_bundle`. - Deploy the contents of `/target/pkg` to Open Build Service (and Launchpad). ## Debian changelog Debian changelog has very strict (and little weird) format. Bash script `extras/packaging/add-to-changelog.sh ` generates a new entry in the changelog and opens it in `$EDITOR`. Edit CAREFULLY, the format is fragile. After that, changes are committed and git tag is updated. ### TODO - Decide, whether this should be replaced with CMake. - Get version from CMake ## `open_build_service_bundle` target At configure time, package related files are injected with up to date information from CMake (variables prefixed `PACKAGE_`) and copied to ``/`pkg`. This target bundles sources to `tar.xz` and updates hashes in the package files. ### Generate using GitHub Actions (CI) In the repository page at GitHub go to actions tab and choose release in the left menu. ![](./media/obs-ci-step-1.png) Click `run workflow` and select a branch. ![](./media/obs-ci-step-2.png) One the workflow has finished, the bundle is to be found in the artifacts section. Upload this bundle to OBS. ![](./media/obs-ci-step-3.png) ### TODO - Package signing ## Open Build Service The easiest way to deploy files to OBS is the `osc` cli tool. - Setup credentials. The first run of `osc` will lead you. - Download the repository: ```shell osc co home:jdupak qtrvsim ``` - Copy files to `home:jdupak`/`qtrvsim`. - Add files to tracking (in the `qtrvsim` directory): ```shell osc addremove * ``` - Commit changes and push: ```shell osc commit ``` - If something went wrong, delete th directory and start from th beginning. ================================================ FILE: docs/developer/coreview-graphics/using-drawio-diagram.md ================================================ # Using .drawio diagram For easier manipulation the coreview diagrams are created i a diagramming tool [diagrams.net](https://app.diagrams.net), formerly known as __draw.io__. It is fully sufficient to use it in browser, but installable version is available as well. ## Prepare your workspace Before you begin and open the diagram, you have to enable some plugins in menu `Extra`/`Plugins...`. Tou will need `tags`. Choose it and reload the app. You will need to enable the tools `Tabs` and `Hidden Tabs` in the `Extras` menu. I also recommend enabling `Layers` tool in view. ## Export You have to export 3 SVG files. In the `Tags` window, empty the field and click `Hide`. Everything will disappear. Then type `simple` and click show. It will show only components tagged as simple. No hit `File`/`Export as...`/`SVG...`. Uncheck the `include a copy of diagram` option and git export. Save as `simple.svg` to `device` and move it to project directory (`src/gui/core`). Hit hide and repeat for other versions ( currently `simple`,`pipeline` and `forwarding`). ## Editing You can load the diagram from device or from GitHub. Device is preferred as it will not create many useless commits. All shapes I have used are on [my GitHub](https://github.com/jdupak/Diagrams.net-CPU-scheme-kit). You can open it in `File` /`Open library from`. If you need write access just open an issue. The editor itself is quite intuitive, so here are only special functions and highlights: - When using texts, make sure, that formatted text option is disabled. It would use SVG foreign objects, which are not supported in most simple renderers and parsers. The option is cca in the middle of the text tab on the right. - If you need to ensure that some elements are inside others in the resulting SVG, you have to use `Custom element`, otherwise the SVG is flattened. Currently, it is how cache works. In the style tab, you can see inspect it by the ` edit shape` button. Make sure to select the element and not some group. You can create new one in `Arrange`/`Insert`/`Shape...`. The language is similar to SVG but not the same, and it is documented here: [https://www.diagrams.net/doc/faq/shape-complex-create-edit](https://www. diagrams.net/doc/faq/shape-complex-create-edit) with complete language reference here: [https://jgraph.github. io/mxgraph/docs/stencils.xsd](https://jgraph.github.io/mxgraph/docs/stencils.xsd). This is the code of the cache: ```xml ``` - Components with dynamic values are created in `Edit data` in context menu (`CTRL+M`). Example (written here as a json): ```json { "component": "instruction-value", "source": "decode" } ``` Components are handled in `src/gui/coreview/scene.cpp`, defined in `src/gui/coreview/domponents` and data bindings (source attribute) are controlled in `src/gui/coreview/data.h`. ![](./media/data.png). - In context menu - `Edit link...` you can create hyperlinks, what will open some part of gui on doubleclick. The connect to `CoreViewScene` signals using table in `src/gui/coreview/data.h`. - To control in which variant the given element appears, you can use `tags` in `Edit data` of gui tool, which can be displayed in `Extras`/`Hidden tags...` ![](./media/tags.png). ================================================ FILE: docs/developer/debuging/logging.md ================================================ # Logging The project uses the Qt logging framework. For quick introduction see [this KDAB presentation](https://www.kdab.com/wp-content/uploads/stories/slides/Day2/KaiKoehne_Qt%20Logging%20Framework%2016_9_0.pdf) . To enable logging in a `cpp` file, add the following snippet. ```c++ #include "common/logging.h" LOG_CATEGORY("."); ``` This creates a category object in the scope and enables seamless use of logging macros `DEBUG` `LOG` `WARN` and `INFO`. If you need to use logging in a header file, call the `LOG_CATEGORY` macro only in a local scope to avoid interfering with log categories in source files. Debug logs are intended to provide fine level of information and should be hidden by default and enabled only for currently debugged category. Debug logs are removed from all `release` builds. ## Using the log Qt supports both C++ streams and `printf`-like interface. **In this project, only `printf` style logs should be used.** Logs are considered to be part of the documentation and stream are too hard to read. The only exception is compatibility with used libraries (curretly *svgscene*). To print any Qt type, just wrap it with `qPrintable` macro. **Example:** ``` QT_LOGGING_CONF=/data/Dev/school/QtRvSim/qtlogging.ini; ``` ```cpp DEBUG("Link triggered (href: %s).", qPrintable(getTargetName())); ``` ## Configuring the log To filter shown logs, modify `qtlogging.ini` file in the root of the project. If this config is not found automatically, use `QT_LOGGING_CONF`environment variable. **Example:** ```shell QT_LOGGING_CONF=/data/Dev/school/QtRvSim/qtlogging.ini ``` **Configuration example** (shows debug logs from `instruction.cpp` only): ```ini [Rules] *.debug=false machine.Instruction.debug=true ``` ================================================ FILE: docs/developer/debuging/sanitizers.md ================================================ # Runtime sanitizers ## Using sanitizers in this project All debug builds are by default built with address and undefined runtime sanitizers. To disable them (which is strongly discouraged) run cmake with options none. ```shell cmake -DSANITIZERS=none ``` To run other selection of sanitiser use, pass colon separated list to the command. ```shell cmake -DSANITIZERS=memory,undefined ``` NOTE: Some sanitizer cannot be used together, like address and memory. ### Sanitizer debug info and Clang If you are using sanitizers with clang and you don't files and line numbers, make sure that `llvm-symbolizer` is installed and if that does not help, add env `ASAN_SYMBOLIZER_PATH=` with path to your symbolizer executable. Most of the time, having it in `PATH` should be enough. ================================================ FILE: docs/developer/need-to-know.md ================================================ # Need To Know **Critical information for every developer.** ## Modal Windows, Dialogues, Message Boxes In typical, native, Qt setting, modal windows allocated on stack and block upon opening. To manage the modal window, Qt spins up a second event loop. However, that is not technically possible in Web-Assembly environment. *(This might be solved in new Qt with “asyncify” build flag, but it does not work with currently used Qt 5.15.2)* [Read more](http://qtandeverything.blogspot.com/2019/05/exec-on-qt-webassembly.html) ### Broken API usage ```cpp QMessageBox msg; msg.exec(); ``` ```cpp QMessageBox::critical("Hello"); ``` The `.exec()` method and single line call are typical warning signs. Some parts of code employ special code for Web-Assembly (like HTML file selector) and the native part may use blocking solution. However, in performance non-critical parts (which most dialogs are), unified asynchronous solution is preferred. ### Solution The modal window object has to be allocated dynamically and started using asynchronous method `open`. Answer has to be obtained via `connect` callback. For ease of use, wrapper functions are prepared in `gui/helper/async_modal.h`. Free can be accomplished using special call `msg->setAttribute(Qt::WA_DeleteOnClose)` before opening. This method is automatically employed in `async_modal.h`. Docs: [WA_DeleteOnClose](https://doc.qt.io/qt-5/qt.html#WidgetAttribute-enum) , [QCloseEvent](https://doc.qt.io/qt-5/qcloseevent.html) ================================================ FILE: docs/user/SUMMARY.md ================================================ # Summary [Introduction](introduction.md) # The basics - [Getting Started](basics/getting_started.md) - [First Launch](basics/first_launch.md) - [Basics of the User Interface](basics/basics_of_user_interface.md) - [Menus and the Toolbar](basics/menus_and_the_toolbar.md) - [Writing Programs](basics/writing_programs.md) # Reference - [External Toolchains](reference/external_toolchains.md) - [Advanced Configuration](reference/advanced_configuration.md) - [Environment Variables](reference/advanced_configuration/environment_variables.md) ================================================ FILE: docs/user/basics/basics_of_user_interface.md ================================================ # User Interface Overview After selecting a configuration preset (or loading an example) in the initial dialog, the main **QtRvSim window** appears. This chapter introduces the primary functional areas of the interface necessary for basic operation. ![QtRvSim main window (empty workspace)](media/gui_elements/user_interface.webp) The interface can be divided into five main areas: ![The QtRvSim interface, divided into main sections](media/gui_elements/user_interface_annotated.webp) ## 1. Menu Bar & Toolbar (Blue Area) - Located at the **top of the window**. - The **Menu Bar** provides full access to all commands (`File`, `Machine`, `Windows`, etc.). - The **Toolbar** below offers quick access to the most commonly used commands via icons. **Key Toolbar Buttons (basic simulation):** - **Compile Source** – Assemble your code. - **Run** – Execute the program continuously. - **Step** – Advance execution one instruction (or one cycle) at a time. - **Reset** – Reset the simulation state to the beginning. ## 2. Program Panel (Green Area) - Toggles via **`Windows -> Program`** or **Ctrl+P**. - Displays the **sequence of assembly instructions** for the currently loaded program. - During execution, the **currently processed instruction** is highlighted (blue). - A dropdown menu allows you to follow a particular **pipeline stage** (fetch, decode, execute, etc.): - Default: **Follow Fetch** – ideal to get started. This panel makes it easy to trace the flow of instructions and observe where execution currently is. ## 3. Datapath Visualization / Core View (Red Area) - The central and most distinctive panel of QtRvSim. - Visually represents the processor’s **datapath** based on the selected configuration (single-cycle/pipelined). - Shows how instructions flow through components such as the **registers, ALU and memory**. This view makes abstract computer architecture concepts concrete by showing the processor "at work." ## 4. Simulation Counters (Purple Area) - Located in the **top-right corner** of the window. - Displays important **statistics** about the simulation: - **Cycles** – Total number of clock cycles since the last reset. - **Stalls** – Number of pipeline stalls encountered (in pipelined configurations). Values update continuously during execution (`Run`) or after each `Step`. Counters reset when you **Reset** or **Reload** the simulation. ## 5. Additional Information Windows QtRvSim offers several **dockable panels** to inspect system state in detail. These can be opened from the **`Windows`** menu: ![QtRvSim with the windows menu open](media/gui_elements/windows_menu.webp) You can rearrange, dock, tab, or float these windows to customize your workspace. Use **`Windows -> Reset Windows`** to restore the default layout. **Essential Panels for Beginners:** - **Registers** (`Windows -> Registers` or **Ctrl+D**) – Displays general-purpose registers **x0–x31** and the Program Counter. - **Memory** (`Windows -> Memory` or **Ctrl+M**) – Displays the contents of simulated memory. - **Terminal** (`Windows -> Terminal`) – For text I/O when programs interact with the console. ## Next Steps In the following chapter, we will look more closely at the available menus and toolbar options. *(If you are just starting out, you may skip ahead to chapter _____ and return later as needed.)* ================================================ FILE: docs/user/basics/first_launch.md ================================================ # First Launch When you start QtRvSim, or when you select **`File > New Simulation`**, the **Pre-sets and ELF File** dialog will appear. This dialog allows you to select a hardware configuration (pre-set) and, optionally, load a program to run. ![QtRvSim launch window](media/gui_elements/launch_window.webp) ## Dialog Options - **Config Pages (Left Pane)** Displays the different categories of hardware settings (ISA, caches, etc.). For now, focus on the **Pre-sets and ELF File** page. The other pages will be covered in later chapters. - **Pre-set (Radio Buttons)** Choose a predefined hardware configuration: - **No pipeline, no cache**: Simplest single-cycle processor model without cache memory. Ideal for learning basic instruction behavior. - **No pipeline, with cache**: Single-cycle processor with cache enabled. - **Pipelined, no hazard unit, no cache**: Classic 5-stage pipeline without automatic hazard handling. Useful to demonstrate data/control hazards. No cache. - **Pipelined, hazard unit, with cache**: More realistic 5-stage pipeline with hazard detection/forwarding enabled, plus caches. - **Custom**: Advanced mode – configure settings manually through the other Config pages. - **Reset at compile time (reload after make)** If enabled, the simulator automatically reapplies the selected configuration each time you assemble a program. *Recommended: Keep this option checked.* - **ELF Executable** Use the **Browse** button to load a precompiled RISC-V ELF program (`.elf`). *Tip: For most workflows, you’ll write and assemble assembly (`.s`) code directly in the editor instead.* - **Action Buttons** - **Example**: Loads a default example assembly program into the editor with the selected pre-set. *This is the easiest way to get started.* - **Start empty**: Opens the simulator with the selected pre-set and an empty editor, ready for you to write or load code. - **Load machine**: Applies the selected pre-set and loads the chosen ELF executable. - **Cancel**: Closes the dialog without applying changes. --- For your first run, we recommend selecting either: - **`No pipeline, no cache`** (simplest model), or - **`Pipelined with hazard unit and cache`** (closer to a realistic processor). Then click **`Example`** (to load a sample program) or **`Start empty`** (to write your own). For this manual, we’ll begin with **`Start empty`** and the **`No pipeline, no cache`** configuration. ================================================ FILE: docs/user/basics/getting_started.md ================================================ # Getting started ## System Requirements QtRvSim is built using the Qt framework and supports multiple platforms. Please ensure your system meets the following minimum requirements before installation. ### Supported Operating Systems - **Linux** (64-bit distributions with Qt 5.12+) - **macOS** (10.10 (Yosemite) or later) - **Windows** (Windows 7 or later) - **WebAssembly (Browser-based)** - Modern versions of Firefox, Chromium-based browsers or Safari ## Installation Download the appropriate installation files from the official QtRvSim releases page: ### WebAssembly Just open [comparch.edu.cvut.cz/qtrvsim/app](https://comparch.edu.cvut.cz/qtrvsim/app) in your web browser to run QtRvSim without any installation. ### Linux Installation Choose the installation method that best suits your distribution: [![Packaging status](https://repology.org/badge/vertical-allrepos/qtrvsim.svg)](https://repology.org/project/qtrvsim/versions) #### Ubuntu 18.04+ and Derivatives ```bash sudo add-apt-repository ppa:qtrvsimteam/ppa sudo apt-get update sudo apt-get install qtrvsim ``` #### Arch, CentOS, Debian, Fedora, openSUSE, Raspbian/Raspberry Pi OS 1. Visit the [Open Build Service download page](https://software.opensuse.org/download.html?project=home%3Ajdupak&package=qtrvsim) 2. Select your specific distribution and version 3. Follow the provided instructions to add the repository and install it #### Arch Linux (AUR) You can install QtRvSim from the Arch User Repository (AUR) using an AUR helper like `yay`: ```bash yay -S qtrvsim # or yay -S qtrvsim-git # for the latest development version ``` #### Flatpak (Universal Package) ```bash flatpak install flathub cz.cvut.edu.comparch.qtrvsim ``` Launch with: ```bash flatpak run cz.cvut.edu.comparch.qtrvsim ``` #### NixOS/Nix Install the package directly from the official `nixpkgs` [repository](https://search.nixos.org/packages?channel=unstable&show=qtrvsim&query=qtrvsim).
Permanent installation Add `qtrvsim` to your `configuration.nix` file: ```nix environment.systemPackages = [ pkgs.qtrvsim ]; ```
Temporary usage ```bash nix-shell -p qtrvsim --run qtrvsim_gui ```
### Windows Installation 1. Download the Windows archive: `qtrvsim-mingw32-[version].zip` 2. Extract the contents to your preferred location (e.g., `C:\QtRvSim`) 3. Launch `qtrvsim.exe` from the extracted folder 4. No additional installation steps required ### macOS Installation #### Method 1: Disk Image (Recommended) 1. Download the macOS disk image: `qtrvsim-macos-[version].dmg` 2. Open the .dmg file 3. Drag the QtRvSim application to your Applications folder 4. **First launch**: Right-click the application in Applications, select "Open," and confirm if prompted by Gatekeeper #### Method 2: Archive File 1. Download the macOS archive: `qtrvsim-macos-[version].zip` 2. Extract the QtRvSim.app bundle 3. Move QtRvSim.app to your Applications folder 4. Follow step 4 from Method 1 for first launch ================================================ FILE: docs/user/basics/menus_and_the_toolbar.md ================================================ ### Menu Bar The menu bar provides access to all features of QtRvSim. It is divided into standard menus: **File**, **Machine**, **Windows**, **Options**, and **Help**. --- #### File Menu Handles operations related to simulation setup, source files, and the application itself. - **New simulation...** (Ctrl+N) Opens the *Pre-sets and ELF File* configuration dialog to start a new simulation. - **Reload simulation** (Ctrl+Shift+R) Reassembles the source code currently in the editor and resets the simulation state (registers, memory, cycle count). - **Print** (Ctrl+Alt+P) Prints the contents of the active panel or editor. - **New source** (Ctrl+F) Opens a new editor tab to write a fresh assembly program. - **Open source** (Ctrl+O) Loads an existing RISC-V assembly source file (`.s`) into the editor. - **Save source** (Ctrl+S) Saves the current editor contents. If previously unsaved, behaves like "Save source as." - **Save source as** Prompts for a filename and saves the editor contents to that path. - **Close source** (Ctrl+W) Closes the current editor tab (prompts to save unsaved work). - **Examples** Opens a submenu of example RISC-V programs bundled with QtRvSim. Selecting one loads it into the editor. - **Exit** (Ctrl+Q) Exits the QtRvSim application. --- #### Machine Menu Controls simulation execution and machine-specific options. - **Run** (Ctrl+R) Starts continuous execution at the configured speed. - **Pause** (Ctrl+P) Halts execution if running. - **Step** (Ctrl+T) Executes only one instruction. - **Speed Controls** Set the execution rate when using **Run**: - 1 instruction/s (Ctrl+1) - 2 instructions/s (Ctrl+2) - 5 instructions/s (Ctrl+5) - 10 instructions/s (Ctrl+0) - 25 instructions/s (Ctrl+F5) - Unlimited (Ctrl+U) – fastest possible speed - Max (Ctrl+A) – fastest speed, with reduced GUI updates (higher performance) - **Restart** Resets registers, memory, peripherals, and counters to state after the last assembly. - **Mnemonics Registers** (checkbox) Toggle register view to show mnemonic names (`ra`, `sp`) instead of raw (`x1`, `x2`). - **Show Symbol** ? to be added ? - **Compile Source** (Ctrl+E) Assembles source code in the editor using the built-in assembler. - **Build Executable** (Ctrl+B) Builds a standalone ELF file using an external RISC-V toolchain (e.g., GCC/Clang). #### Windows Menu Manages visibility of all simulation panels. - **Registers** (Ctrl+D) – Register state viewer. ![alt text](media/gui_elements/registers.webp) - **Program** (Ctrl+P) – Code view with current instruction highlighting. ![alt text](media/gui_elements/program.webp) - **Memory** (Ctrl+M) – Memory contents. ![alt text](media/gui_elements/memory.webp) - **Program Cache** (Ctrl+Shift+P) – Instruction cache visualization (if enabled). ![alt text](media/gui_elements/program_cache.webp) - **Data Cache** (Ctrl+Shift+M) – Data cache visualization (if enabled). ![alt text](media/gui_elements/data_cache.webp) - **L2 Cache** – Second-level cache view (if enabled). ![alt text](media/gui_elements/l2_cache.webp) - **Branch Predictor** – Displays prediction tables and data (if configured). ![alt text](media/gui_elements/branch_predictor.webp) - **Peripherals** – Container for simulated I/O devices. ![alt text](media/gui_elements/peripherals.webp) - **Terminal** – Simulated serial terminal. ![alt text](media/gui_elements/terminal.webp) - **LCD Display** – Simulated LCD. ![alt text](media/gui_elements/lcd_display.webp) - **Control and Status Registers (CSR)** (Ctrl+I) – CSR panel. ![alt text](media/gui_elements/control_and_status_registers.webp) - **Core View** – Datapath visualization (CPU core). ![alt text](media/gui_elements/core.webp) - **Messages** – Log and feedback panel. ![alt text](media/gui_elements/messages.webp) - **Reset Windows** – Restores all panels to their default positions. --- #### Options Menu - **Show Line Numbers** (checkbox) – Toggle line numbers in the code editor. --- #### Help Menu Provides information about QtRvSim, version details, and licensing. --- ### Toolbar ![Toolbar screenshot](media/gui_elements/toolbar.webp) The toolbar provides quick access to frequently used actions: - **New Simulation** – Equivalent to *File → New simulation...* (Ctrl+N). - **Reload** – Resets simulator state (equivalent to *Machine → Restart*). - **Run** – Start program execution (Ctrl+R). - **Pause** – Pause execution (Ctrl+P). - **Step** – Execute one instruction (Ctrl+T). - **Speed Controls** – Select execution speed (same as *Machine → Speed Controls*). - **New Source** – Open a new editor tab (Ctrl+F). - **Open Source** – Load an existing `.s` or `.elf` file (Ctrl+O). - **Save Source** – Save current file (Ctrl+S). - **Close Source** – Close active code editor tab (Ctrl+W). - **Compile Source** – Assemble current RISC-V program with the built-in assembler (Ctrl+E). - **Build Executable** – Build ELF with external RISC-V toolchain (Ctrl+B). ================================================ FILE: docs/user/basics/writing_programs.md ================================================ # Writing Programs This chapter covers how to write and run RISC-V assembly programs in QtRvSim. ## Using the Built-in Assembler QtRvSim includes a built-in assembler that can compile RISC-V assembly code directly within the application: 1. Write your assembly code in the editor 2. Click **Compile Source** (Ctrl+E) to assemble the code 3. The program is loaded into simulated memory and ready to execute This is the recommended workflow for learning and experimenting with RISC-V assembly. ## RISC-V Register Reference RISC-V has 32 general-purpose registers. The simulator supports both numeric names (`x0`–`x31`) and ABI mnemonic names: | Register | ABI Name | Description | |----------|----------|-------------| | x0 | zero | Hard-wired zero | | x1 | ra | Return address | | x2 | sp | Stack pointer | | x3 | gp | Global pointer | | x4 | tp | Thread pointer | | x5–x7 | t0–t2 | Temporary registers | | x8 | s0/fp | Saved register / Frame pointer | | x9 | s1 | Saved register | | x10–x11 | a0–a1 | Function arguments / Return values | | x12–x17 | a2–a7 | Function arguments | | x18–x27 | s2–s11 | Saved registers | | x28–x31 | t3–t6 | Temporary registers | Toggle between numeric and mnemonic names via **Machine → Mnemonic Registers**. ## Simple Assembly Program Template Here is a minimal RISC-V assembly template for use with the built-in assembler: ```asm .globl _start .text _start: # Your code here # Example: Load immediate values li a0, 42 # Load 42 into a0 li a1, 10 # Load 10 into a1 add a2, a0, a1 # a2 = a0 + a1 end: ebreak # Stop execution j end # Loop forever if continued ``` The `ebreak` instruction triggers a breakpoint exception, which causes the simulator to pause execution. ## Breakpoints and Debugging ### Software Breakpoints The `ebreak` instruction causes the simulator to pause execution. This is useful for stopping at specific points in your program: ```asm # ... some code ... ebreak # Pause here # ... more code ... ``` When `ebreak` is encountered, the simulator stops. You can then inspect registers and memory, and continue execution with **Step** or **Run**. ### Hardware Breakpoints You can also set breakpoints directly in the QtRvSim interface: 1. In the **Program** panel, click on the address or instruction where you want to break 2. The simulator will pause when that instruction is fetched ## Loading Programs There are several ways to load programs into QtRvSim: 1. **Built-in editor**: Write assembly in the editor and compile with Ctrl+E 2. **Open source file**: Use **File → Open Source** to load a `.s` file, then compile 3. **Load ELF**: In the launch dialog, use **Browse** to select a precompiled `.elf` file 4. **Build Executable**: Use **Machine → Build Executable** (Ctrl+B) to compile using an external toolchain ## Next Steps Once you're comfortable writing basic assembly programs, you may want to explore: - Using an [external RISC-V toolchain](external_toolchains.md) for compiling C programs or more complex projects ================================================ FILE: docs/user/book.toml ================================================ [book] title = "QtRVSim User Manual" authors = ["Filip Kraus"] language = "en" src = "." ================================================ FILE: docs/user/introduction.md ================================================ # Introduction Welcome to the **QtRvSim User Manual**. QtRvSim is a graphical simulator designed to support learning the fundamentals of computer architecture using the **RISC-V instruction set**. It provides an interactive environment for writing, assembling, and simulating simple RISC-V assembly programs. By visualizing instruction execution on different processor models, QtRvSim helps make abstract computer architecture concepts clear and tangible. The project is developed by the [Computer Architectures Education](http://comparch.edu.cvut.cz) project at [Czech Technical University](http://www.cvut.cz/) and souce code is available on [GitHub](https://github.com/cvut/qtrvsim). ## Key Features - **Visual Datapaths** – Observe execution on both single-cycle and 5-stage pipelined processor models. - **Integrated Tools** – Write code with the built-in text editor and assemble directly within the simulator. - **State Inspection** – Inspect register contents, memory, and cache status in real time. - **Peripheral Emulation** – Experiment with simulated devices, including a terminal, LCD, and LEDs/buttons. - **Cross-Platform Support** – Available on Linux, Windows, macOS, and via WebAssembly (browser-based). ## About This Manual This manual will guide you through: - Installing QtRvSim on your preferred platform - Understanding the user interface - ... Whether you are a student learning the basics of computer architecture or an instructor looking for an accessible teaching tool, QtRvSim provides an intuitive and hands-on way to experiment with RISC-V systems. ================================================ FILE: docs/user/reference/advanced_configuration/environment_variables.md ================================================ # Environment Variables QtRvSim supports several environment variables to customize its behavior. These are primarily useful for advanced users, portable deployments, and development purposes. --- ## GUI Configuration ### Portable Application Mode By default, QtRvSim GUI stores application state (window positions, last selected configuration, etc.) using the system's standard configuration location. To make the simulator fully portable (e.g., running from a USB drive), you can specify a custom configuration file path. **`QTRVSIM_CONFIG_FILE`** Set this variable to the path of a `.ini` file where QtRvSim should store its GUI state. **Example (Linux/macOS):** ```bash export QTRVSIM_CONFIG_FILE=/path/to/portable/qtrvsim_config.ini ./qtrvsim_gui ``` **Example (Windows):** ```cmd set QTRVSIM_CONFIG_FILE=D:\QtRvSim\config.ini qtrvsim_gui.exe ``` --- ### Scale GUi Fonts for Classroom Projector The fonts can be to small for students to see them from the more distant places or projector resolution can be interpolated or scaled that text in editors and menus is hard to read. The next option allows to scale fonts for the whole application. ```bash QT_SCALE_FACTOR=1.4 ./qtrvsim_gui ``` --- ## Logging Logging environment variables are primarily intended for development and debugging purposes. For detailed information about available logging options, please refer to the developer documentation. ================================================ FILE: docs/user/reference/advanced_configuration.md ================================================ # Advanced Configuration This section covers advanced configuration options for QtRvSim, including environment variables and other settings for power users. - [Environment Variables](advanced_configuration/environment_variables.md) – Customize simulator behavior through environment variables, including portable application mode. ================================================ FILE: docs/user/reference/external_toolchains.md ================================================ # External Toolchains For more complex programs or C code, you can use an external RISC-V cross-compiler toolchain instead of the built-in assembler. ## Installing the Toolchain **Ubuntu/Debian:** ```bash sudo apt install gcc-riscv64-unknown-elf ``` **Fedora:** ```bash sudo dnf install riscv64-elf-gcc ``` **Arch Linux:** ```bash sudo pacman -S riscv64-elf-gcc ``` **macOS (Homebrew):** ```bash brew install riscv64-elf-gcc ``` ## Compiling Assembly Programs To compile a simple assembly program: ```bash riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib -nostartfiles -o program.elf program.s ``` Or for RV64: ```bash riscv64-unknown-elf-gcc -march=rv64i -mabi=lp64 -nostdlib -nostartfiles -o program.elf program.s ``` The above choice is for basic RISC-V integer ISA with 32 registers. QtRvSim supports even following extensions: - M (`-march=rv32im`/`-march=rv64im`) - hardware multiply and divide instructions - A (with M `-march=rv32ima`/`-march=rv64ima`) - atomic operations - Zicsr (with A and M `-march=rv32ima_zicsr`/`-march=rv64ima_zicsr`) support for control registers The A, M and XLEN should match setting in the `Core ISA and Hazards` setup dialog tab. ## Compiling C Programs For C programs, you need a minimal startup file and appropriate compiler flags. **Startup code (`crt0.s`):** ```asm /* minimal replacement of crt0.o which is else provided by C library */ .globl main .globl _start .globl _heap_stack_start .globl _heap_stack_end .text _start: .option push .option norelax /* set a global pointer to allow access to C global variables */ la gp, __global_pointer$ /* it has to be done without "relax", because else it is * optimized to a gp register relative operation by linker */ .option pop la sp, _heap_stack_end addi a0, zero, 0 addi a1, zero, 0 jal main _exit: addi a0, zero, 0 addi a7, zero, 93 /* SYS_exit */ ecall /* catch case when syscalls are disabled */ ebreak j _exit .bss .align 4 /* the area which can be used for a heap from the bootom * and stack from the top */ _heap_stack_start: .skip 16384 _heap_stack_end: .end ``` **Example C program (`hello.c`):** ```c #define SERIAL_PORT_BASE 0xffffc000 #define SERP_TX_ST_REG (*(volatile unsigned *)(SERIAL_PORT_BASE + 0x00)) #define SERP_TX_DATA_REG (*(volatile unsigned *)(SERIAL_PORT_BASE + 0x04)) void print_char(char c) { while (SERP_TX_ST_REG & 0x1); // Wait while busy SERP_TX_DATA_REG = c; } void print_string(const char *s) { while (*s) { print_char(*s++); } } int main(void) { print_string("Hello from QtRvSim!\n"); return 0; } ``` **Compilation:** ```bash riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -c crt0.s -o crt0.o riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -O2 -c hello.c -o hello.o riscv64-unknown-elf-gcc -march=rv32i -mabi=ilp32 -nostdlib -static -T linker.ld crt0.o hello.o -o hello.elf ``` ## Linker Script A simple linker script (`linker.ld`) for QtRvSim: ```ld ENTRY(_start) MEMORY { RAM (rwx) : ORIGIN = 0x00000000, LENGTH = 64K } SECTIONS { .text : { *(.text) *(.text.*) } > RAM .rodata : { *(.rodata) *(.rodata.*) } > RAM .data : { *(.data) *(.data.*) } > RAM .bss : { *(.bss) *(.bss.*) *(COMMON) } > RAM } ``` ## Using Make for Build Automation For larger projects, a `Makefile` automates the build process: ```makefile CROSS = riscv64-unknown-elf- CC = $(CROSS)gcc AS = $(CROSS)as LD = $(CROSS)ld OBJCOPY = $(CROSS)objcopy OBJDUMP = $(CROSS)objdump ARCH_FLAGS = -march=rv32i -mabi=ilp32 CFLAGS = $(ARCH_FLAGS) -O2 -Wall -nostdlib -ffreestanding LDFLAGS = $(ARCH_FLAGS) -nostdlib -static -T linker.ld SOURCES = crt0.s main.c TARGET = program.elf OBJECTS = $(SOURCES:.s=.o) OBJECTS := $(OBJECTS:.c=.o) all: $(TARGET) %.o: %.s $(CC) $(ARCH_FLAGS) -c $< -o $@ %.o: %.c $(CC) $(CFLAGS) -c $< -o $@ $(TARGET): $(OBJECTS) $(CC) $(LDFLAGS) $^ -o $@ disasm: $(TARGET) $(OBJDUMP) -d $(TARGET) clean: rm -f $(OBJECTS) $(TARGET) .PHONY: all clean disasm ``` ## Loading ELF Files To load a compiled ELF file into QtRvSim: 1. In the launch dialog, use **Browse** to select the `.elf` file, then click **Load machine** 2. Or use **Machine → Build Executable** (Ctrl+B) to compile and load using an external toolchain directly from the editor ================================================ FILE: external/compiler/compile.sh ================================================ #!/usr/bin/env bash # This script compiles compilation tools for mips to be used with qtmips set -e INST_PREFIX="$(pwd)" INST_CT_PREFIX="$INST_PREFIX/ct-ng" mkdir -p "$INST_CT_PREFIX" # First update git submodule pushd "$(dirname "$0")" >/dev/null git submodule update crosstool-ng pushd crosstool-ng >/dev/null # Now compile it # TODO don't compile it in place? ./bootstrap ./configure --prefix="$INST_CT_PREFIX" make make install # TODO do cleanups? popd >/dev/null popd >/dev/null # Copy configuration cp "$(dirname "$0")/config" ct-ng/.config pushd ct-ng >/dev/null # And compile ./bin/ct-ng oldconfig CT_PREFIX="$INST_PREFIX" ./bin/ct-ng build popd >/dev/null # Cleanup installed crosstool-ng rm -rf ct-ng ================================================ FILE: external/compiler/config ================================================ # # Automatically generated file; DO NOT EDIT. # Crosstool-NG Configuration # CT_CONFIGURE_has_static_link=y CT_CONFIGURE_has_wget=y CT_CONFIGURE_has_curl=y CT_CONFIGURE_has_stat_flavor_GNU=y CT_CONFIGURE_has_make_3_81_or_newer=y CT_CONFIGURE_has_libtool_2_4_or_newer=y CT_CONFIGURE_has_libtoolize_2_4_or_newer=y CT_CONFIGURE_has_autoconf_2_63_or_newer=y CT_CONFIGURE_has_autoreconf_2_63_or_newer=y CT_CONFIGURE_has_automake_1_15_or_newer=y CT_CONFIGURE_has_gnu_m4_1_4_12_or_newer=y CT_CONFIGURE_has_svn=y CT_CONFIGURE_has_git=y CT_MODULES=y # # Paths and misc options # # # crosstool-NG behavior # # CT_OBSOLETE is not set # CT_EXPERIMENTAL is not set # CT_DEBUG_CT is not set # # Paths # CT_LOCAL_TARBALLS_DIR="${HOME}/src" CT_SAVE_TARBALLS=y CT_WORK_DIR="${CT_TOP_DIR}/.build" CT_BUILD_TOP_DIR="${CT_WORK_DIR}/${CT_HOST:+HOST-${CT_HOST}/}${CT_TARGET}" CT_PREFIX_DIR="${CT_PREFIX:-${HOME}/x-tools}/${CT_HOST:+HOST-${CT_HOST}/}${CT_TARGET}" CT_RM_RF_PREFIX_DIR=y CT_REMOVE_DOCS=y CT_PREFIX_DIR_RO=y CT_STRIP_HOST_TOOLCHAIN_EXECUTABLES=y # CT_STRIP_TARGET_TOOLCHAIN_EXECUTABLES is not set # # Downloading # CT_DOWNLOAD_AGENT_WGET=y # CT_DOWNLOAD_AGENT_CURL is not set # CT_DOWNLOAD_AGENT_NONE is not set # CT_FORBID_DOWNLOAD is not set # CT_FORCE_DOWNLOAD is not set CT_CONNECT_TIMEOUT=10 CT_DOWNLOAD_WGET_OPTIONS="--passive-ftp --tries=3 -nc --progress=dot:binary" # CT_ONLY_DOWNLOAD is not set # CT_USE_MIRROR is not set # # Extracting # # CT_FORCE_EXTRACT is not set CT_OVERRIDE_CONFIG_GUESS_SUB=y # CT_ONLY_EXTRACT is not set CT_PATCH_BUNDLED=y # CT_PATCH_LOCAL is not set # CT_PATCH_BUNDLED_LOCAL is not set # CT_PATCH_LOCAL_BUNDLED is not set # CT_PATCH_BUNDLED_FALLBACK_LOCAL is not set # CT_PATCH_LOCAL_FALLBACK_BUNDLED is not set # CT_PATCH_NONE is not set CT_PATCH_ORDER="bundled" # # Build behavior # CT_PARALLEL_JOBS=0 CT_LOAD="" CT_USE_PIPES=y CT_EXTRA_CFLAGS_FOR_BUILD="" CT_EXTRA_LDFLAGS_FOR_BUILD="" CT_EXTRA_CFLAGS_FOR_HOST="" CT_EXTRA_LDFLAGS_FOR_HOST="" # CT_CONFIG_SHELL_SH is not set # CT_CONFIG_SHELL_ASH is not set CT_CONFIG_SHELL_BASH=y # CT_CONFIG_SHELL_CUSTOM is not set CT_CONFIG_SHELL="${bash}" # # Logging # # CT_LOG_ERROR is not set # CT_LOG_WARN is not set # CT_LOG_INFO is not set CT_LOG_EXTRA=y # CT_LOG_ALL is not set # CT_LOG_DEBUG is not set CT_LOG_LEVEL_MAX="EXTRA" # CT_LOG_SEE_TOOLS_WARN is not set CT_LOG_PROGRESS_BAR=y CT_LOG_TO_FILE=y CT_LOG_FILE_COMPRESS=y # # Target options # CT_ARCH="mips" # CT_ARCH_alpha is not set # CT_ARCH_arm is not set # CT_ARCH_avr is not set # CT_ARCH_m68k is not set CT_ARCH_mips=y # CT_ARCH_nios2 is not set # CT_ARCH_powerpc is not set # CT_ARCH_s390 is not set # CT_ARCH_sh is not set # CT_ARCH_sparc is not set # CT_ARCH_x86 is not set # CT_ARCH_xtensa is not set CT_ARCH_alpha_AVAILABLE=y CT_ARCH_arm_AVAILABLE=y CT_ARCH_avr_AVAILABLE=y CT_ARCH_m68k_AVAILABLE=y CT_ARCH_microblaze_AVAILABLE=y CT_ARCH_mips_AVAILABLE=y CT_ARCH_nios2_AVAILABLE=y CT_ARCH_powerpc_AVAILABLE=y CT_ARCH_s390_AVAILABLE=y CT_ARCH_sh_AVAILABLE=y CT_ARCH_sparc_AVAILABLE=y CT_ARCH_x86_AVAILABLE=y CT_ARCH_xtensa_AVAILABLE=y CT_ARCH_SUFFIX="" # # Generic target options # # CT_MULTILIB is not set CT_DEMULTILIB=y CT_ARCH_USE_MMU=y CT_ARCH_SUPPORTS_BOTH_ENDIAN=y CT_ARCH_DEFAULT_BE=y CT_ARCH_BE=y # CT_ARCH_LE is not set CT_ARCH_ENDIAN="big" CT_ARCH_SUPPORTS_32=y CT_ARCH_SUPPORTS_64=y CT_ARCH_DEFAULT_32=y CT_ARCH_BITNESS=32 CT_ARCH_32=y # CT_ARCH_64 is not set # # Target optimisations # CT_ARCH_SUPPORTS_WITH_ARCH=y CT_ARCH_SUPPORTS_WITH_TUNE=y CT_ARCH_SUPPORTS_WITH_FLOAT=y CT_ARCH_ARCH="" CT_ARCH_TUNE="" # CT_ARCH_FLOAT_AUTO is not set # CT_ARCH_FLOAT_HW is not set CT_ARCH_FLOAT_SW=y CT_TARGET_CFLAGS="" CT_TARGET_LDFLAGS="" CT_ARCH_FLOAT="soft" # # mips other options # CT_ARCH_mips_o32=y CT_ARCH_mips_ABI="32" # # Toolchain options # # # General toolchain options # CT_WANTS_STATIC_LINK=y CT_WANTS_STATIC_LINK_CXX=y CT_STATIC_TOOLCHAIN=y CT_TOOLCHAIN_PKGVERSION="" CT_TOOLCHAIN_BUGURL="" # # Tuple completion and aliasing # CT_TARGET_VENDOR="qtmips" CT_TARGET_ALIAS_SED_EXPR="" CT_TARGET_ALIAS="" # # Toolchain type # CT_CROSS=y # CT_CANADIAN is not set CT_TOOLCHAIN_TYPE="cross" # # Build system # CT_BUILD="" CT_BUILD_PREFIX="" CT_BUILD_SUFFIX="" # # Misc options # # CT_TOOLCHAIN_ENABLE_NLS is not set # # Operating System # CT_BARE_METAL=y CT_KERNEL="bare-metal" CT_KERNEL_bare_metal=y # CT_KERNEL_linux is not set CT_KERNEL_bare_metal_AVAILABLE=y CT_KERNEL_linux_AVAILABLE=y CT_KERNEL_windows_AVAILABLE=y # # Common kernel options # # # Binary utilities # CT_ARCH_BINFMT_ELF=y CT_BINUTILS="binutils" CT_BINUTILS_binutils=y # # GNU binutils # CT_BINUTILS_VERSION="2.28" # CT_BINUTILS_SHOW_LINARO is not set CT_BINUTILS_V_2_28=y # CT_BINUTILS_V_2_27 is not set # CT_BINUTILS_V_2_26 is not set CT_BINUTILS_2_27_or_later=y CT_BINUTILS_2_26_or_later=y CT_BINUTILS_2_25_1_or_later=y CT_BINUTILS_2_25_or_later=y CT_BINUTILS_2_24_or_later=y CT_BINUTILS_2_23_2_or_later=y CT_BINUTILS_HAS_HASH_STYLE=y CT_BINUTILS_HAS_GOLD=y CT_BINUTILS_HAS_PLUGINS=y CT_BINUTILS_HAS_PKGVERSION_BUGURL=y CT_BINUTILS_LINKER_LD=y CT_BINUTILS_LINKERS_LIST="ld" CT_BINUTILS_LINKER_DEFAULT="bfd" CT_BINUTILS_EXTRA_CONFIG_ARRAY="" # # binutils other options # # # C-library # CT_LIBC="newlib" CT_LIBC_VERSION="2.5.0.20170323" CT_LIBC_newlib=y # CT_LIBC_none is not set CT_LIBC_avr_libc_AVAILABLE=y CT_LIBC_glibc_AVAILABLE=y CT_THREADS="none" CT_LIBC_mingw_AVAILABLE=y CT_LIBC_musl_AVAILABLE=y CT_LIBC_newlib_AVAILABLE=y # CT_CC_NEWLIB_SHOW_LINARO is not set CT_LIBC_NEWLIB_V_2_5_0=y # CT_LIBC_NEWLIB_V_2_4_0 is not set # CT_LIBC_NEWLIB_V_2_3_0 is not set # CT_LIBC_NEWLIB_V_2_2_0 is not set # CT_LIBC_NEWLIB_V_2_1_0 is not set # CT_LIBC_NEWLIB_V_2_0_0 is not set # CT_LIBC_NEWLIB_V_1_20_0 is not set # CT_LIBC_NEWLIB_V_1_19_0 is not set # CT_LIBC_NEWLIB_V_1_18_0 is not set # CT_LIBC_NEWLIB_V_1_17_0 is not set CT_LIBC_NEWLIB_2_5=y CT_LIBC_NEWLIB_2_5_or_later=y CT_LIBC_NEWLIB_2_4_or_later=y CT_LIBC_NEWLIB_2_3_or_later=y CT_LIBC_NEWLIB_2_2_or_later=y CT_LIBC_NEWLIB_2_1_or_later=y CT_LIBC_NEWLIB_2_0_or_later=y CT_LIBC_NEWLIB_TARGET_CFLAGS="" CT_LIBC_none_AVAILABLE=y CT_LIBC_uClibc_AVAILABLE=y CT_LIBC_SUPPORT_THREADS_NONE=y CT_LIBC_PROVIDES_CXA_ATEXIT=y # # Common C library options # CT_THREADS_NONE=y # # newlib other options # # CT_LIBC_NEWLIB_IO_C99FMT is not set # CT_LIBC_NEWLIB_IO_LL is not set # CT_LIBC_NEWLIB_IO_FLOAT is not set # CT_LIBC_NEWLIB_IO_POS_ARGS is not set CT_LIBC_NEWLIB_FVWRITE_IN_STREAMIO=y CT_LIBC_NEWLIB_UNBUF_STREAM_OPT=y CT_LIBC_NEWLIB_FSEEK_OPTIMIZATION=y # CT_LIBC_NEWLIB_DISABLE_SUPPLIED_SYSCALLS is not set # CT_LIBC_NEWLIB_REGISTER_FINI is not set CT_LIBC_NEWLIB_ATEXIT_DYNAMIC_ALLOC=y # CT_LIBC_NEWLIB_GLOBAL_ATEXIT is not set # CT_LIBC_NEWLIB_LITE_EXIT is not set # CT_LIBC_NEWLIB_REENT_SMALL is not set CT_LIBC_NEWLIB_MULTITHREAD=y # CT_LIBC_NEWLIB_EXTRA_SECTIONS is not set CT_LIBC_NEWLIB_WIDE_ORIENT=y CT_LIBC_NEWLIB_ENABLE_TARGET_OPTSPACE=y # CT_LIBC_NEWLIB_NANO_MALLOC is not set # CT_LIBC_NEWLIB_NANO_FORMATTED_IO is not set CT_LIBC_NEWLIB_EXTRA_CONFIG_ARRAY="" # # C compiler # CT_CC="gcc" CT_CC_CORE_PASS_2_NEEDED=y CT_CC_gcc=y CT_CC_GCC_VERSION="5.4.0" # CT_CC_GCC_SHOW_LINARO is not set # CT_CC_GCC_V_6_3_0 is not set CT_CC_GCC_V_5_4_0=y # CT_CC_GCC_V_4_9_4 is not set CT_CC_GCC_4_8_or_later=y CT_CC_GCC_4_9_or_later=y CT_CC_GCC_5=y CT_CC_GCC_5_or_later=y CT_CC_GCC_HAS_LIBMPX=y CT_CC_GCC_ENABLE_CXX_FLAGS="" CT_CC_GCC_CORE_EXTRA_CONFIG_ARRAY="" CT_CC_GCC_EXTRA_CONFIG_ARRAY="" # CT_CC_GCC_TARGET_FINAL is not set CT_CC_GCC_STATIC_LIBSTDCXX=y # CT_CC_GCC_SYSTEM_ZLIB is not set CT_CC_GCC_CONFIG_TLS=m # # Optimisation features # CT_CC_GCC_USE_GRAPHITE=y # # Settings for libraries running on target # CT_CC_GCC_ENABLE_TARGET_OPTSPACE=y # CT_CC_GCC_LIBMUDFLAP is not set # CT_CC_GCC_LIBSSP is not set # CT_CC_GCC_LIBQUADMATH is not set # # Misc. obscure options. # CT_CC_CXA_ATEXIT=y # CT_CC_GCC_DISABLE_PCH is not set CT_CC_GCC_LDBL_128=m # CT_CC_GCC_BUILD_ID is not set CT_CC_GCC_LNK_HASH_STYLE_DEFAULT=y # CT_CC_GCC_LNK_HASH_STYLE_SYSV is not set # CT_CC_GCC_LNK_HASH_STYLE_GNU is not set # CT_CC_GCC_LNK_HASH_STYLE_BOTH is not set CT_CC_GCC_LNK_HASH_STYLE="" CT_CC_GCC_DEC_FLOAT_AUTO=y # CT_CC_GCC_DEC_FLOAT_BID is not set # CT_CC_GCC_DEC_FLOAT_DPD is not set # CT_CC_GCC_DEC_FLOATS_NO is not set CT_CC_GCC_HAS_ARCH_OPTIONS=y # # archictecture-specific options # CT_CC_GCC_mips_llsc=m CT_CC_GCC_mips_synci=m # CT_CC_GCC_mips_plt is not set CT_CC_SUPPORT_CXX=y CT_CC_SUPPORT_FORTRAN=y CT_CC_SUPPORT_JAVA=y CT_CC_SUPPORT_ADA=y CT_CC_SUPPORT_OBJC=y CT_CC_SUPPORT_OBJCXX=y CT_CC_SUPPORT_GOLANG=y # # Additional supported languages: # # CT_CC_LANG_CXX is not set # CT_CC_LANG_FORTRAN is not set # # Debug facilities # # CT_DEBUG_gdb is not set # CT_DEBUG_ltrace is not set # CT_DEBUG_strace is not set # # Companion libraries # CT_COMPLIBS_NEEDED=y CT_GMP_NEEDED=y CT_MPFR_NEEDED=y CT_ISL_NEEDED=y CT_MPC_NEEDED=y CT_COMPLIBS=y # CT_LIBICONV is not set # CT_GETTEXT is not set CT_GMP=y CT_MPFR=y CT_ISL=y CT_MPC=y # CT_ZLIB is not set CT_GMP_V_6_1_2=y CT_GMP_5_0_2_or_later=y CT_GMP_VERSION="6.1.2" CT_MPFR_V_3_1_5=y CT_MPFR_VERSION="3.1.5" CT_ISL_V_0_16_1=y # CT_ISL_V_0_15 is not set CT_ISL_V_0_16_or_later=y CT_ISL_V_0_15_or_later=y CT_ISL_V_0_14_or_later=y CT_ISL_V_0_12_or_later=y CT_ISL_VERSION="0.16.1" CT_MPC_V_1_0_3=y CT_MPC_VERSION="1.0.3" # # Companion libraries common options # # CT_COMPLIBS_CHECK is not set # # Companion tools # # CT_COMP_TOOLS_FOR_HOST is not set # CT_COMP_TOOLS_autoconf is not set # CT_COMP_TOOLS_automake is not set # CT_COMP_TOOLS_libtool is not set # CT_COMP_TOOLS_m4 is not set # CT_COMP_TOOLS_make is not set ================================================ FILE: external/svgscene/CMakeLists.txt ================================================ cmake_minimum_required(VERSION 3.10) project(svgscene) set(QT_VERSION_MAJOR "auto" CACHE STRING "Qt major version to use. 5|6|auto") if (NOT "${QT_VERSION_MAJOR}" MATCHES "5|6|auto") message(FATAL_ERROR "Invalid value for QT_VERSION_MAJOR: ${QT_VERSION_MAJOR} (expected 5, 6 or auto)") endif () if ("${QT_VERSION_MAJOR}" STREQUAL "auto") find_package(QT NAMES Qt5 Qt6 COMPONENTS Core REQUIRED) endif () # Normally, we would use variable Qt5 or Qt6 to reference the Qt library. Here we do that through # this variable based on detected version major of Qt. set(QtLib "Qt${QT_VERSION_MAJOR}") find_package(${QtLib} REQUIRED COMPONENTS Core Widgets Gui) set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) add_library(svgscene STATIC src/svgscene/components/groupitem.cpp src/svgscene/components/groupitem.h src/svgscene/components/hyperlinkitem.cpp src/svgscene/components/hyperlinkitem.h src/svgscene/components/simpletextitem.cpp src/svgscene/components/simpletextitem.h src/svgscene/graphicsview/svggraphicsview.cpp src/svgscene/graphicsview/svggraphicsview.h src/svgscene/svgdocument.cpp src/svgscene/svgdocument.h src/svgscene/svggraphicsscene.cpp src/svgscene/svggraphicsscene.h src/svgscene/svghandler.cpp src/svgscene/svghandler.h src/svgscene/svgmetadata.cpp src/svgscene/svgmetadata.h src/svgscene/svgspec.h src/svgscene/polyfills/qt5/qwheelevent.h src/svgscene/polyfills/qt5/qstringview.h src/svgscene/utils/logging.h src/svgscene/utils/memory_ownership.h ) target_link_libraries(svgscene PRIVATE ${QtLib}::Core ${QtLib}::Gui ${QtLib}::Widgets) target_include_directories(svgscene PUBLIC src PRIVATE src/svgscene) add_executable(svgscene-example EXCLUDE_FROM_ALL src/example/main.cpp src/example/mainwindow.cpp src/example/mainwindow.h src/example/mainwindow.ui ) target_link_libraries(svgscene-example PRIVATE ${QtLib}::Core ${QtLib}::Gui ${QtLib}::Widgets svgscene) ================================================ FILE: external/svgscene/LICENSE ================================================ GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright (C) 2007 Free Software Foundation, Inc. Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The GNU General Public License is a free, copyleft license for software and other kinds of works. The licenses for most software and other practical works are designed to take away your freedom to share and change the works. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change all versions of a program--to make sure it remains free software for all its users. We, the Free Software Foundation, use the GNU General Public License for most of our software; it applies also to any other work released this way by its authors. You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for them if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs, and that you know you can do these things. To protect your rights, we need to prevent others from denying you these rights or asking you to surrender the rights. Therefore, you have certain responsibilities if you distribute copies of the software, or if you modify it: responsibilities to respect the freedom of others. For example, if you distribute copies of such a program, whether gratis or for a fee, you must pass on to the recipients the same freedoms that you received. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. Developers that use the GNU GPL protect your rights with two steps: (1) assert copyright on the software, and (2) offer you this License giving you legal permission to copy, distribute and/or modify it. For the developers' and authors' protection, the GPL clearly explains that there is no warranty for this free software. For both users' and authors' sake, the GPL requires that modified versions be marked as changed, so that their problems will not be attributed erroneously to authors of previous versions. Some devices are designed to deny users access to install or run modified versions of the software inside them, although the manufacturer can do so. This is fundamentally incompatible with the aim of protecting users' freedom to change the software. The systematic pattern of such abuse occurs in the area of products for individuals to use, which is precisely where it is most unacceptable. Therefore, we have designed this version of the GPL to prohibit the practice for those products. If such problems arise substantially in other domains, we stand ready to extend this provision to those domains in future versions of the GPL, as needed to protect the freedom of users. Finally, every program is threatened constantly by software patents. States should not allow patents to restrict development and use of software on general-purpose computers, but in those that do, we wish to avoid the special danger that patents applied to a free program could make it effectively proprietary. To prevent this, the GPL assures that patents cannot be used to render the program non-free. The precise terms and conditions for copying, distribution and modification follow. TERMS AND CONDITIONS 0. Definitions. "This License" refers to version 3 of the GNU General Public License. "Copyright" also means copyright-like laws that apply to other kinds of works, such as semiconductor masks. "The Program" refers to any copyrightable work licensed under this License. Each licensee is addressed as "you". "Licensees" and "recipients" may be individuals or organizations. To "modify" a work means to copy from or adapt all or part of the work in a fashion requiring copyright permission, other than the making of an exact copy. The resulting work is called a "modified version" of the earlier work or a work "based on" the earlier work. A "covered work" means either the unmodified Program or a work based on the Program. To "propagate" a work means to do anything with it that, without permission, would make you directly or secondarily liable for infringement under applicable copyright law, except executing it on a computer or modifying a private copy. Propagation includes copying, distribution (with or without modification), making available to the public, and in some countries other activities as well. To "convey" a work means any kind of propagation that enables other parties to make or receive copies. Mere interaction with a user through a computer network, with no transfer of a copy, is not conveying. An interactive user interface displays "Appropriate Legal Notices" to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. 1. Source Code. The "source code" for a work means the preferred form of the work for making modifications to it. "Object code" means any non-source form of a work. A "Standard Interface" means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. The "System Libraries" of an executable work include anything, other than the work as a whole, that (a) is included in the normal form of packaging a Major Component, but which is not part of that Major Component, and (b) serves only to enable use of the work with that Major Component, or to implement a Standard Interface for which an implementation is available to the public in source code form. A "Major Component", in this context, means a major essential component (kernel, window system, and so on) of the specific operating system (if any) on which the executable work runs, or a compiler used to produce the work, or an object code interpreter used to run it. The "Corresponding Source" for a work in object code form means all the source code needed to generate, install, and (for an executable work) run the object code and to modify the work, including scripts to control those activities. However, it does not include the work's System Libraries, or general-purpose tools or generally available free programs which are used unmodified in performing those activities but which are not part of the work. For example, Corresponding Source includes interface definition files associated with source files for the work, and the source code for shared libraries and dynamically linked subprograms that the work is specifically designed to require, such as by intimate data communication or control flow between those subprograms and other parts of the work. The Corresponding Source need not include anything that users can regenerate automatically from other parts of the Corresponding Source. The Corresponding Source for a work in source code form is that same work. 2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. 3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. 4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. 5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. b) The work must carry prominent notices stating that it is released under this License and any conditions added under section 7. This requirement modifies the requirement in section 4 to "keep intact all notices". c) You must license the entire work, as a whole, under this License to anyone who comes into possession of a copy. This License will therefore apply, along with any applicable section 7 additional terms, to the whole of the work, and all its parts, regardless of how they are packaged. This License gives no permission to license the work in any other way, but it does not invalidate such permission if you have separately received it. d) If the work has interactive user interfaces, each must display Appropriate Legal Notices; however, if the Program has interactive interfaces that do not display Appropriate Legal Notices, your work need not make them do so. A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an "aggregate" if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. 6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. b) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by a written offer, valid for at least three years and valid for as long as you offer spare parts or customer support for that product model, to give anyone who possesses the object code either (1) a copy of the Corresponding Source for all the software in the product that is covered by this License, on a durable physical medium customarily used for software interchange, for a price no more than your reasonable cost of physically performing this conveying of source, or (2) access to copy the Corresponding Source from a network server at no charge. c) Convey individual copies of the object code with a copy of the written offer to provide the Corresponding Source. This alternative is allowed only occasionally and noncommercially, and only if you received the object code with such an offer, in accord with subsection 6b. d) Convey the object code by offering access from a designated place (gratis or for a charge), and offer equivalent access to the Corresponding Source in the same way through the same place at no further charge. You need not require recipients to copy the Corresponding Source along with the object code. If the place to copy the object code is a network server, the Corresponding Source may be on a different server (operated by you or a third party) that supports equivalent copying facilities, provided you maintain clear directions next to the object code saying where to find the Corresponding Source. Regardless of what server hosts the Corresponding Source, you remain obligated to ensure that it is available for as long as needed to satisfy these requirements. e) Convey the object code using peer-to-peer transmission, provided you inform other peers where the object code and Corresponding Source of the work are being offered to the general public at no charge under subsection 6d. A separable portion of the object code, whose source code is excluded from the Corresponding Source as a System Library, need not be included in conveying the object code work. A "User Product" is either (1) a "consumer product", which means any tangible personal property which is normally used for personal, family, or household purposes, or (2) anything designed or sold for incorporation into a dwelling. In determining whether a product is a consumer product, doubtful cases shall be resolved in favor of coverage. For a particular product received by a particular user, "normally used" refers to a typical or common use of that class of product, regardless of the status of the particular user or of the way in which the particular user actually uses, or expects or is expected to use, the product. A product is a consumer product regardless of whether the product has substantial commercial, industrial or non-consumer uses, unless such uses represent the only significant mode of use of the product. "Installation Information" for a User Product means any methods, procedures, authorization keys, or other information required to install and execute modified versions of a covered work in that User Product from a modified version of its Corresponding Source. The information must suffice to ensure that the continued functioning of the modified object code is in no case prevented or interfered with solely because modification has been made. If you convey an object code work under this section in, or with, or specifically for use in, a User Product, and the conveying occurs as part of a transaction in which the right of possession and use of the User Product is transferred to the recipient in perpetuity or for a fixed term (regardless of how the transaction is characterized), the Corresponding Source conveyed under this section must be accompanied by the Installation Information. But this requirement does not apply if neither you nor any third party retains the ability to install modified object code on the User Product (for example, the work has been installed in ROM). The requirement to provide Installation Information does not include a requirement to continue to provide support service, warranty, or updates for a work that has been modified or installed by the recipient, or for the User Product in which it has been modified or installed. Access to a network may be denied when the modification itself materially and adversely affects the operation of the network or violates the rules and protocols for communication across the network. Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. 7. Additional Terms. "Additional permissions" are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. Notwithstanding any other provision of this License, for material you add to a covered work, you may (if authorized by the copyright holders of that material) supplement the terms of this License with terms: a) Disclaiming warranty or limiting liability differently from the terms of sections 15 and 16 of this License; or b) Requiring preservation of specified reasonable legal notices or author attributions in that material or in the Appropriate Legal Notices displayed by works containing it; or c) Prohibiting misrepresentation of the origin of that material, or requiring that modified versions of such material be marked in reasonable ways as different from the original version; or d) Limiting the use for publicity purposes of names of licensors or authors of the material; or e) Declining to grant rights under trademark law for use of some trade names, trademarks, or service marks; or f) Requiring indemnification of licensors and authors of that material by anyone who conveys the material (or modified versions of it) with contractual assumptions of liability to the recipient, for any liability that these contractual assumptions directly impose on those licensors and authors. All other non-permissive additional terms are considered "further restrictions" within the meaning of section 10. If the Program as you received it, or any part of it, contains a notice stating that it is governed by this License along with a term that is a further restriction, you may remove that term. If a license document contains a further restriction but permits relicensing or conveying under this License, you may add to a covered work material governed by the terms of that license document, provided that the further restriction does not survive such relicensing or conveying. If you add terms to a covered work in accord with this section, you must place, in the relevant source files, a statement of the additional terms that apply to those files, or a notice indicating where to find the applicable terms. Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. 8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. Moreover, your license from a particular copyright holder is reinstated permanently if the copyright holder notifies you of the violation by some reasonable means, this is the first time you have received notice of violation of this License (for any work) from that copyright holder, and you cure the violation prior to 30 days after your receipt of the notice. Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. 9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. 10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An "entity transaction" is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. 11. Patents. A "contributor" is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's "contributor version". A contributor's "essential patent claims" are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, "control" includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. Each contributor grants you a non-exclusive, worldwide, royalty-free patent license under the contributor's essential patent claims, to make, use, sell, offer for sale, import and otherwise run, modify and propagate the contents of its contributor version. In the following three paragraphs, a "patent license" is any express agreement or commitment, however denominated, not to enforce a patent (such as an express permission to practice a patent or covenant not to sue for patent infringement). To "grant" such a patent license to a party means to make such an agreement or commitment not to enforce a patent against the party. If you convey a covered work, knowingly relying on a patent license, and the Corresponding Source of the work is not available for anyone to copy, free of charge and under the terms of this License, through a publicly available network server or other readily accessible means, then you must either (1) cause the Corresponding Source to be so available, or (2) arrange to deprive yourself of the benefit of the patent license for this particular work, or (3) arrange, in a manner consistent with the requirements of this License, to extend the patent license to downstream recipients. "Knowingly relying" means you have actual knowledge that, but for the patent license, your conveying the covered work in a country, or your recipient's use of the covered work in a country, would infringe one or more identifiable patents in that country that you have reason to believe are valid. If, pursuant to or in connection with a single transaction or arrangement, you convey, or propagate by procuring conveyance of, a covered work, and grant a patent license to some of the parties receiving the covered work authorizing them to use, propagate, modify or convey a specific copy of the covered work, then the patent license you grant is automatically extended to all recipients of the covered work and works based on it. A patent license is "discriminatory" if it does not include within the scope of its coverage, prohibits the exercise of, or is conditioned on the non-exercise of one or more of the rights that are specifically granted under this License. You may not convey a covered work if you are a party to an arrangement with a third party that is in the business of distributing software, under which you make payment to the third party based on the extent of your activity of conveying the work, and under which the third party grants, to any of the parties who would receive the covered work from you, a discriminatory patent license (a) in connection with copies of the covered work conveyed by you (or copies made from those copies), or (b) primarily for and in connection with specific products or compilations that contain the covered work, unless you entered into that arrangement, or that patent license was granted, prior to 28 March 2007. Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. 12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. 13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. 14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License "or any later version" applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. If the Program specifies that a proxy can decide which future versions of the GNU General Public License can be used, that proxy's public statement of acceptance of a version permanently authorizes you to choose that version for the Program. Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. 15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. 17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS How to Apply These Terms to Your New Programs If you develop a new program, and you want it to be of the greatest possible use to the public, the best way to achieve this is to make it free software which everyone can redistribute and change under these terms. To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the "copyright" line and a pointer to where the full notice is found. {one line to give the program's name and a brief idea of what it does.} Copyright (C) {year} {name of author} This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with this program. If not, see . Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: {project} Copyright (C) {year} {fullname} This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an "about box". You should also get your employer (if you work as a programmer) or school, if any, to sign a "copyright disclaimer" for the program, if necessary. For more information on this, and how to apply and follow the GNU GPL, see . The GNU General Public License does not permit incorporating your program into proprietary programs. If your program is a subroutine library, you may consider it more useful to permit linking proprietary applications with the library. If this is what you want to do, use the GNU Lesser General Public License instead of this License. But first, please read . ================================================ FILE: external/svgscene/README.md ================================================ # Qt SVG DOM This tool parses an SVG file into Qt graphics objects and provides tools to lookup elements based on xml attributes in the SVG file. **WARNING** SVG support is extremely limited, but it is enough to render most static SVGs. ## Based on - [svgscene](https://github.com/fvacek/svgscene) - [libshv](https://github.com/silicon-heaven/libshv/) - [Qt SVG](https://github.com/qt/qtsvg) ## Roadmap - Parametrize xml attributes to save - Add default style attributes. - Store style in struct, not hashtable. Maybe parse to enums. ================================================ FILE: external/svgscene/src/example/main.cpp ================================================ #include "mainwindow.h" #include "svgscene/utils/logging.h" #include int main(int argc, char *argv[]) { QApplication a(argc, argv); MainWindow w; w.show(); w.openFile(QApplication::arguments().value(1)); return QApplication::exec(); } ================================================ FILE: external/svgscene/src/example/mainwindow.cpp ================================================ #include "mainwindow.h" #include "svgscene/components/simpletextitem.h" #include "svgscene/graphicsview/svggraphicsview.h" #include "svgscene/svgdocument.h" #include "svgscene/svghandler.h" #include "ui_mainwindow.h" #include #include #include #include using namespace svgscene; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow) { ui->setupUi(this); m_scene = new QGraphicsScene(this); ui->graphicsView->setScene(m_scene); } MainWindow::~MainWindow() { delete ui; } void MainWindow::openFile(const QString &fn) { QString file_name = fn; if (file_name.isEmpty()) file_name = QFileDialog::getOpenFileName( this, tr("Open Image"), "", tr("SVG Image Files (*.svg)")); if (file_name.isEmpty()) return; QFile f(file_name); if (f.open(QFile::ReadOnly)) { m_scene->clear(); QXmlStreamReader rd(&f); SvgHandler h(m_scene); } } void MainWindow::on_action_Open_triggered() { openFile(); } void MainWindow::on_actionZoom_to_fit_triggered() { ui->graphicsView->zoomToFit(); } void MainWindow::inc() {} ================================================ FILE: external/svgscene/src/example/mainwindow.h ================================================ #pragma once #include "svgscene/components/simpletextitem.h" #include #include class QGraphicsScene; namespace Ui { class MainWindow; } class MainWindow : public QMainWindow { Q_OBJECT public: explicit MainWindow(QWidget *parent = nullptr); ~MainWindow() override; void openFile(const QString &fn = QString()); private: Q_SLOT void on_action_Open_triggered(); Q_SLOT void on_actionZoom_to_fit_triggered(); Q_SLOT void inc(); private: Ui::MainWindow *ui; QGraphicsScene *m_scene; QTimer test; svgscene::SimpleTextItem *label; int counter = 0; }; ================================================ FILE: external/svgscene/src/example/mainwindow.ui ================================================ MainWindow 0 0 1000 693 MainWindow 0 0 1000 32 &File &View TopToolBarArea false &Open Z&oom to fit Ctrl+I true SvgGraphicsView QGraphicsView
svgscene/graphicsview/svggraphicsview.h
================================================ FILE: external/svgscene/src/svgscene/components/groupitem.cpp ================================================ #include "groupitem.h" #include namespace svgscene { GroupItem::GroupItem(QGraphicsItem *parent) : Super(parent) {} } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/components/groupitem.h ================================================ #pragma once #include namespace svgscene { class GroupItem : public QGraphicsRectItem { using Super = QGraphicsRectItem; public: explicit GroupItem(QGraphicsItem *parent = nullptr); }; } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/components/hyperlinkitem.cpp ================================================ #include "hyperlinkitem.h" #include "svgmetadata.h" #include "utils/logging.h" LOG_CATEGORY("svgscene.hyperlink"); namespace svgscene { HyperlinkItem::HyperlinkItem() = default; QString svgscene::HyperlinkItem::getTargetName() const { // href attribute is mandatory, therefore using default value return getXmlAttributeOr(this, "href", ""); } void HyperlinkItem::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { Q_UNUSED(event); DEBUG("Link triggered (href: %s).", qPrintable(getTargetName())); emit triggered(); } } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/components/hyperlinkitem.h ================================================ #pragma once #include "groupitem.h" namespace svgscene { /** * Represents SVG element . * * Works exactly as the group item but it adds emits event on doubleclick. * * @see https://developer.mozilla.org/en-US/docs/Web/SVG/Element/a * @see https://www.w3.org/TR/SVG11/linking.html#Links */ class HyperlinkItem : public QObject, public GroupItem { Q_OBJECT public: explicit HyperlinkItem(); QString getTargetName() const; void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; signals: void triggered(); }; } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/components/simpletextitem.cpp ================================================ #include "simpletextitem.h" #include namespace svgscene { SimpleTextItem::SimpleTextItem(const CssAttributes &css, QGraphicsItem *parent) : Super(parent) { const QString anchor = css.value(QStringLiteral("text-anchor")); if (anchor == QLatin1String("middle")) m_alignment = Qt::AlignHCenter; else if (anchor == QLatin1String("end")) m_alignment = Qt::AlignRight; else m_alignment = Qt::AlignLeft; } void SimpleTextItem::setText(const QString &text) { if (!m_origTransformLoaded) { m_origTransformLoaded = true; m_origTransform = transform(); } Super::setText(text); if (m_alignment != Qt::AlignLeft) { qreal w = boundingRect().width(); QTransform t = m_origTransform; if (m_alignment == Qt::AlignHCenter) t.translate(-w / 2, 0); else if (m_alignment == Qt::AlignRight) t.translate(-w, 0); setTransform(t); } } void SimpleTextItem::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { Super::paint(painter, option, widget); // painter->setPen(Qt::green); // painter->drawRect(boundingRect()); } } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/components/simpletextitem.h ================================================ #pragma once #include "svgscene/svghandler.h" #include namespace svgscene { class SimpleTextItem : public QGraphicsSimpleTextItem { using Super = QGraphicsSimpleTextItem; public: explicit SimpleTextItem(const CssAttributes &css, QGraphicsItem *parent = nullptr); void setText(const QString &text); void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; private: int m_alignment = Qt::AlignLeft; QTransform m_origTransform; bool m_origTransformLoaded = false; }; } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/graphicsview/svggraphicsview.cpp ================================================ #include "svggraphicsview.h" #include "polyfills/qt5/qwheelevent.h" #include "utils/logging.h" #include LOG_CATEGORY("svgscene.view"); SvgGraphicsView::SvgGraphicsView(QWidget *parent) : Super(parent) {} void SvgGraphicsView::zoomToFit() { if (scene()) { QRectF sr = scene()->sceneRect(); fitInView(sr, Qt::KeepAspectRatio); } } void SvgGraphicsView::zoom(double delta, const QPointF &mouse_pos) { LOG() << "delta:" << delta << "center_pos:" << mouse_pos.x() << mouse_pos.y(); double factor = delta / 100; factor = 1 + factor; if (factor < 0) factor = 0.1; scale(factor, factor); QRect view_rect = QRect(viewport()->pos(), viewport()->size()); QPoint view_center = view_rect.center(); QSize view_d(view_center.x() - mouse_pos.x(), view_center.y() - mouse_pos.y()); view_d /= factor; view_center = QPoint(mouse_pos.x() + view_d.width(), mouse_pos.y() + view_d.height()); QPointF new_scene_center = mapToScene(view_center); centerOn(new_scene_center); } void SvgGraphicsView::paintEvent(QPaintEvent *event) { Super::paintEvent(event); } void SvgGraphicsView::wheelEvent(QWheelEvent *ev) { if (ev->angleDelta().y() != 0) { // vertical orientation if (ev->modifiers() == Qt::ControlModifier) { double delta = ev->angleDelta().y(); zoom(delta / 10, QWheelEvent_poly(ev).position()); ev->accept(); return; } } Super::wheelEvent(ev); } void SvgGraphicsView::mousePressEvent(QMouseEvent *ev) { if (ev->button() == Qt::LeftButton && ev->modifiers() == Qt::ControlModifier) { m_dragMouseStartPos = ev->pos(); setCursor(QCursor(Qt::ClosedHandCursor)); ev->accept(); return; } Super::mousePressEvent(ev); } void SvgGraphicsView::mouseReleaseEvent(QMouseEvent *ev) { if (ev->button() == Qt::LeftButton && ev->modifiers() == Qt::ControlModifier) { setCursor(QCursor()); } Super::mouseReleaseEvent(ev); } void SvgGraphicsView::mouseMoveEvent(QMouseEvent *ev) { if (ev->buttons() == Qt::LeftButton && ev->modifiers() == Qt::ControlModifier) { QPoint pos = ev->pos(); QRect view_rect = QRect(viewport()->pos(), viewport()->size()); QPoint view_center = view_rect.center(); QPoint d(pos.x() - m_dragMouseStartPos.x(), pos.y() - m_dragMouseStartPos.y()); view_center -= d; QPointF new_scene_center = mapToScene(view_center); centerOn(new_scene_center); m_dragMouseStartPos = pos; ev->accept(); return; } Super::mouseMoveEvent(ev); } ================================================ FILE: external/svgscene/src/svgscene/graphicsview/svggraphicsview.h ================================================ #pragma once #include class SvgGraphicsView : public QGraphicsView { Q_OBJECT using Super = QGraphicsView; public: explicit SvgGraphicsView(QWidget *parent = nullptr); void zoomToFit(); protected: void zoom(double delta, const QPointF &mouse_pos); void paintEvent(QPaintEvent *event) override; void wheelEvent(QWheelEvent *ev) Q_DECL_OVERRIDE; void mousePressEvent(QMouseEvent *ev) Q_DECL_OVERRIDE; void mouseReleaseEvent(QMouseEvent *ev) Q_DECL_OVERRIDE; void mouseMoveEvent(QMouseEvent *ev) Q_DECL_OVERRIDE; private: QPoint m_dragMouseStartPos; }; ================================================ FILE: external/svgscene/src/svgscene/polyfills/qt5/qstringview.h ================================================ #pragma once #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) using QStringView = QString; #endif ================================================ FILE: external/svgscene/src/svgscene/polyfills/qt5/qwheelevent.h ================================================ #pragma once #include class QWheelEvent_poly final : public QWheelEvent { public: explicit QWheelEvent_poly(QWheelEvent *event) : QWheelEvent(*event) {} #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) QPointF position() const { return this->pos(); } #endif }; ================================================ FILE: external/svgscene/src/svgscene/svgdocument.cpp ================================================ #include "svgdocument.h" namespace svgscene { SvgDomTree SvgDocument::getRoot() const { return root; } SvgDocument::SvgDocument(QGraphicsItem *root) : root(root) {} } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/svgdocument.h ================================================ #pragma once #include "svgmetadata.h" #include namespace svgscene { template class SvgDomTree { public: explicit SvgDomTree(QGraphicsItem *root); TT *getElement() const; QString getAttrValueOr(const QString &attr_name, const QString &default_value = QString()); QString getCssValueOr(const QString &attr_name, const QString &default_value = QString()); template static SvgDomTree findFromParent( const QGraphicsItem *parent, const QString &attr_name = QString(), const QString &attr_value = QString()); template static QList> findAllFromParent( const QGraphicsItem *parent, const QString &attr_name = QString(), const QString &attr_value = QString()); template SvgDomTree find(const QString &attr_name = QString(), const QString &attr_value = QString()); template QList> findAll(const QString &attr_name = QString(), const QString &attr_value = QString()); protected: /** * WASM does not allows exception catching so we have to handle * recoverable errors in a different way - using nullptr. */ template static T *findFromParentRaw( const QGraphicsItem *parent, const QString &attr_name = QString(), const QString &attr_value = QString()); protected: TT *root; }; class SvgDocument { public: explicit SvgDocument(QGraphicsItem *root); SvgDomTree getRoot() const; protected: SvgDomTree root; }; template bool itemMatchesSelector( const QGraphicsItem *item, const QString &attr_name, const QString &attr_value) { if (item == nullptr) { throw std::out_of_range("Supplied item is nullptr."); } if (attr_name.isEmpty()) { return true; } auto attrs = getXmlAttributes(item); if (!attrs.contains(attr_name)) { return false; } return attr_value.isEmpty() || attrs.value(attr_name) == attr_value; } template SvgDomTree::SvgDomTree(QGraphicsItem *root) : root(dynamic_cast(root)) { if (this->root == nullptr) { throw std::out_of_range("Cannot build dom tree with nullptr item."); } } template TT *SvgDomTree::getElement() const { return root; } template QString SvgDomTree::getAttrValueOr(const QString &attr_name, const QString &default_value) { svgscene::XmlAttributes attrs = getXmlAttributes(root); return attrs.value(attr_name, default_value); } template QString SvgDomTree::getCssValueOr(const QString &attr_name, const QString &default_value) { return getCssAttributeOr(root, attr_name, default_value); } template template SvgDomTree SvgDomTree::findFromParent( const QGraphicsItem *parent, const QString &attr_name, const QString &attr_value) { if (!parent) { throw std::out_of_range("Current element is nullptr."); } for (QGraphicsItem *_child : parent->childItems()) { if (T *child = dynamic_cast(_child)) { if (itemMatchesSelector(child, attr_name, attr_value)) { return SvgDomTree(child); } } T *found = findFromParentRaw(_child, attr_name, attr_value); if (found != nullptr) { return SvgDomTree(found); } } throw std::out_of_range("Not found."); } template template T *SvgDomTree::findFromParentRaw( const QGraphicsItem *parent, const QString &attr_name, const QString &attr_value) { if (!parent) { return nullptr; } for (QGraphicsItem *_child : parent->childItems()) { if (T *child = dynamic_cast(_child)) { if (itemMatchesSelector(child, attr_name, attr_value)) { return child; } } T *found = findFromParentRaw(_child, attr_name, attr_value); if (found != nullptr) { return found; } } return nullptr; } template template QList> SvgDomTree::findAllFromParent( const QGraphicsItem *parent, const QString &attr_name, const QString &attr_value) { QList> ret; if (!parent) { return ret; } for (QGraphicsItem *_child : parent->childItems()) { if (T *child = dynamic_cast(_child)) { if (itemMatchesSelector(child, attr_name, attr_value)) { ret.append(SvgDomTree(child)); } } ret.append(findAllFromParent(_child, attr_name, attr_value)); } return ret; } template template SvgDomTree SvgDomTree::find(const QString &attr_name, const QString &attr_value) { return findFromParent(root, attr_name, attr_value); } template template QList> SvgDomTree::findAll(const QString &attr_name, const QString &attr_value) { return findAllFromParent(root, attr_name, attr_value); } } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/svggraphicsscene.cpp ================================================ #include "svggraphicsscene.h" #include "components/hyperlinkitem.h" #include #include namespace svgscene { void SvgGraphicsScene::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) { // Do not prevent default behavior. QGraphicsScene::mouseDoubleClickEvent(event); // Hyperlink will usually be obscured, but we need to propagate the click. QGraphicsItem *item = this->itemAt(event->pos(), {}); while (item != nullptr) { if (auto hyperlink = dynamic_cast(item)) { hyperlink->mouseDoubleClickEvent(event); break; } item = item->parentItem(); } } } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/svggraphicsscene.h ================================================ #pragma once #include namespace svgscene { /** * Graphics scene with extended support for SVG. * * Current support: * - hyperlinks (doubleclick) * Links in svg are parsed as groups and therefore they are always * obscured in qt scene. Therefore we have to travers the while tree up * and find the closest hyperlink element (if one exists). */ class SvgGraphicsScene : public QGraphicsScene { protected: virtual void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override; }; } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/svghandler.cpp ================================================ #include "svghandler.h" #include "components/groupitem.h" #include "components/hyperlinkitem.h" #include "components/simpletextitem.h" #include "polyfills/qt5/qstringview.h" #include "svgmetadata.h" #include "svgspec.h" #include "utils/logging.h" #include #include #include #include #include #include #include LOG_CATEGORY("svgscene.parsing"); namespace svgscene { SvgDocument parseFromFileName(QGraphicsScene *scene, const QString &filename) { QFile file(filename); file.open(QIODevice::ReadOnly); return parseFromFile(scene, &file); } SvgDocument parseFromFile(QGraphicsScene *scene, QFile *file) { QXmlStreamReader xml(file); SvgHandler handler(scene); handler.load(&xml); return handler.getDocument(); } // see: https://www.w3.org/TR/SVG11/ // '0' is 0x30 and '9' is 0x39 static inline bool isDigit(ushort ch) { static quint16 magic = 0x3ff; return ((ch >> 4) == 3) && (magic >> (ch & 15)); } static qreal toDouble(const QChar *&str) { const int maxLen = 255; // technically doubles can go til 308+ but whatever char temp[maxLen + 1]; int pos = 0; if (*str == QLatin1Char('-')) { temp[pos++] = '-'; ++str; } else if (*str == QLatin1Char('+')) { ++str; } while (isDigit(str->unicode()) && pos < maxLen) { temp[pos++] = str->toLatin1(); ++str; } if (*str == QLatin1Char('.') && pos < maxLen) { temp[pos++] = '.'; ++str; } while (isDigit(str->unicode()) && pos < maxLen) { temp[pos++] = str->toLatin1(); ++str; } bool exponent = false; if ((*str == QLatin1Char('e') || *str == QLatin1Char('E')) && pos < maxLen) { exponent = true; temp[pos++] = 'e'; ++str; if ((*str == QLatin1Char('-') || *str == QLatin1Char('+')) && pos < maxLen) { temp[pos++] = str->toLatin1(); ++str; } while (isDigit(str->unicode()) && pos < maxLen) { temp[pos++] = str->toLatin1(); ++str; } } temp[pos] = '\0'; qreal val; if (!exponent && pos < 10) { int ival = 0; const char *t = temp; bool neg = false; if (*t == '-') { neg = true; ++t; } while (*t && *t != '.') { ival *= 10; ival += (*t) - '0'; ++t; } if (*t == '.') { ++t; int div = 1; while (*t) { ival *= 10; ival += (*t) - '0'; div *= 10; ++t; } val = ((qreal)ival) / ((qreal)div); } else { val = ival; } if (neg) val = -val; } else { val = QByteArray::fromRawData(temp, pos).toDouble(); } return val; } static qreal toDouble(const QString &str, bool *ok = nullptr) { const QChar *c = str.constData(); qreal res = toDouble(c); if (ok) { *ok = ((*c) == QLatin1Char('\0')); } return res; } /* static qreal toDouble(const QStringView &str, bool *ok = nullptr) { const QChar *c = str.constData(); qreal res = toDouble(c); if (ok) { *ok = (c == (str.constData() + str.length())); } return res; } */ static inline void parseNumbersArray(const QChar *&str, QVarLengthArray &points) { while (str->isSpace()) ++str; while (isDigit(str->unicode()) || *str == QLatin1Char('-') || *str == QLatin1Char('+') || *str == QLatin1Char('.')) { points.append(toDouble(str)); while (str->isSpace()) ++str; if (*str == QLatin1Char(',')) ++str; // eat the rest of space while (str->isSpace()) ++str; } } static QVector parseNumbersList(const QChar *&str) { QVector points; if (!str) return points; points.reserve(32); while (str->isSpace()) ++str; while (isDigit(str->unicode()) || *str == QLatin1Char('-') || *str == QLatin1Char('+') || *str == QLatin1Char('.')) { points.append(toDouble(str)); while (str->isSpace()) ++str; if (*str == QLatin1Char(',')) ++str; // eat the rest of space while (str->isSpace()) ++str; } return points; } static QVector parsePercentageList(const QChar *&str) { QVector points; if (!str) return points; while (str->isSpace()) ++str; while ((*str >= QLatin1Char('0') && *str <= QLatin1Char('9')) || *str == QLatin1Char('-') || *str == QLatin1Char('+') || *str == QLatin1Char('.')) { points.append(toDouble(str)); while (str->isSpace()) ++str; if (*str == QLatin1Char('%')) ++str; while (str->isSpace()) ++str; if (*str == QLatin1Char(',')) ++str; // eat the rest of space while (str->isSpace()) ++str; } return points; } static inline int qsvg_h2i(char hex) { if (hex >= '0' && hex <= '9') return hex - '0'; if (hex >= 'a' && hex <= 'f') return hex - 'a' + 10; if (hex >= 'A' && hex <= 'F') return hex - 'A' + 10; return -1; } static inline int qsvg_hex2int(const char *s) { return (qsvg_h2i(s[0]) << 4) | qsvg_h2i(s[1]); } static inline int qsvg_hex2int(char s) { int h = qsvg_h2i(s); return (h << 4) | h; } bool qsvg_get_hex_rgb(const char *name, QRgb *rgb) { if (name[0] != '#') return false; name++; int len = static_cast(qstrlen(name)); int r, g, b; if (len == 12) { r = qsvg_hex2int(name); g = qsvg_hex2int(name + 4); b = qsvg_hex2int(name + 8); } else if (len == 9) { r = qsvg_hex2int(name); g = qsvg_hex2int(name + 3); b = qsvg_hex2int(name + 6); } else if (len == 6) { r = qsvg_hex2int(name); g = qsvg_hex2int(name + 2); b = qsvg_hex2int(name + 4); } else if (len == 3) { r = qsvg_hex2int(name[0]); g = qsvg_hex2int(name[1]); b = qsvg_hex2int(name[2]); } else { r = g = b = -1; } if ((uint)r > 255 || (uint)g > 255 || (uint)b > 255) { *rgb = 0; return false; } *rgb = qRgb(r, g, b); return true; } bool qsvg_get_hex_rgb(const QByteArray &str, int len, QRgb *rgb) { if (len > 13) return false; char tmp[16]; for (int i = 0; i < len; ++i) tmp[i] = str[i]; tmp[len] = 0; return qsvg_get_hex_rgb(tmp, rgb); } static QColor parseColor(const QString &color, const QString &opacity) { QColor ret; { QStringView color_str = QStringView(color).trimmed(); if (color_str.isEmpty()) return ret; switch (color_str.at(0).unicode()) { case '#': { // #rrggbb is very very common, so let's tackle it here // rather than falling back to QColor QRgb rgb; bool ok = qsvg_get_hex_rgb(color_str.toUtf8(), color_str.length(), &rgb); if (ok) ret.setRgb(rgb); break; } case 'r': { // starts with "rgb(", ends with ")" and consists of at least 7 // characters "rgb(,,)" if (color_str.length() >= 7 && color_str.at(color_str.length() - 1) == QLatin1Char(')') && QStringView(color_str.cbegin(), 4) == QLatin1String("rgb(")) { const QChar *s = color_str.cbegin() + 4; QVector compo = parseNumbersList(s); // 1 means that it failed after reaching non-parsable // character which is going to be "%" if (compo.size() == 1) { s = color_str.cbegin() + 4; compo = parsePercentageList(s); for (double &i : compo) i *= (qreal)2.55; } if (compo.size() == 3) { ret = QColor(int(compo[0]), int(compo[1]), int(compo[2])); } } break; } case 'c': if (color_str == QLatin1String("currentColor")) { // color = handler->currentColor(); return ret; } break; case 'i': if (color_str == QLatin1String("inherit")) return ret; break; default: ret = QColor(QString(color_str.cbegin(), color_str.length())); break; } } if (!opacity.isEmpty() && ret.isValid()) { bool ok = true; qreal op = qMin(qreal(1.0), qMax(qreal(0.0), toDouble(opacity, &ok))); if (!ok) op = 1.0; ret.setAlphaF(op); } return ret; } static QTransform parseTransformationMatrix(const QStringView &value) { if (value.isEmpty()) return {}; QTransform matrix; const QChar *str = value.cbegin(); const QChar *end = str + value.length(); while (str < end) { if (str->isSpace() || *str == QLatin1Char(',')) { ++str; continue; } enum State { Matrix, Translate, Rotate, Scale, SkewX, SkewY }; State state = Matrix; if (*str == QLatin1Char('m')) { // matrix const char *ident = "atrix"; for (int i = 0; i < 5; ++i) if (*(++str) != QLatin1Char(ident[i])) goto error; ++str; state = Matrix; } else if (*str == QLatin1Char('t')) { // translate const char *ident = "ranslate"; for (int i = 0; i < 8; ++i) if (*(++str) != QLatin1Char(ident[i])) goto error; ++str; state = Translate; } else if (*str == QLatin1Char('r')) { // rotate const char *ident = "otate"; for (int i = 0; i < 5; ++i) if (*(++str) != QLatin1Char(ident[i])) goto error; ++str; state = Rotate; } else if (*str == QLatin1Char('s')) { // scale, skewX, skewY ++str; if (*str == QLatin1Char('c')) { const char *ident = "ale"; for (int i = 0; i < 3; ++i) if (*(++str) != QLatin1Char(ident[i])) goto error; ++str; state = Scale; } else if (*str == QLatin1Char('k')) { if (*(++str) != QLatin1Char('e')) goto error; if (*(++str) != QLatin1Char('w')) goto error; ++str; if (*str == QLatin1Char('X')) state = SkewX; else if (*str == QLatin1Char('Y')) state = SkewY; else goto error; ++str; } else { goto error; } } else { goto error; } while (str < end && str->isSpace()) ++str; if (*str != QLatin1Char('(')) goto error; ++str; QVarLengthArray points; parseNumbersArray(str, points); if (*str != QLatin1Char(')')) goto error; ++str; if (state == Matrix) { if (points.count() != 6) goto error; matrix = QTransform(points[0], points[1], points[2], points[3], points[4], points[5]) * matrix; } else if (state == Translate) { if (points.count() == 1) matrix.translate(points[0], 0); else if (points.count() == 2) matrix.translate(points[0], points[1]); else goto error; } else if (state == Rotate) { if (points.count() == 1) { matrix.rotate(points[0]); } else if (points.count() == 3) { matrix.translate(points[1], points[2]); matrix.rotate(points[0]); matrix.translate(-points[1], -points[2]); } else { goto error; } } else if (state == Scale) { if (points.count() < 1 || points.count() > 2) goto error; qreal sx = points[0]; qreal sy = sx; if (points.count() == 2) sy = points[1]; matrix.scale(sx, sy); } else if (state == SkewX) { if (points.count() != 1) goto error; const auto deg2rad = qreal(0.017453292519943295769); matrix.shear(qTan(points[0] * deg2rad), 0); } else if (state == SkewY) { if (points.count() != 1) goto error; const auto deg2rad = qreal(0.017453292519943295769); matrix.shear(0, qTan(points[0] * deg2rad)); } } error: return matrix; } // the arc handling code underneath is from XSVG (BSD license) /* * Copyright 2002 USC/Information Sciences Institute * * Permission to use, copy, modify, distribute, and sell this software * and its documentation for any purpose is hereby granted without * fee, provided that the above copyright notice appear in all copies * and that both that copyright notice and this permission notice * appear in supporting documentation, and that the name of * Information Sciences Institute not be used in advertising or * publicity pertaining to distribution of the software without * specific, written prior permission. Information Sciences Institute * makes no representations about the suitability of this software for * any purpose. It is provided "as is" without express or implied * warranty. * * INFORMATION SCIENCES INSTITUTE DISCLAIMS ALL WARRANTIES WITH REGARD * TO THIS SOFTWARE, INCLUDING ALL IMPLIED WARRANTIES OF * MERCHANTABILITY AND FITNESS, IN NO EVENT SHALL INFORMATION SCIENCES * INSTITUTE BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL * DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA * OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER * TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR * PERFORMANCE OF THIS SOFTWARE. * */ static void pathArcSegment( QPainterPath &path, qreal xc, qreal yc, qreal th0, qreal th1, qreal rx, qreal ry, qreal xAxisRotation) { qreal sinTh, cosTh; qreal a00, a01, a10, a11; qreal x1, y1, x2, y2, x3, y3; qreal t; qreal thHalf; sinTh = qSin(xAxisRotation * (M_PI / 180.0)); cosTh = qCos(xAxisRotation * (M_PI / 180.0)); a00 = cosTh * rx; a01 = -sinTh * ry; a10 = sinTh * rx; a11 = cosTh * ry; thHalf = 0.5 * (th1 - th0); t = (8.0 / 3.0) * qSin(thHalf * 0.5) * qSin(thHalf * 0.5) / qSin(thHalf); x1 = xc + qCos(th0) - t * qSin(th0); y1 = yc + qSin(th0) + t * qCos(th0); x3 = xc + qCos(th1); y3 = yc + qSin(th1); x2 = x3 + t * qSin(th1); y2 = y3 - t * qCos(th1); path.cubicTo( a00 * x1 + a01 * y1, a10 * x1 + a11 * y1, a00 * x2 + a01 * y2, a10 * x2 + a11 * y2, a00 * x3 + a01 * y3, a10 * x3 + a11 * y3); } static void pathArc( QPainterPath &path, qreal rx, qreal ry, qreal x_axis_rotation, int large_arc_flag, int sweep_flag, qreal x, qreal y, qreal curx, qreal cury) { qreal sin_th, cos_th; qreal a00, a01, a10, a11; qreal x0, y0, x1, y1, xc, yc; qreal d, sfactor, sfactor_sq; qreal th0, th1, th_arc; int i, n_segs; qreal dx, dy, dx1, dy1, Pr1, Pr2, Px, Py, check; rx = qAbs(rx); ry = qAbs(ry); sin_th = qSin(x_axis_rotation * (M_PI / 180.0)); cos_th = qCos(x_axis_rotation * (M_PI / 180.0)); dx = (curx - x) / 2.0; dy = (cury - y) / 2.0; dx1 = cos_th * dx + sin_th * dy; dy1 = -sin_th * dx + cos_th * dy; Pr1 = rx * rx; Pr2 = ry * ry; Px = dx1 * dx1; Py = dy1 * dy1; /* Spec : check if radii are large enough */ check = Px / Pr1 + Py / Pr2; if (check > 1) { rx = rx * qSqrt(check); ry = ry * qSqrt(check); } a00 = cos_th / rx; a01 = sin_th / rx; a10 = -sin_th / ry; a11 = cos_th / ry; x0 = a00 * curx + a01 * cury; y0 = a10 * curx + a11 * cury; x1 = a00 * x + a01 * y; y1 = a10 * x + a11 * y; /* (x0, y0) is current point in transformed coordinate space. (x1, y1) is new point in transformed coordinate space. The arc fits a unit-radius circle in this space. */ d = (x1 - x0) * (x1 - x0) + (y1 - y0) * (y1 - y0); sfactor_sq = 1.0 / d - 0.25; if (sfactor_sq < 0) sfactor_sq = 0; sfactor = qSqrt(sfactor_sq); if (sweep_flag == large_arc_flag) sfactor = -sfactor; xc = 0.5 * (x0 + x1) - sfactor * (y1 - y0); yc = 0.5 * (y0 + y1) + sfactor * (x1 - x0); /* (xc, yc) is center of the circle. */ th0 = qAtan2(y0 - yc, x0 - xc); th1 = qAtan2(y1 - yc, x1 - xc); th_arc = th1 - th0; if (th_arc < 0 && sweep_flag) th_arc += 2 * M_PI; else if (th_arc > 0 && !sweep_flag) th_arc -= 2 * M_PI; n_segs = qCeil(qAbs(th_arc / (M_PI * 0.5 + 0.001))); for (i = 0; i < n_segs; i++) { pathArcSegment( path, xc, yc, th0 + i * th_arc / n_segs, th0 + (i + 1) * th_arc / n_segs, rx, ry, x_axis_rotation); } } static bool parsePathDataFast(const QStringView &dataStr, QPainterPath &path) { qreal x0 = 0, y0 = 0; // starting point qreal x = 0, y = 0; // current point char lastMode = 0; QPointF ctrlPt; const QChar *str = dataStr.cbegin(); const QChar *end = str + dataStr.size(); while (str != end) { while (str->isSpace()) ++str; QChar pathElem = *str; ++str; QChar endc = *end; *const_cast(end) = QChar('\0'); // parseNumbersArray requires // 0-termination that QStringView cannot // guarantee QVarLengthArray arg; parseNumbersArray(str, arg); *const_cast(end) = endc; if (pathElem == QLatin1Char('z') || pathElem == QLatin1Char('Z')) arg.append(0); // dummy const qreal *num = arg.constData(); int count = arg.count(); while (count > 0) { qreal offsetX = x; // correction offsets qreal offsetY = y; // for relative commands switch (pathElem.unicode()) { case 'm': { if (count < 2) { num++; count--; break; } x = x0 = num[0] + offsetX; y = y0 = num[1] + offsetY; num += 2; count -= 2; path.moveTo(x0, y0); // As per 1.2 spec 8.3.2 The "moveto" commands // If a 'moveto' is followed by multiple pairs of coordinates // without explicit commands, the subsequent pairs shall be // treated as implicit 'lineto' commands. pathElem = QLatin1Char('l'); } break; case 'M': { if (count < 2) { num++; count--; break; } x = x0 = num[0]; y = y0 = num[1]; num += 2; count -= 2; path.moveTo(x0, y0); // As per 1.2 spec 8.3.2 The "moveto" commands // If a 'moveto' is followed by multiple pairs of coordinates // without explicit commands, the subsequent pairs shall be // treated as implicit 'lineto' commands. pathElem = QLatin1Char('L'); } break; case 'z': case 'Z': { x = x0; y = y0; count--; // skip dummy num++; path.closeSubpath(); } break; case 'l': { if (count < 2) { num++; count--; break; } x = num[0] + offsetX; y = num[1] + offsetY; num += 2; count -= 2; path.lineTo(x, y); } break; case 'L': { if (count < 2) { num++; count--; break; } x = num[0]; y = num[1]; num += 2; count -= 2; path.lineTo(x, y); } break; case 'h': { x = num[0] + offsetX; num++; count--; path.lineTo(x, y); } break; case 'H': { x = num[0]; num++; count--; path.lineTo(x, y); } break; case 'v': { y = num[0] + offsetY; num++; count--; path.lineTo(x, y); } break; case 'V': { y = num[0]; num++; count--; path.lineTo(x, y); } break; case 'c': { if (count < 6) { num += count; count = 0; break; } QPointF c1(num[0] + offsetX, num[1] + offsetY); QPointF c2(num[2] + offsetX, num[3] + offsetY); QPointF e(num[4] + offsetX, num[5] + offsetY); num += 6; count -= 6; path.cubicTo(c1, c2, e); ctrlPt = c2; x = e.x(); y = e.y(); break; } case 'C': { if (count < 6) { num += count; count = 0; break; } QPointF c1(num[0], num[1]); QPointF c2(num[2], num[3]); QPointF e(num[4], num[5]); num += 6; count -= 6; path.cubicTo(c1, c2, e); ctrlPt = c2; x = e.x(); y = e.y(); break; } case 's': { if (count < 4) { num += count; count = 0; break; } QPointF c1; if (lastMode == 'c' || lastMode == 'C' || lastMode == 's' || lastMode == 'S') c1 = QPointF(2 * x - ctrlPt.x(), 2 * y - ctrlPt.y()); else c1 = QPointF(x, y); QPointF c2(num[0] + offsetX, num[1] + offsetY); QPointF e(num[2] + offsetX, num[3] + offsetY); num += 4; count -= 4; path.cubicTo(c1, c2, e); ctrlPt = c2; x = e.x(); y = e.y(); break; } case 'S': { if (count < 4) { num += count; count = 0; break; } QPointF c1; if (lastMode == 'c' || lastMode == 'C' || lastMode == 's' || lastMode == 'S') c1 = QPointF(2 * x - ctrlPt.x(), 2 * y - ctrlPt.y()); else c1 = QPointF(x, y); QPointF c2(num[0], num[1]); QPointF e(num[2], num[3]); num += 4; count -= 4; path.cubicTo(c1, c2, e); ctrlPt = c2; x = e.x(); y = e.y(); break; } case 'q': { if (count < 4) { num += count; count = 0; break; } QPointF c(num[0] + offsetX, num[1] + offsetY); QPointF e(num[2] + offsetX, num[3] + offsetY); num += 4; count -= 4; path.quadTo(c, e); ctrlPt = c; x = e.x(); y = e.y(); break; } case 'Q': { if (count < 4) { num += count; count = 0; break; } QPointF c(num[0], num[1]); QPointF e(num[2], num[3]); num += 4; count -= 4; path.quadTo(c, e); ctrlPt = c; x = e.x(); y = e.y(); break; } case 't': { if (count < 2) { num += count; count = 0; break; } QPointF e(num[0] + offsetX, num[1] + offsetY); num += 2; count -= 2; QPointF c; if (lastMode == 'q' || lastMode == 'Q' || lastMode == 't' || lastMode == 'T') c = QPointF(2 * x - ctrlPt.x(), 2 * y - ctrlPt.y()); else c = QPointF(x, y); path.quadTo(c, e); ctrlPt = c; x = e.x(); y = e.y(); break; } case 'T': { if (count < 2) { num += count; count = 0; break; } QPointF e(num[0], num[1]); num += 2; count -= 2; QPointF c; if (lastMode == 'q' || lastMode == 'Q' || lastMode == 't' || lastMode == 'T') c = QPointF(2 * x - ctrlPt.x(), 2 * y - ctrlPt.y()); else c = QPointF(x, y); path.quadTo(c, e); ctrlPt = c; x = e.x(); y = e.y(); break; } case 'a': { if (count < 7) { num += count; count = 0; break; } qreal rx = (*num++); qreal ry = (*num++); qreal xAxisRotation = (*num++); qreal largeArcFlag = (*num++); qreal sweepFlag = (*num++); qreal ex = (*num++) + offsetX; qreal ey = (*num++) + offsetY; count -= 7; qreal curx = x; qreal cury = y; pathArc( path, rx, ry, xAxisRotation, int(largeArcFlag), int(sweepFlag), ex, ey, curx, cury); x = ex; y = ey; } break; case 'A': { if (count < 7) { num += count; count = 0; break; } qreal rx = (*num++); qreal ry = (*num++); qreal xAxisRotation = (*num++); qreal largeArcFlag = (*num++); qreal sweepFlag = (*num++); qreal ex = (*num++); qreal ey = (*num++); count -= 7; qreal curx = x; qreal cury = y; pathArc( path, rx, ry, xAxisRotation, int(largeArcFlag), int(sweepFlag), ex, ey, curx, cury); x = ex; y = ey; } break; default: return false; } lastMode = pathElem.toLatin1(); } } return true; } SvgHandler::SvgHandler(QGraphicsScene *scene) : m_scene(scene) {} SvgHandler::~SvgHandler() = default; void SvgHandler::load(QXmlStreamReader *data, bool skip_definitions) { m_skipDefinitions = skip_definitions; m_xml = data; m_defaultPen = QPen(Qt::black, 1, Qt::SolidLine, Qt::FlatCap, Qt::SvgMiterJoin); m_defaultPen.setMiterLimit(4); parse(); /* QGraphicsRectItem *it = new QGraphicsRectItem(); it->setRect(m_scene->sceneRect()); it->setPen(QPen(Qt::blue)); m_scene->addItem(it); */ } void SvgHandler::parse() { m_xml->setNamespaceProcessing(false); bool done = false; m_elementStack.push(SvgElement::initial_element()); while (!m_xml->atEnd() && !done) { switch (m_xml->readNext()) { case QXmlStreamReader::StartElement: { SvgElement el(m_xml->name().toString()); if (el.name == QLatin1String("defs")) { if (m_skipDefinitions) { m_xml->skipCurrentElement(); } } el.styleAttributes = m_elementStack.last().styleAttributes; el.xmlAttributes = parseXmlAttributes(m_xml->attributes(), el.styleAttributes); DEBUG() << QString(m_elementStack.count(), '-') << ">" << "+ start element:" << el.name << "id:" << el.xmlAttributes.value("id"); mergeCSSAttributes(el.styleAttributes, QStringLiteral("style"), el.xmlAttributes); m_elementStack.push(el); bool is_item_created = startElement(); m_elementStack.last().itemCreated = is_item_created; break; } case QXmlStreamReader::EndElement: { SvgElement svg_element = m_elementStack.pop(); DEBUG() << QString(m_elementStack.count(), '-') << ">" << "- end element:" << m_xml->name() << "item created:" << svg_element.itemCreated; if (svg_element.itemCreated && m_topLevelItem) { // logSvgI() << "m_topLevelItem:" << m_topLevelItem << typeid // (*m_topLevelItem).name() << svg_element.name; installVisuController(m_topLevelItem, svg_element); m_topLevelItem = m_topLevelItem->parentItem(); } break; } case QXmlStreamReader::Characters: { DEBUG() << "characters element:" << m_xml->text(); // << typeid // (*m_topLevelItem).name(); if (auto *text_item = dynamic_cast(m_topLevelItem)) { QString text = text_item->text(); if (!text.isEmpty()) text += '\n'; DEBUG() << text_item->text() << "+" << m_xml->text().toString(); text_item->setText(text + m_xml->text().toString()); } else if (auto *text_item = dynamic_cast(m_topLevelItem)) { QString text = text_item->toPlainText(); if (!text.isEmpty()) text += '\n'; text += m_xml->text(); text_item->setPlainText(text); // nInfo() << text_item->toPlainText(); } else { DEBUG() << "characters are not part of text item, will be ignored"; // nWarning() << "top:" << m_topLevelItem << (m_topLevelItem? // typeid (*m_topLevelItem).name(): "NULL"); } break; } case QXmlStreamReader::ProcessingInstruction: DEBUG() << "ProcessingInstruction:" << m_xml->processingInstructionTarget() << m_xml->processingInstructionData(); // processingInstruction(xml->processingInstructionTarget().toString(), // xml->processingInstructionData().toString()); break; default: break; } } } bool SvgHandler::startElement() { const SvgElement &el = m_elementStack.last(); if (!m_topLevelItem) { if (el.name == QLatin1String("svg")) { m_topLevelItem = new QGraphicsRectItem(); m_scene->addItem(m_topLevelItem); root = m_topLevelItem; return true; } else { WARN() << "unsupported root element:" << el.name; } return false; } else { if (el.name == QLatin1String("g")) { QGraphicsItem *item = createGroupItem(el); if (item) { setXmlAttributes(item, el); if (auto *rect_item = dynamic_cast(item)) { setStyle(rect_item, el.xmlAttributes); } setTransform(item, el.xmlAttributes.value(QStringLiteral("transform"))); addItem(item); return true; } return false; } else if (el.name == QLatin1String("a")) { QGraphicsItem *item = createHyperlinkItem(el); if (item) { setXmlAttributes(item, el); if (auto *rect_item = dynamic_cast(item)) { setStyle(rect_item, el.xmlAttributes); } setTransform(item, el.xmlAttributes.value(QStringLiteral("transform"))); addItem(item); return true; } return false; } else if (el.name == QLatin1String("rect")) { qreal x = el.xmlAttributes.value(QStringLiteral("x")).toDouble(); qreal y = el.xmlAttributes.value(QStringLiteral("y")).toDouble(); qreal w = el.xmlAttributes.value(QStringLiteral("width")).toDouble(); qreal h = el.xmlAttributes.value(QStringLiteral("height")).toDouble(); if (auto *text_item = dynamic_cast(m_topLevelItem)) { QTransform t; t.translate(x, y); text_item->setTransform(t, true); text_item->setTextWidth(w); return false; } else { auto *item = new QGraphicsRectItem(); setXmlAttributes(item, el); item->setRect(QRectF(x, y, w, h)); setStyle(item, el.styleAttributes); setTransform(item, el.xmlAttributes.value(QStringLiteral("transform"))); addItem(item); return true; } } else if (el.name == QLatin1String("circle")) { auto *item = new QGraphicsEllipseItem(); setXmlAttributes(item, el); qreal cx = toDouble(el.xmlAttributes.value(QStringLiteral("cx"))); qreal cy = toDouble(el.xmlAttributes.value(QStringLiteral("cy"))); qreal rx = toDouble(el.xmlAttributes.value(QStringLiteral("r"))); QRectF r(0, 0, 2 * rx, 2 * rx); r.translate(cx - rx, cy - rx); item->setRect(r); setStyle(item, el.styleAttributes); setTransform(item, el.xmlAttributes.value(QStringLiteral("transform"))); addItem(item); return true; } else if (el.name == QLatin1String("ellipse")) { auto *item = new QGraphicsEllipseItem(); setXmlAttributes(item, el); qreal cx = toDouble(el.xmlAttributes.value(QStringLiteral("cx"))); qreal cy = toDouble(el.xmlAttributes.value(QStringLiteral("cy"))); qreal rx = toDouble(el.xmlAttributes.value(QStringLiteral("rx"))); qreal ry = toDouble(el.xmlAttributes.value(QStringLiteral("ry"))); QRectF r(0, 0, 2 * rx, 2 * ry); r.translate(cx - rx, cy - ry); item->setRect(r); setStyle(item, el.styleAttributes); setTransform(item, el.xmlAttributes.value(QStringLiteral("transform"))); addItem(item); return true; } else if (el.name == QLatin1String("path")) { auto *item = new QGraphicsPathItem(); setXmlAttributes(item, el); QString data = el.xmlAttributes.value(QStringLiteral("d")); QPainterPath p; parsePathDataFast(QStringView(data), p); setStyle(item, el.styleAttributes); static auto FILL_RULE = QStringLiteral("fill-rule"); if (el.styleAttributes.value(FILL_RULE) == QLatin1String("evenodd")) p.setFillRule(Qt::OddEvenFill); else p.setFillRule(Qt::WindingFill); item->setPath(p); setTransform(item, el.xmlAttributes.value(QStringLiteral("transform"))); addItem(item); return true; } else if (el.name == QLatin1String("text")) { auto *item = new SimpleTextItem(el.styleAttributes); setXmlAttributes(item, el); qreal x = toDouble(el.xmlAttributes.value(QStringLiteral("x"))); qreal y = toDouble(el.xmlAttributes.value(QStringLiteral("y"))); // item->setPos(x, y); setStyle(item, el.styleAttributes); setTextStyle(item, el.styleAttributes); setTransform(item, el.xmlAttributes.value(QStringLiteral("transform"))); QFontMetricsF fm(item->font()); QTransform t; t.translate(x, y - fm.ascent()); item->setTransform(t, true); addItem(item); return true; } else if (el.name == QLatin1String("tspan")) { auto *item = new SimpleTextItem(el.styleAttributes); setXmlAttributes(item, el); qreal x = toDouble(el.xmlAttributes.value(QStringLiteral("x"))); qreal y = toDouble(el.xmlAttributes.value(QStringLiteral("y"))); // text->setPos(x, y); setStyle(item, el.styleAttributes); setTextStyle(item, el.styleAttributes); setTransform(item, el.xmlAttributes.value(QStringLiteral("transform"))); QFontMetricsF fm(item->font()); QTransform t; t.translate(x, y - fm.ascent()); item->setTransform(t, true); addItem(item); return true; } else if (el.name == QLatin1String("flowRoot")) { auto *item = new QGraphicsTextItem(); setXmlAttributes(item, el); // nWarning() << "FlowRoot:" << (QGraphicsItem*)item; setTextStyle(item, el.styleAttributes); setTransform(item, el.xmlAttributes.value(QStringLiteral("transform"))); addItem(item); return true; } else { LOG() << "unsupported element:" << el.name; } return false; } } QGraphicsItem *SvgHandler::createGroupItem(const SvgHandler::SvgElement &el) { Q_UNUSED(el) QGraphicsItem *item = new GroupItem(); return item; } QGraphicsItem *SvgHandler::createHyperlinkItem(const SvgHandler::SvgElement &el) { Q_UNUSED(el) QGraphicsItem *item = new HyperlinkItem(); return item; } void SvgHandler::installVisuController(QGraphicsItem *it, const SvgHandler::SvgElement &el) { Q_UNUSED(it) Q_UNUSED(el) } void SvgHandler::setXmlAttributes(QGraphicsItem *git, const SvgHandler::SvgElement &el) { git->setData( static_cast(MetadataType::XmlAttributes), QVariant::fromValue(el.xmlAttributes)); git->setData( static_cast(MetadataType::CssAttributes), QVariant::fromValue(el.styleAttributes)); } void SvgHandler::setTransform(QGraphicsItem *it, const QString &str_val) { QStringView transform(str_val); QTransform mx = parseTransformationMatrix(transform.trimmed()); if (!mx.isIdentity()) { QTransform t(mx); // logSvgI() << typeid (*it).name() << "setting matrix:" << t.dx() << // t.dy(); it->setTransform(t); } } CssAttributes SvgHandler::parseXmlAttributes(const QXmlStreamAttributes &attributes, CssAttributes &css) { XmlAttributes xml; for (const QXmlStreamAttribute &attr : attributes) { const auto name = attr.name().toString(); const auto value = attr.value().toString(); xml[name] = value; /* * """ * The presentation attributes thus will participate in the CSS2 cascade * as if they were replaced by corresponding CSS style rules placed at * the start of the author style sheet with a specificity of zero. In * general, this means that the presentation attributes have lower * priority than other CSS style rules specified in author style sheets * or ‘style’ attributes. * """ * https://www.w3.org/TR/SVG/styling.html#UsingPresentationAttributes */ if (svgspec::presentation_attributes.contains(name)) { css[name] = value; } } return xml; } void SvgHandler::mergeCSSAttributes( CssAttributes &css_attributes, const QString &attr_name, const XmlAttributes &xml_attributes) { #if QT_VERSION < QT_VERSION_CHECK(5, 15, 0) QStringList css = xml_attributes.value(attr_name).split(';', QString::SkipEmptyParts); #else QStringList css = xml_attributes.value(attr_name).split(';', Qt::SkipEmptyParts); #endif for (const QString &ss : css) { int ix = ss.indexOf(':'); if (ix > 0) { css_attributes[ss.mid(0, ix).trimmed()] = ss.mid(ix + 1).trimmed(); } } } void SvgHandler::setStyle(QAbstractGraphicsShapeItem *it, const CssAttributes &attributes) { QString fill = attributes.value(QStringLiteral("fill")); if (fill.isEmpty()) { // default fill } else if (fill == QLatin1String("none")) { it->setBrush(Qt::NoBrush); } else { QString opacity = attributes.value(QStringLiteral("fill-opacity")); it->setBrush(parseColor(fill, opacity)); } QString stroke = attributes.value(QStringLiteral("stroke")); if (stroke.isEmpty() || stroke == QLatin1String("none")) { it->setPen(Qt::NoPen); } else { QString opacity = attributes.value(QStringLiteral("stroke-opacity")); QPen pen(parseColor(stroke, opacity)); pen.setWidthF(toDouble(attributes.value(QStringLiteral("stroke-width")))); QString linecap = attributes.value(QStringLiteral("stroke-linecap")); if (linecap == QLatin1String("round")) pen.setCapStyle(Qt::RoundCap); else if (linecap == QLatin1String("square")) pen.setCapStyle(Qt::SquareCap); else // if(linecap == QLatin1String("butt")) pen.setCapStyle(Qt::FlatCap); QString join = attributes.value(QStringLiteral("stroke-linejoin")); if (join == QLatin1String("round")) pen.setJoinStyle(Qt::RoundJoin); else if (join == QLatin1String("bevel")) pen.setJoinStyle(Qt::BevelJoin); else // if( join == QLatin1String("miter")) pen.setJoinStyle(Qt::MiterJoin); QString dash_pattern = attributes.value(QStringLiteral("stroke-dasharray")); if (!(dash_pattern.isEmpty() || dash_pattern == QLatin1String("none"))) { QStringList array = dash_pattern.split(','); QVector arr; for (const auto &s : array) { bool ok; double d = s.toDouble(&ok); if (!ok) { LOG() << "Invalid stroke dash definition:" << dash_pattern; arr.clear(); break; } arr << d; } if (!arr.isEmpty()) { pen.setDashPattern(arr); } } QString dash_offset = attributes.value(QStringLiteral("stroke-dashoffset")); if (!(dash_offset.isEmpty() || dash_offset == QLatin1String("none"))) { bool ok; double d = dash_offset.toDouble(&ok); if (ok) { pen.setDashOffset(d); } else { LOG() << "Invalid stroke dash offset:" << dash_offset; } } it->setPen(pen); } } void SvgHandler::setTextStyle(QFont &font, const CssAttributes &attributes) { DEBUG() << "orig font" << font.toString(); // font.setStyleName(QString()); font.setStyleName(QStringLiteral("Normal")); QString font_size = attributes.value(QStringLiteral("font-size")); if (!font_size.isEmpty()) { DEBUG() << "font_size:" << font_size; if (font_size.endsWith(QLatin1String("px"))) font.setPixelSize((int)toDouble(font_size.mid(0, font_size.size() - 2))); else if (font_size.endsWith(QLatin1String("pt"))) font.setPointSizeF(toDouble(font_size.mid(0, font_size.size() - 2))); } QString font_family = attributes.value(QStringLiteral("font-family")); if (!font_family.isEmpty()) { DEBUG() << "font_family:" << font_family; font.setFamily(font_family); } font.setWeight(QFont::Normal); QString font_weight = attributes.value(QStringLiteral("font-weight")); if (!font_weight.isEmpty()) { DEBUG() << "font_weight:" << font_weight; if (font_weight == QLatin1String("thin")) font.setWeight(QFont::Thin); else if (font_weight == QLatin1String("light")) font.setWeight(QFont::Light); else if (font_weight == QLatin1String("normal")) font.setWeight(QFont::Normal); else if (font_weight == QLatin1String("medium")) font.setWeight(QFont::Medium); else if (font_weight == QLatin1String("bold")) font.setWeight(QFont::Bold); else if (font_weight == QLatin1String("black")) font.setWeight(QFont::Black); } font.setStretch(QFont::Unstretched); QString font_stretch = attributes.value(QStringLiteral("font-stretch")); if (!font_stretch.isEmpty()) { DEBUG() << "font_stretch:" << font_stretch; if (font_stretch == QLatin1String("ultra-condensed")) font.setStretch(QFont::UltraCondensed); else if (font_stretch == QLatin1String("extra-condensed")) font.setStretch(QFont::ExtraCondensed); else if (font_stretch == QLatin1String("condensed")) font.setStretch(QFont::Condensed); else if (font_stretch == QLatin1String("semi-condensed")) font.setStretch(QFont::SemiCondensed); else if (font_stretch == QLatin1String("semi-expanded")) font.setStretch(QFont::SemiExpanded); else if (font_stretch == QLatin1String("expanded")) font.setStretch(QFont::Expanded); else if (font_stretch == QLatin1String("extra-expanded")) font.setStretch(QFont::ExtraExpanded); else if (font_stretch == QLatin1String("ultra-expanded")) font.setStretch(QFont::UltraExpanded); else // if(font_stretch == QLatin1String("normal")) font.setStretch(QFont::Unstretched); } font.setStyle(QFont::StyleNormal); QString font_style = attributes.value(QStringLiteral("font-style")); if (!font_style.isEmpty()) { if (font_style == QLatin1String("normal")) font.setStyle(QFont::StyleNormal); else if (font_style == QLatin1String("italic")) font.setStyle(QFont::StyleItalic); else if (font_style == QLatin1String("oblique")) font.setStyle(QFont::StyleOblique); } DEBUG() << "font" << "px size:" << font.pixelSize() << "pt size:" << font.pointSize() << "stretch:" << font.stretch() << "weight:" << font.weight(); DEBUG() << "new font" << font.toString(); } void SvgHandler::setTextStyle(QGraphicsSimpleTextItem *text, const CssAttributes &attributes) { QFont f = text->font(); setTextStyle(f, attributes); text->setFont(f); } void SvgHandler::setTextStyle(QGraphicsTextItem *text, const CssAttributes &attributes) { QFont f = text->font(); setTextStyle(f, attributes); text->setFont(f); static auto FILL = QStringLiteral("fill"); QString fill = attributes.value(FILL); if (fill.isEmpty() || fill == QLatin1String("none")) { // it->setBrush(Qt::NoBrush); } else { static auto FILL_OPACITY = QStringLiteral("fill-opacity"); QString opacity = attributes.value(FILL_OPACITY); text->setDefaultTextColor(parseColor(fill, opacity)); } } QString SvgHandler::point2str(QPointF r) { auto ret = QStringLiteral("Point(%1, %2)"); return ret.arg(r.x()).arg(r.y()); } QString SvgHandler::rect2str(QRectF r) { auto ret = QStringLiteral("Rect(%1, %2 %3 x %4)"); return ret.arg(r.x()).arg(r.y()).arg(r.width()).arg(r.height()); } void SvgHandler::addItem(QGraphicsItem *it) { if (!m_topLevelItem) return; DEBUG() << "adding item:" << typeid(*it).name() << "pos:" << point2str(it->pos()) << "bounding rect:" << rect2str(it->boundingRect()); if (auto *grp = dynamic_cast(m_topLevelItem)) { grp->addToGroup(it); } else { it->setParentItem(m_topLevelItem); } m_topLevelItem = it; } SvgDocument SvgHandler::getDocument() const { return SvgDocument(root); } SvgHandler::SvgElement SvgHandler::SvgElement::initial_element() { auto el = SvgHandler::SvgElement(); el.styleAttributes = { // TODO Choose some reasonable set. { "stroke-width", "1" }, }; return el; } } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/svghandler.h ================================================ #pragma once #include "svgdocument.h" #include "svgmetadata.h" #include #include #include #include #include class QXmlStreamReader; class QXmlStreamAttributes; class QGraphicsScene; class QGraphicsItem; class QGraphicsSimpleTextItem; class QGraphicsTextItem; class QAbstractGraphicsShapeItem; namespace svgscene { SvgDocument parseFromFileName(QGraphicsScene *scene, const QString &filename); SvgDocument parseFromFile(QGraphicsScene *scene, QFile *file); class SvgHandler { public: struct SvgElement { QString name; XmlAttributes xmlAttributes; CssAttributes styleAttributes; bool itemCreated = false; SvgElement() = default; explicit SvgElement(QString n, bool created = false) : name(std::move(n)) , itemCreated(created) {} static SvgElement initial_element(); }; public: explicit SvgHandler(QGraphicsScene *scene); virtual ~SvgHandler(); void load(QXmlStreamReader *data, bool is_skip_definitions = false); static QString point2str(QPointF r); static QString rect2str(QRectF r); SvgDocument getDocument() const; protected: virtual QGraphicsItem *createGroupItem(const SvgElement &el); QGraphicsItem *createHyperlinkItem(const SvgElement &el); virtual void installVisuController(QGraphicsItem *it, const SvgElement &el); virtual void setXmlAttributes(QGraphicsItem *git, const SvgElement &el); QGraphicsScene *m_scene; private: void parse(); static CssAttributes parseXmlAttributes(const QXmlStreamAttributes &attributes, CssAttributes &css); static void mergeCSSAttributes( CssAttributes &css_attributes, const QString &attr_name, const XmlAttributes &xml_attributes); static void setTransform(QGraphicsItem *it, const QString &str_val); static void setStyle(QAbstractGraphicsShapeItem *it, const CssAttributes &attributes); static void setTextStyle(QFont &font, const CssAttributes &attributes); static void setTextStyle(QGraphicsSimpleTextItem *text, const CssAttributes &attributes); static void setTextStyle(QGraphicsTextItem *text, const CssAttributes &attributes); bool startElement(); void addItem(QGraphicsItem *it); private: QGraphicsItem *root = nullptr; QStack m_elementStack; QGraphicsItem *m_topLevelItem = nullptr; QXmlStreamReader *m_xml = nullptr; QPen m_defaultPen; bool m_skipDefinitions = false; }; } // namespace svgscene Q_DECLARE_METATYPE(svgscene::XmlAttributes) ================================================ FILE: external/svgscene/src/svgscene/svgmetadata.cpp ================================================ #include "svgmetadata.h" namespace svgscene { XmlAttributes getXmlAttributes(const QGraphicsItem *element) { QVariant raw = element->data(static_cast(MetadataType::XmlAttributes)); if (!raw.isValid() || !raw.canConvert()) { throw std::out_of_range("XmlAttributes not present in the object.\n" "Check whether the object was created by the svgscene parser."); } return qvariant_cast(raw); } QString getXmlAttribute(const QGraphicsItem *element, const QString &name) { XmlAttributes attrs = getXmlAttributes(element); if (!attrs.contains(name)) { throw std::out_of_range("Element does not contain requested XML attribute."); } return attrs.value(name); } QString getXmlAttributeOr( const QGraphicsItem *element, const QString &name, const QString &defaultValue) noexcept { XmlAttributes attrs = getXmlAttributes(element); return attrs.value(name, defaultValue); } CssAttributes getCssAttributes(const QGraphicsItem *element) { QVariant raw = element->data(static_cast(MetadataType::CssAttributes)); if (!raw.isValid() || !raw.canConvert()) { throw std::out_of_range("CssAttributes not present in the object.\n" "Check whether the object was created by the svgscene parser."); } return qvariant_cast(raw); } QString getCssAttribute(const QGraphicsItem *element, const QString &name) { CssAttributes attrs = getCssAttributes(element); if (!attrs.contains(name)) { throw std::out_of_range("Element does not contain requested XML attribute."); } return attrs.value(name); } QString getCssAttributeOr( const QGraphicsItem *element, const QString &name, const QString &defaultValue) noexcept { CssAttributes attrs = getCssAttributes(element); return attrs.value(name, defaultValue); } } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/svgmetadata.h ================================================ #pragma once #include #include #include namespace svgscene { /* * Fields that can be found on SVG related `QGraphicsItem`s using the `data` * method. * Data are stored using QVariant. */ enum class MetadataType { XmlAttributes = 1, CssAttributes, }; constexpr MetadataType LAST_VALUE = MetadataType::CssAttributes; using XmlAttributes = QMap; XmlAttributes getXmlAttributes(const QGraphicsItem *element); QString getXmlAttribute(const QGraphicsItem *element, const QString &name); QString getXmlAttributeOr( const QGraphicsItem *element, const QString &name, const QString &defaultValue) noexcept; using CssAttributes = QMap; CssAttributes getCssAttributes(const QGraphicsItem *element); QString getCssAttribute(const QGraphicsItem *element, const QString &name); QString getCssAttributeOr( const QGraphicsItem *element, const QString &name, const QString &defaultValue) noexcept; } // namespace svgscene ================================================ FILE: external/svgscene/src/svgscene/svgspec.h ================================================ #ifndef SVGSCENE_SVG_SPEC_H #define SVGSCENE_SVG_SPEC_H #include namespace svgspec { /** * https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute */ static const QSet presentation_attributes { "alignment-baseline", "baseline-shift", "clip", "clip-path", "clip-rule", "color", "color-interpolation", "color-interpolation-filters", "color-profile", "color-rendering", "cursor", "direction", "display", "dominant-baseline", "enable-background", "fill", "fill-opacity", "fill-rule", "filter", "flood-color", "flood-opacity", "font-family", "font-size", "font-size-adjust", "font-stretch", "font-style", "font-variant", "font-weight", "glyph-orientation-horizontal", "glyph-orientation-vertical", "image-rendering", "kerning", "letter-spacing", "lighting-color", "marker-end", "marker-mid", "marker-start", "mask", "opacity", "overflow", "pointer-events", "shape-rendering", "stop-color", "stop-opacity", "stroke", "stroke-dasharray", "stroke-dashoffset", "stroke-linecap", "stroke-linejoin", "stroke-miterlimit", "stroke-opacity", "stroke-width", "text-anchor", "text-decoration", "text-rendering", "transform", "transform-origin", "unicode-bidi", "vector-effect", "visibility", "word-spacing", "writing-mode", }; } // namespace svgspec #endif // SVGSCENE_SVG_SPEC_H ================================================ FILE: external/svgscene/src/svgscene/utils/logging.h ================================================ #pragma once /** * Wrapper for QT logging library. * * Each source file is expected to declare a log category name what is * implicitly used for all logging macros. When logging in header files take * precaution not to pollute global scope. Either log manually or declare the * log within class scope. * Log categories can be structured using dots in name: `machine.core * .decode`. * * @see * https://www.kdab.com/wp-content/uploads/stories/slides/Day2/KaiKoehne_Qt%20Logging%20Framework%2016_9_0.pdf */ #include #define LOG_CATEGORY(NAME) static QLoggingCategory _loging_category_(NAME) #if !defined(QT_NO_QDEBUG_MACRO) #define QT_NO_QDEBUG_MACRO \ while (false) \ QMessageLogger().noDebug #endif #if defined(QT_NO_DEBUG_OUTPUT) #define DEBUG QT_NO_QDEBUG_MACRO #else #define DEBUG(...) qCDebug(_loging_category_, __VA_ARGS__) #endif #define LOG(...) qCInfo(_loging_category_, __VA_ARGS__) #define WARN(...) qCWarning(_loging_category_, __VA_ARGS__) #define ERROR(...) qCCritical(_loging_category_, __VA_ARGS__) ================================================ FILE: external/svgscene/src/svgscene/utils/memory_ownership.h ================================================ /** * Manual memory ownership toolkit. */ #pragma once #include /** * Tag for pointer owned by someone else. It is recommended to mention owner * in comment to make lifetimes manually verifiable. */ #define BORROWED /** * Pointer with static lifetime. */ #define STATIC /** * Owned pointer deallocated by automatic destructor. */ template using Box = QScopedPointer; ================================================ FILE: extras/building/build-wasm.sh ================================================ #!/usr/bin/env bash set -eu EMSCRIPTEN_VERSION=$1 QT_WASM=$2 EMSDK_PATH=$3 # If you know how to do this better, please, let me know. [ "$(stat -c '%a' "$EMSDK_PATH")" -ne 777 ] && sudo chmod 777 -R $EMSDK_PATH emsdk install $EMSCRIPTEN_VERSION emsdk activate $EMSCRIPTEN_VERSION source $EMSDK_PATH/emsdk_env.sh mkdir -p build/wasm pushd build/wasm emcmake cmake ../.. \ -DCMAKE_BUILD_TYPE=Release \ -DCMAKE_PREFIX_PATH="$QT_WASM" \ -DCMAKE_FIND_ROOT_PATH="$QT_WASM" \ -Wno-dev -G Ninja ninja popd ================================================ FILE: extras/core_graphics/diagram.drawio ================================================ ================================================ FILE: extras/crosscompiling/shell-mips-elf.nix ================================================ # Provides shell with a crosscompiler for code to simulate with import { crossSystem = { config = "mips-elf"; }; }; mkShell { buildInputs = [ ]; # your dependencies here } ================================================ FILE: extras/crosscompiling/shell-riscv-elf-with-newlib.nix ================================================ # Provides shell with a crosscompiler for code to simulate with import { crossSystem = { config = "riscv32-none-elf"; libc = "newlib"; gcc.arch = "rv32g"; }; }; mkShell { buildInputs = [ ]; # your dependencies here } ================================================ FILE: extras/crosscompiling/shell-riscv-elf.nix ================================================ # Provides shell with a crosscompiler for code to simulate with import {}; pkgsCross.riscv64-embedded.mkShell {} ================================================ FILE: extras/packaging/_tools/add-to-changelog.sh ================================================ #!/usr/bin/env bash CHANGELOG_FILE="extras/packaging/deb/debian/changelog" if [ $# -lt 1 ]; then echo "Version has to be specified" exit 1 fi V_TXT="$1" cd "$(dirname "$0")/../.." V_DATE_MDY="$(date '+%m/%d/%Y')" V_DATE_YMD="$(date '+%Y-%m-%d')" V_DATE_RFC="$(date -R)" V_USER_NAME="$(git config user.name)" V_USER_EMAIL="$(git config user.email)" if grep -q "qtrvsim ($V_TXT)" $CHANGELOG_FILE; then sed --in-place \ -e '1,/^ -- .*$/s/^ -- .*$/'" -- $V_USER_NAME <$V_USER_EMAIL> $V_DATE_RFC/" \ $CHANGELOG_FILE else cat >$CHANGELOG_FILE.tmp < $V_DATE_RFC EOF cat $CHANGELOG_FILE >> $CHANGELOG_FILE.tmp mv $CHANGELOG_FILE.tmp $CHANGELOG_FILE fi $EDITOR $CHANGELOG_FILE echo Press enter to continue read x git add $CHANGELOG_FILE echo >.git/GITGUI_MSG "Version updated to $V_TXT" git gui git tag -d v$V_TXT git tag -s v$V_TXT # TODO #if [ -x /usr/lib/obs-build/changelog2spec ]; then # /usr/lib/obs-build/changelog2spec debian/changelog >../qtrvsim.changes #elif [ -x /usr/lib/build/changelog2spec ]; then # /usr/lib/build/changelog2spec debian/changelog >../qtrvsim.changes #fi ================================================ FILE: extras/packaging/_tools/git-archive-submodules.sh ================================================ #!/usr/bin/env bash set -eu # Tool to crate source archive including submodules # based on (MIT): https://github.com/nzanepro/git-archive-submodules # Parameters: project-root destination file_name module-prefix version [git-command] [tar-command] [xz-command] export TMPDIR=`realpath ${2}`/pack/ export SOURCE=`realpath ${1}` export DESTINATION=`realpath ${2}/${3}` export TARMODULE=${4} export TARVERSION=${5} export TARPREFIX="${TARMODULE}-${TARVERSION}" export GIT=${6:-"git"} export TAR=${7:-"tar"} export XZ=${8:-"xz"} mkdir -p ${TMPDIR} pushd ${SOURCE} || exit # create module archive ${GIT} archive --prefix=${TARPREFIX}/ -o ${TMPDIR}${TARPREFIX}.tar HEAD if [[ ! -f "${TMPDIR}${TARPREFIX}.tar" ]]; then echo "ERROR: base sourcecode archive was not created. check git output in log above." exit 1 fi # Makefile is only helper for development and should be excluded in release. # The command is allowed to fail if Makefile is not found. ${TAR} -f "${TMPDIR}${TARPREFIX}.tar" --delete ${TARPREFIX}/Makefile 2>/dev/null || true # force init submodules ${GIT} submodule update --init --recursive # tar each submodule recursively ${GIT} submodule foreach --recursive 'git archive --prefix=${TARPREFIX}/${displaypath}/ HEAD > ${TMPDIR}tmp.tar && ${TAR} --concatenate --file=${TMPDIR}${TARPREFIX}.tar ${TMPDIR}tmp.tar' popd || exit # compress tar file ${XZ} ${TMPDIR}${TARPREFIX}.tar if [[ ! -f "${TMPDIR}${TARPREFIX}.tar.xz" ]]; then echo "ERROR: xzipped archive was not created. check git output in log above." exit 1 fi cp ${TMPDIR}${TARPREFIX}.tar.xz ${DESTINATION} if [[ -f "${TMPDIR}${TARPREFIX}.tar.xz" ]]; then rm ${TMPDIR}${TARPREFIX}.tar.xz echo "created ${DESTINATION}" else echo "ERROR copying ${TMPDIR}${TARPREFIX}.tar.xz to ${DESTINATION}" usage exit 1 fi # cleanup rm -r ${TMPDIR} ================================================ FILE: extras/packaging/add-to-changelog.sh ================================================ #!/usr/bin/env bash CHANGELOG_FILE="extras/packaging/deb/debian/changelog" MAIN_PROJECT_NAME_LOWER="qtrvsim" if false ; then CMAKELISTS_DIR="$( old_pwd="" ; while [ ! -e CMakeLists.txt ] ; do if [ "$old_pwd" = `pwd` ] ; then exit 1 ; else old_pwd="$(pwd)" ; cd -L .. 2>/dev/null ; fi ; done ; pwd )" if [ $? -ne 0 ] ; then echo 'Can not found top level project directory with "CMakeLists.txt" file' exit 1 fi cd "$CMAKELISTS_DIR" else cd "$(dirname "$0")/../.." CMAKELISTS_DIR="$(pwd)" fi if [ $# -ge 1 ]; then V_TXT="$1" else V_TXT="$(sed -n '/^[ \t]*project *(/{s///; :1; /) *$/!{N; b1;}; s///; s/^.*VERSION[\t ]\+\([^ ]*\)\b.*$/\1/p};' "$CMAKELISTS_DIR/CMakeLists.txt")" fi if [ -z "$V_TXT" ] ; then echo 'Version is not specified as argument neither determined from "CMakeLists.txt" file' exit 1 fi V_TXT_W_SUF="$( echo "$V_TXT" | sed -n 's/\(.*-.*\)/\1/p')" if [ -z "$V_TXT_W_SUF" ] ; then V_TXT_W_SUF="$V_TXT-1" fi V_DATE_MDY="$(date '+%m/%d/%Y')" V_DATE_YMD="$(date '+%Y-%m-%d')" V_DATE_RFC="$(date -R)" V_USER_NAME="$(git config user.name)" V_USER_EMAIL="$(git config user.email)" if grep -q "$MAIN_PROJECT_NAME_LOWER ($V_TXT_W_SUF)" $CHANGELOG_FILE; then sed --in-place \ -e '1,/^ -- .*$/s/^ -- .*$/'" -- $V_USER_NAME <$V_USER_EMAIL> $V_DATE_RFC/" \ $CHANGELOG_FILE else cat >$CHANGELOG_FILE.tmp < $V_DATE_RFC EOF cat $CHANGELOG_FILE >> $CHANGELOG_FILE.tmp mv $CHANGELOG_FILE.tmp $CHANGELOG_FILE fi if [ -z "$EDITOR" ] ; then EDITOR=nano fi $EDITOR $CHANGELOG_FILE echo Press enter to continue read x git add $CHANGELOG_FILE echo >.git/GITGUI_MSG "Version updated to $V_TXT" git gui git tag -d v$V_TXT git tag -s v$V_TXT # TODO #if [ -x /usr/lib/obs-build/changelog2spec ]; then # /usr/lib/obs-build/changelog2spec debian/changelog >../$MAIN_PROJECT_NAME_LOWER.changes #elif [ -x /usr/lib/build/changelog2spec ]; then # /usr/lib/build/changelog2spec debian/changelog >../$MAIN_PROJECT_NAME_LOWER.changes #fi ================================================ FILE: extras/packaging/appimage/appimage.yml.in ================================================ app: @PACKAGE_NAME@ build: packages: - linuxdeployqt - pkgconfig(Qt5Core) - pkgconfig(Qt5Widgets) - pkgconfig(Qt5Test) - pkgconfig(Qt5PrintSupport) - libelf-devel script: - cd $BUILD_SOURCE_DIR - tar xf @PACKAGE_SOURCE_ARCHIVE_FILE@ - cd @PACKAGE_TOPLEVEL_DIR@ - mkdir build - cd build - export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) - cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr - cmake --build . - DESTDIR="$BUILD_APPDIR" cmake --install . - unset QTDIR; unset QT_PLUGIN_PATH; unset LD_LIBRARY_PATH - linuxdeployqt $BUILD_APPDIR/usr/share/applications/*.desktop -bundle-non-qt-libs -verbose=2 -no-strip ================================================ FILE: extras/packaging/arch/PKGBUILD.in ================================================ # Maintainer: @PACKAGE_MAINTAINER@ pkgname=@PACKAGE_NAME@ pkgver=@PROJECT_VERSION@ pkgrel=1 pkgdesc="@PACKAGE_DESCRIPTION@" arch=("any") url="@PACKAGE_URL@" license=('@PACKAGE_LICENCE@') depends=("qt5-base") makedepends=("cmake" "elfutils") source=("@PACKAGE_SOURCE_ARCHIVE_FILE@") md5sums=("@FILE_MD5@") prepare() { export CMAKE_BUILD_PARALLEL_LEVEL=$(nproc) cd "$srcdir/@PACKAGE_TOPLEVEL_DIR@" mkdir build cd build cmake .. -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/usr } build() { cd "$srcdir/@PACKAGE_TOPLEVEL_DIR@/build" cmake --build . } package() { cd "$srcdir/@PACKAGE_TOPLEVEL_DIR@/build" DESTDIR="$pkgdir" cmake --install . } ================================================ FILE: extras/packaging/deb/debian/changelog ================================================ qtrvsim (0.9.8-1) unstable; urgency=medium * Machine: aclintmtimer fix count type * GUI: fix a crash on nonexistent include * Use win32 config of libelf when compiling natively for Windows * CI: Add Windows Clang debug build and macos ARM * CLI: reporter dump to json * Machine: instruction parsing refactor * GUI: make printer settings persistent and scale to fit PDF page size * Assembler: fix immediate parsing * Assembler: implement GAS modifiers - PC rel still basic only * Machine: fix zext.w/h inst parse and fix tokenized for inst.xxx * Machine: fix parse_csr_address and CSR::RegisterMapByName key type * Machine and GUI: Pseudo LRU cache policy * Add 25x speed for teaching convenience * Machien and GUI: Include Jiri Stefan's work on branch predictor * Machien and GUI: BTB, BHT and BHR are implemented * Project: Explicit cmake qt major version option * Packaging: add Keywords entry into desktop file * Machine: add peripherals high/top address aliases for XLEN=64 * GUI: switch "New" dialog page selection to tree widget, polishing required -- Pavel Pisa Tue, 01 Oct 2024 20:45:22 +0200 qtrvsim (0.9.7-1) unstable; urgency=medium * GUI: fix examples button crashes on Windows and sometimes other platforms * GUI: fix crash when no tab is selected * CI: add windows libs to artifacts and use more cores available * CI: Make pack QT with Win artifact * Project: Use the include directory for LibElf on macOS, Homebrew misses RISC-V * Machine: set SXL and UXL to 64-bit XLEN for RV64 -- Pavel Pisa Thu, 15 Feb 2024 20:57:12 +0100 qtrvsim (0.9.6-1) unstable; urgency=medium * GUI: add reset widows menu entry to restore default windows layout * Machine: extend CSR support to pass rv32mi-p-mcsr and rv64mi-p-mcsr official test * Machine: serial port interrupts reworked for RISC-V as platform irq 16 and 17 * GUI: RISC-V ACLINT MTIMER mapping added into resources/samples/template.S * Machine: implemented RISC-V A extension for RV32IMA/RV64IMA support * GUI: the XLEN, atomic and multiply options available in new simulation dialog * GUI: update registers and CSR views for bare RV64IMA support * Machine and GUI: simple level 2 cache implementation * GUI: increase cache set count limit to 1024 * CLI: add isa-variant, cycle-limit and l2-cache options * CLI: dump-ranges allows to use symbols even from internal assembly * Memory: correctly propagate external/DMA changes to GUI * Machine: where possible, re-implement pseudo instructions by aliase tables * os_emulation: resolve problem with write and read from/to stack area on RV32 * GUI: fix double free of children widgets in control register widget * GUI: refactor gui source file to tree structure * GUI: program view - collapse address and breakpoint if space is limited * GUI: split central widget tabs to coreview and editor * GUI: editor line numbers and highlight error in the editor on message click * GUI: editor toggle comment (ctrl+/) * GUI: ensure that all lines of external make process output are processed * os_emulation: correct ftruncate syscall arguments for 64 and 32-bit ABI * Update README.md to document interrupt, trap, ACLINT+MTIMER and AMO support * CI: drop support for Ubuntu 18 * Project: bump to c++17 -- Pavel Pisa Mon, 11 Dec 2023 11:12:15 +0100 qtrvsim (0.9.5-1) unstable; urgency=medium * Machine: use cvector in instruction args to spedup decoding * Machine: move controlstate to csr and prepare BitArg to be usd there * Machine: use method for CSR writes to enable mutual register dependencies * Machine: CSR: define mie register. * Machine: CSR: fix conditions for register write and add mie to the list. * Machine: fix range for branch instructions B-encoding * CLI: add simple tracer for memory reads and writes. * CLI: initial support to enable OS emulation even for CLI version * GUI: update coreview graphics by Michal Stepanosvky * GUI: fix wrong svg label connection and reset all to zero -- Pavel Pisa Mon, 16 Jan 2023 11:22:08 +0200 qtrvsim (0.9.4-1) unstable; urgency=medium * GUI: Async modal library to overcome WebAssembly/Emscripten limitations * Wasm: support and build improved * os_emulation: correct open flags O_xxx values to match RISC-V Linux ABI. * packaging: fix Fedora build according to Jan Grulich advice. * README.md: add reference to Embedded World Conference 2022 article. * qtrvsim_tester: Tomas Veznik implemented testing against official RISC/V ISA tests. * CI: speedup by using common build of official tests * Machine: initial support for CSR instructions by Jakub Dupak * GUI: CSR: syntax highlight CSR reg names * Machine: CSR: disassemble CSR register based on the mnemonic register settings * GUI: save mnemonic registers settings * Machine: add support for 64-bit RV64IM target and related 32-bit/word limited instructions * README.md: update information about basic 64-bit support. -- Pavel Pisa Mon, 24 Oct 2022 23:07:19 +0200 qtrvsim (0.9.3-1) unstable; urgency=medium * Debian package updated to version 0.9.3. * Machine: fix LCD display endianness. * Machine: correct memory stall cycles computation. * Machine: correct unaligned and partial (lb, sb, lh, sh) to peripheral registers. * Packaging: flatpak support kindly provided by David Heidelberg * Machine and GUI: switch to RISC-V CSR names and remove references to MIPS COP0. * Machine: correct parsing of registers s10 and s11 names. * Machine: fix null pointer usage in cache * GUI: fix null pointer usage in cache * Machine: correct cache graphics visualization for byte accesses. * Machine: LFU cache policy incorrect use of sets count instead of degree of associativity. -- Pavel Pisa Thu, 21 Apr 2022 09:40:37 +0200 qtrvsim (0.9.2-1) unstable; urgency=medium * Debian package updated to version 0.9.2. * GUI: rework of coreview graphics to correspond to Mr. Stepanovsky slides * CI: downgrade runner os to win2019 to prevent failing builds * WASM: fix exception support * LRU cache policy fix (check was incoorect only for flush, i.e. fence instruction) * Machine: basic RV32M support (no specific GUI provided, considered as part of ALU) * README.md update, document RV32M support * Machine: RISC-V ABI places stack pointer into x2 register. * Machine: rewrite all core_alu_forward and complex memory tests for RISC-V. * Machine: RISC-V is by default little endian, even when ELF file is not loaded -- Pavel Pisa Tue, 12 Mar 2022 20:45:12 +0200 qtrvsim (0.9.1-1) unstable; urgency=medium * Debian package updated to version 0.9.1. * Recognize FENCE and FENCE.I instructions, they flush whole cache for now. * Corrected register numbers visualization. * Corrected negative numbers in disassembler. * Bugfixes and cleanup. -- Pavel Pisa Tue, 1 Mar 2022 19:15:22 +0200 qtrvsim (0.9.0-1) unstable; urgency=medium * Debian package updated to version 0.9.0. * JALR. * Basic pseudoinstruction support. * Partial recreation of testing. * Bugfixes and cleanup. -- Jakub Dupak Tue, 15 Feb 2022 03:32:50 +0200 qtrvsim (0.8.1-1) unstable; urgency=medium * Debian package updated to version 0.8.1-1. * Fixes visualization of stalls. * Moves jump evaluation from EX to MEM as initially intended and as shown in the cpu diagram. * Fixes WASM build - keyboard handling. * Adds Qt6 support. -- Jakub Dupak Wed, 15 Dec 2021 18:55:55 +0100 qtrvsim (0.8.0) unstable; urgency=medium * Debian package updated to version 0.8.0. -- Jakub Dupak Mon, 17 May 2021 15:27:10 +0200 qtrvsim (0.8.0-1) unstable; urgency=medium * Debian package updated to version 0.8.0. * Switch to RISC-V simulator -- Jakub Dupak Tue, 04 May 2021 23:01:01 +0200 qtmips (0.7.5) unstable; urgency=medium * Debian package updated to version 0.7.5. * Improve UI compatbility with dark color schemes (by Michal Maly, MadCatX) * prepare-release: document alternative Debian package preparation in quilt mode. * Enable dock widows nesting to enable show cache + memory simultaneously (by Frantisek Vacek) * AppImage build run (by Frantisek Vacek) * qtmips_cli: add option to connect serial port input and output to file. * samples: template-os.S template.S fixed SPILED_REG_BASE definition. * README: add documentation how to modify comprocessor 0 regs (by Jan Kaisrlik) * EADME.md: fix reference to uart rx interrupt mask (by Jan Kaisrlik) * Fix open of ELF files using national alphabets in the path names on Windows. * qtmips_gui: fix response to user decision to save modified file when make is invoked. -- Pavel Pisa Sun, 06 Sep 2020 13:16:34 +0200 qtmips (0.7.4) unstable; urgency=medium * Debian package updated to version 0.7.4. * debian packaging: add git attribute to merge changelog. * debian packaging: add watch file for GitHub QtMips repository. -- Pavel Pisa Sun, 15 Sep 2019 20:32:41 +0200 qtmips (0.7.3) unstable; urgency=medium * Debian package updated to version 0.7.3. * Implemented SKIP/SPACE assembler directives. * Add OpenHub statistic page link. * Provide support for include directive in simple assembler. * In include, use content from editor if file is already open. * Add #pragma processing to integrated assembler and its usage to control windows. * Use #pragma in examples to lower initial learning curve. * samples: simple-lw-sw-ia.S: place data section to 0x2000 address. -- Pavel Pisa Thu, 12 Sep 2019 11:47:46 +0200 qtmips (0.7.2) unstable; urgency=medium * Debian package updated to version 0.7.2. * More changes to use dialog open instead of exec (Emscripten does not support exec). * Updates to RPM packaging and spec file to better follow Suse rules. * Move fixmatheval and assembly compiler to separate library independent on Qt GUI API. * Implemented message window to report compilation errors. * Store operator text description in the fixmatheval operators tree. * Simple assembler moved to separate class which is independent on Qt GUI API. * Do not open editor twice for same filename. * Enable CLI version to use simple assembler. * Update editor search algorithm to prefer current editor for unnamed buffers. * Add config option to reset machine before internal assembler starts. * Include C language syntax highlighter for alternative sources editing. * Action to execute external make command and ask for unsaved sources. * Ask to save modified files on exit. * Ask for modified source close and handle unnamed sources close. * Replace shortcuts to not hide Ctrl+C and some others. * Implemented ASCII and ASCIZ operations. * Include actions New source and Close source on toolbar. * Make program and memory window visible when address requested from symbol dialog. * Add embedded examples menu and resources. -- Pavel Pisa Wed, 21 Aug 2019 00:13:06 +0200 qtmips (0.7.1) unstable; urgency=medium * Debian package updated to version 0.7.1. * Add option to parse and show symbolic registers names. * Implemented simple integrated assembler - it is not recommended for advanced users. * Updated instructions parsing to be usable for integrated assembler. * Change instruction parsing to allow multiple words pseudo-operations. * Implemented simple expressions and labels/symbols evaluation. * Simple highlighter for assembly language added. * Include simple text editor in QtMips emulator. * Fix memory leakages which repeat during program operation. * Externally caused address-space changes (i.e. from peripherals) update memory view. * Provide option to hide coreview to speedup simulation. -- Pavel Pisa Wed, 03 Jul 2019 12:18:15 +0200 qtmips (0.7.0) unstable; urgency=medium * Debian package updated to version 0.7.0. * Include simple LCD frame-buffer and display implementation. * Simulate push of dial buttons by check box. * Allow to create simulator without loaded executable. * Printing/export to PDF file reduces print area/page to actual image size. * Disable text elide for memory and program views (fix for MAC OS). * Implement standard zoom handling by mouse wheel and keys. -- Pavel Pisa Fri, 28 Jun 2019 15:38:52 +0200 qtmips (0.6.8) unstable; urgency=medium * Debian package updated to version 0.6.8. * Coreview multiplexers updated and added for branch compare forward. * qtmips_gui: set application window icon. * Set gray background to stalled instructions/idled stages. * Setting background color dial * qtmips_cli: start report in decimal mode. -- Pavel Pisa Mon, 01 Apr 2019 15:26:46 +0200 qtmips (0.6.7) unstable; urgency=medium * Debian package updated to version 0.6.7. * Change single cycle core with delay slot to use separate fetch stage. * Program listing and stages use color background. * Correct write through spelling. Reported by Richard Susta. * qtmips_cli can be used for cache statistic and result memory tests. -- Pavel Pisa Wed, 27 Mar 2019 00:38:24 +0100 qtmips (0.6.6) unstable; urgency=medium * Corrected row and column output in cache address fields. * Highlight cache read and write acesses. * Highlight registers and coprocessor 0 read and writes. -- Pavel Pisa Sun, 17 Mar 2019 20:51:55 +0100 qtmips (0.6.5) unstable; urgency=medium * Initial Debian packaging. -- Pavel Pisa Wed, 13 Mar 2019 19:38:33 +0100 ================================================ FILE: extras/packaging/deb/debian/compat ================================================ 9 ================================================ FILE: extras/packaging/deb/debian/control.in ================================================ Source: @PACKAGE_NAME@ Section: devel Priority: optional Maintainer: @PACKAGE_MAINTAINER@ Build-Depends: debhelper (>= 9), qtbase5-dev, libelf-dev, cmake Standards-Version: 3.9.8 Homepage: @PACKAGE_URL@ Vcs-Git: @PACKAGE_GIT@ Vcs-Browser: @PACKAGE_URL@ Package: @PACKAGE_NAME@ Architecture: any Depends: ${shlibs:Depends}, ${misc:Depends} Description: @PACKAGE_DESCRIPTION@ @PACKAGE_LONG_DESCRIPTION@ ================================================ FILE: extras/packaging/deb/debian/copyright ================================================ Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: qtrvsim Source: https://github.com/cvut/qtrvsim/ Files: * Copyright: 2017-2019 Karel Koci 2019-2022 Pavel Pisa 2020-2022 Jakub Dupak 2020-2021 Max Hollmann License: GPL-3.0+ This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. . This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. . You should have received a copy of the GNU General Public License along with this program. If not, see . ================================================ FILE: extras/packaging/deb/debian/docs ================================================ README.md docs ================================================ FILE: extras/packaging/deb/debian/rules ================================================ #!/usr/bin/make -f # -*- makefile -*- export DH_VERBOSE = 1 export DEB_BUILD_MAINT_OPTIONS=hardening=+all export DEB_LDFLAGS_MAINT_APPEND=-Wl,--as-needed export QT_SELECT := qt5 # --parallel %: dh $@ --buildsystem=cmake ================================================ FILE: extras/packaging/deb/debian/source/format ================================================ 3.0 (quilt) ================================================ FILE: extras/packaging/deb/dsc.in ================================================ Format: 3.0 (quilt) Source: @PACKAGE_NAME@ Binary: @PACKAGE_NAME@ Architecture: any Version: @PACKAGE_VERSION@-@PACKAGE_RELEASE@ Maintainer: @PACKAGE_MAINTAINER@ Homepage: @PACKAGE_URL@ Standards-Version: 3.9.8 Vcs-Browser: @PACKAGE_URL@ Vcs-Git: @PACKAGE_GIT@ Build-Depends: debhelper (>= 9), qtbase5-dev, libelf-dev, cmake Package-List: @PACKAGE_NAME@ deb devel optional arch=any Checksums-Sha1: @DEBIAN_SHA1@ @DEBIAN_SIZE@ @DEBIAN_ARCHIVE_FILE@ @FILE_SHA1@ @FILE_SIZE@ @PACKAGE_SOURCE_ARCHIVE_FILE@ Checksums-Sha256: @DEBIAN_SHA256@ @DEBIAN_SIZE@ @DEBIAN_ARCHIVE_FILE@ @FILE_SHA256@ @FILE_SIZE@ @PACKAGE_SOURCE_ARCHIVE_FILE@ Files: @DEBIAN_MD5@ @DEBIAN_SIZE@ @DEBIAN_ARCHIVE_FILE@ @FILE_MD5@ @FILE_SIZE@ @PACKAGE_SOURCE_ARCHIVE_FILE@ ================================================ FILE: extras/packaging/flatpak/cz.cvut.edu.comparch.qtrvsim.json ================================================ { "app-id" : "cz.cvut.edu.comparch.qtrvsim", "runtime" : "org.kde.Platform", "runtime-version" : "6.2", "sdk" : "org.kde.Sdk", "tags" : [ "nightly" ], "command" : "qtrvsim_gui", "rename-desktop-file" : "qtrvsim.desktop", "rename-icon" : "qtrvsim_gui", "finish-args" : [ "--device=dri", "--share=ipc", "--socket=fallback-x11", "--socket=wayland" ], "modules" : [ { "name" : "qtrvsim", "buildsystem" : "cmake-ninja", "builddir" : true, "config-opts" : [ "-DCMAKE_BUILD_TYPE=RelWithDebInfo" ], "sources" : [ { "type" : "git", "url" : "https://github.com/cvut/qtrvsim/" } ] } ] } ================================================ FILE: extras/packaging/mingw/cmake-i686-w64-mingw32.conf ================================================ # cmake -DCMAKE_TOOLCHAIN_FILE=cmake-i686-w64-mingw32.conf -DCMAKE_BUILD_TYPE=Release ../qtrvsim set(CMAKE_SYSTEM_NAME Windows) set(CMAKE_SYSTEM_PROCESSOR x86) set(CMAKE_SYSROOT /usr/i686-w64-mingw32) set(CMAKE_STAGING_PREFIX /home/devel/stage) set(tools /usr) set(CMAKE_C_COMPILER ${tools}/bin/i686-w64-mingw32-gcc) set(CMAKE_CXX_COMPILER ${tools}/bin/i686-w64-mingw32-g++) set(Qt5_DIR /usr/i686-w64-mingw32/qt/lib/cmake/Qt5) set(QT_DIR /usr/i686-w64-mingw32/qt/lib/cmake/Qt5) set(CMAKE_FIND_ROOT_PATH_MODE_PROGRAM NEVER) set(CMAKE_FIND_ROOT_PATH_MODE_LIBRARY ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_INCLUDE ONLY) set(CMAKE_FIND_ROOT_PATH_MODE_PACKAGE ONLY) ================================================ FILE: extras/packaging/nix/qtrvsim.nix ================================================ { lib, stdenv, cmake, wrapQtAppsHook, qtbase, qtsvg }: stdenv.mkDerivation { name = "QtRVSim"; src = builtins.fetchGit ../../..; nativeBuildInputs = [ cmake wrapQtAppsHook ]; buildInputs = [ qtbase qtsvg ]; meta = { description = "RISC-V CPU simulator for education purposes."; longDescription = '' RISC-V CPU simulator for education purposes with pipeline and cache visualization. Developed at FEE CTU for computer architecture classes. ''; homepage = "https://github.com/cvut/qtrvsim"; license = lib.licenses.gpl3Plus; maintainers = [ "Jakub Dupak " ]; }; } ================================================ FILE: extras/packaging/rpm/spec.in ================================================ # # spec file for package @PACKAGE_NAME@ # @COPYRIGHT_HASH_COMMENT@ # # This program is free software: you can redistribute it and/or modify it # under the terms of the GNU General Public License as published by the Free # Software Foundation, either version 3 of the License, or (at your option) # any later version. # # This program is distributed in the hope that it will be useful, but WITHOUT # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or # FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for # more details. # You should have received a copy of the GNU General Public License along with # this program. If not, see # # Please submit bugfixes or comments via # @PROJECT_HOME_PAGE@ # issues tracker. # Name: @PACKAGE_NAME@ Version: @PACKAGE_VERSION@ Release: @PACKAGE_RELEASE@ Summary: @PACKAGE_DESCRIPTION@ License: @PACKAGE_LICENCE@ Group: System/Emulators/Other URL: @PACKAGE_URL@ Source: @PACKAGE_SOURCE_ARCHIVE_FILE@ BuildRequires: cmake BuildRequires: gcc-c++ BuildRequires: hicolor-icon-theme BuildRequires: pkgconfig BuildRequires: pkgconfig(Qt5Core) BuildRequires: pkgconfig(Qt5PrintSupport) BuildRequires: pkgconfig(Qt5Test) BuildRequires: pkgconfig(Qt5Widgets) %if ! 0%{?suse_version} BuildRequires: desktop-file-utils BuildRequires: pkgconfig(libelf) %endif %if 0%{?suse_version} BuildRequires: libelf-devel BuildRequires: update-desktop-files %endif %description @PACKAGE_LONG_DESCRIPTION@ %prep %setup -q %build %if 0%{?suse_version} %cmake -DCMAKE_CXX_FLAGS="-Wno-error" -DCMAKE_C_FLAGS="-Wno-error" %else %cmake %endif %cmake_build %install %cmake_install #desktop icon %if 0%{?suse_version} %suse_update_desktop_file -r -i @PACKAGE_NAME@ 'System Emulator' %endif %if 0%{?fedora} || 0%{?rhel} || 0%{?centos} desktop-file-validate %{buildroot}%{_datadir}/applications/@PACKAGE_NAME@.desktop %endif # TODO: this should be generated from CMake %files %{_bindir}/qtrvsim_gui %{_bindir}/qtrvsim_cli %{_datadir}/icons/hicolor/scalable/apps/qtrvsim_gui.svg %{_datadir}/icons/hicolor/48x48/apps/qtrvsim_gui.png %{_datadir}/applications/qtrvsim.desktop %{_datadir}/metainfo/cz.cvut.edu.comparch.qtrvsim.metainfo.xml %license LICENSE %doc README.md %changelog ================================================ FILE: qtlogging.ini ================================================ [Rules] *.debug=false ================================================ FILE: src/assembler/CMakeLists.txt ================================================ project(assembler DESCRIPTION "Simple assembler library to use in the UI.") set(CMAKE_AUTOMOC ON) set(assembler_SOURCES fixmatheval.cpp simpleasm.cpp ) set(assembler_HEADERS fixmatheval.h messagetype.h simpleasm.h ) add_library(assembler STATIC ${assembler_SOURCES} ${assembler_HEADERS}) target_link_libraries(assembler PRIVATE ${QtLib}::Core) ================================================ FILE: src/assembler/fixmatheval.cpp ================================================ #include "fixmatheval.h" #include "common/math/bit_ops.h" #include "memory/address.h" #include #include using namespace fixmatheval; // Consumes part of string that is a valid symbol name, returns it and removes it from the input // string. QStringView tokenize_symbol(QStringView &expression) { QStringView symbol = expression; int i = 0; for (QChar ch : expression) { if (!(ch.isLetterOrNumber() || (ch == '_'))) { break; } i++; } expression = expression.mid(i); return symbol; } FmeSymbolDb::~FmeSymbolDb() = default; bool FmeSymbolDb::getValue(FmeValue &value, QString name) { (void)value; (void)name; return false; } FmeNode::FmeNode(int priority) { prio = priority; } FmeNode::~FmeNode() = default; int FmeNode::priority() const { return prio; } bool FmeNode::insert(FmeNode *node) { (void)node; return false; } FmeNode *FmeNode::child() { return nullptr; } FmeNode *FmeNode::find_last_child() { FmeNode *current = this; FmeNode *child = this->child(); while (child != nullptr) { current = child; child = child->child(); } return current; } FmeNodeConstant::FmeNodeConstant(FmeValue value) : FmeNode(INT_MAX) { this->value = value; } FmeNodeConstant::~FmeNodeConstant() = default; bool FmeNodeConstant::eval( FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) { std::ignore = symdb; std::ignore = error; std::ignore = inst_addr; value = this->value; return true; } QString FmeNodeConstant::dump() { return QString::number(value); } FmeNodeSymbol::FmeNodeSymbol(QString &name) : FmeNode(INT_MAX) { this->name = name; } FmeNodeSymbol::~FmeNodeSymbol() = default; bool FmeNodeSymbol::eval( FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) { std::ignore = inst_addr; if (!symdb) { error = QString("no symbol table to find value for %1").arg(name); return false; } bool ok = symdb->getValue(value, name); if (!ok) { error = QString("value for symbol \"%1\" not found").arg(name); } return ok; } QString FmeNodeSymbol::dump() { return name; } FmeNodeUnaryOp::FmeNodeUnaryOp( int priority, FmeValue (*op)(FmeValue &a, machine::Address inst_addr), QString description) : FmeNode(priority) { this->operand_a = nullptr; this->op = op; this->description = std::move(description); } FmeNodeUnaryOp::~FmeNodeUnaryOp() { delete operand_a; } bool FmeNodeUnaryOp::FmeNodeUnaryOp::eval( FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) { FmeValue value_a; if (!operand_a) { return false; } if (!operand_a->eval(value_a, symdb, error, inst_addr)) { return false; } value = op(value_a, inst_addr); return true; } FmeNode *FmeNodeUnaryOp::child() { return operand_a; } bool FmeNodeUnaryOp::insert(FmeNode *node) { operand_a = node; return true; } QString FmeNodeUnaryOp::dump() { return "(" + description + " " + (operand_a ? operand_a->dump() : "nullptr") + ")"; } FmeNodeBinaryOp::FmeNodeBinaryOp( int priority, FmeValue (*op)(FmeValue &a, FmeValue &b), FmeNode *left, QString description) : FmeNode(priority) { this->operand_a = left; this->operand_b = nullptr; this->op = op; this->description = std::move(description); } FmeNodeBinaryOp::~FmeNodeBinaryOp() { delete operand_a; delete operand_b; } bool FmeNodeBinaryOp::eval( FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) { FmeValue value_a; FmeValue value_b; if (!operand_a || !operand_b) { return false; } if (!operand_a->eval(value_a, symdb, error, inst_addr) || !operand_b->eval(value_b, symdb, error, inst_addr)) { return false; } value = op(value_a, value_b); return true; } FmeNode *FmeNodeBinaryOp::child() { return operand_b; } bool FmeNodeBinaryOp::insert(FmeNode *node) { operand_b = node; return true; } QString FmeNodeBinaryOp::dump() { return "(" + (operand_a ? operand_a->dump() : "nullptr") + " " + description + " " + (operand_b ? operand_b->dump() : "nullptr") + ")"; } FmeExpression::FmeExpression() : FmeNode(0) { root = nullptr; } bool FmeExpression::parse(const QString &expression, QString &error) { delete root; int base_prio = 100; root = nullptr; bool ok = true; int i; int word_start = 0; bool in_word = false; bool is_unary = true; QString optxtx; for (i = 0; true; i++) { QChar ch {}; if (i < expression.size()) { ch = expression.at(i); } if (!(ch.isLetterOrNumber() || (ch == '_')) || (i >= expression.size())) { if (ch.isSpace()) { continue; } if (in_word) { FmeNode *new_node = nullptr; QString word = expression.mid(word_start, i - word_start); if (word.at(0).isDigit()) { new_node = new FmeNodeConstant(word.toLongLong(&ok, 0)); if (!ok) { error = QString("cannot convert \"%1\" to number").arg(word); delete new_node; break; } } else { new_node = new FmeNodeSymbol(word); } FmeNode *node = this->find_last_child(); ok = node->insert(new_node); if (!ok) { error = QString("parse stuck at \"%1\"").arg(word); break; } in_word = false; is_unary = false; } if (i >= expression.size()) { break; } FmeValue (*binary_op)(FmeValue &a, FmeValue &b) = nullptr; FmeValue (*unary_op)(FmeValue &a, machine::Address inst_addr) = nullptr; int prio = base_prio; optxtx = ch; if (ch == '%') { // `%` MODIFIER `(` SYMBOL `)` // MODIFIER := `hi` | `lo` | `pcrel_hi` | `pcrel_lo` prio += 90; // The opening parenthesis is peeked and asserted but not consumed. QStringView expr = QStringView(expression).mid(i + 1); if (expr.startsWith(QStringLiteral("hi("))) { i += 2; optxtx = QStringLiteral("%hi"); unary_op = [](FmeValue &a, machine::Address) -> FmeValue { return get_bits(a + 0x800, 31, 12); }; } else if (expr.startsWith(QStringLiteral("lo("))) { i += 2; optxtx = QStringLiteral("%lo"); unary_op = [](FmeValue &a, machine::Address) -> FmeValue { return sign_extend(get_bits(a, 11, 0), 12); }; } else if (expr.startsWith(QStringLiteral("pcrel_hi("))) { i += 8; optxtx = QStringLiteral("%pcrel_hi"); unary_op = [](FmeValue &a, machine::Address inst_addr) -> FmeValue { return get_bits(a - inst_addr.get_raw(), 31, 12) + get_bit(a - inst_addr.get_raw(), 11); }; } else if (expr.startsWith(QStringLiteral("pcrel_lo("))) { i += 8; optxtx = QStringLiteral("%pcrel_lo"); unary_op = [](FmeValue &a, machine::Address inst_addr) -> FmeValue { return sign_extend(get_bits(a - inst_addr.get_raw() + 4, 11, 0), 12); }; } else { auto modifier = tokenize_symbol(expr); error = QString("Unknown modifier \"%1\"").arg(modifier); ok = false; break; } } else if (ch == '~') { prio += 90; unary_op = [](FmeValue &a, machine::Address) -> FmeValue { return ~a; }; } else if (ch == '-') { if (is_unary) { prio += 90; unary_op = [](FmeValue &a, machine::Address) -> FmeValue { return -a; }; } else { binary_op = [](FmeValue &a, FmeValue &b) -> FmeValue { return a - b; }; prio += 20; } } else if (ch == '+') { if (is_unary) { continue; } binary_op = [](FmeValue &a, FmeValue &b) -> FmeValue { return a + b; }; prio += 20; } else if (ch == '*') { binary_op = [](FmeValue &a, FmeValue &b) -> FmeValue { return a * b; }; prio += 30; } else if (ch == '/') { binary_op = [](FmeValue &a, FmeValue &b) -> FmeValue { return a / b; }; prio += 30; } else if (ch == '|') { binary_op = [](FmeValue &a, FmeValue &b) -> FmeValue { return a | b; }; prio += 10; } else if (ch == '&') { binary_op = [](FmeValue &a, FmeValue &b) -> FmeValue { return a & b; }; prio += 15; } else if (ch == '^') { binary_op = [](FmeValue &a, FmeValue &b) -> FmeValue { return a ^ b; }; prio += 15; } else if (ch == '(') { base_prio += 100; } else if (ch == ')') { base_prio -= 100; if (base_prio <= 0) { ok = false; error = QString("Unbalanced brackets."); break; } } else { error = QString("Unknown character \"%1\" in expression.").arg(ch); ok = false; break; } if ((binary_op != nullptr) || (unary_op != nullptr)) { FmeNode *node; FmeNode *child; for (node = this; (child = node->child()) != nullptr; node = child) { if (child->priority() >= prio) { break; } } if (binary_op != nullptr) { ok = node->insert(new FmeNodeBinaryOp(prio, binary_op, child, optxtx)); is_unary = true; } else { ok = node->insert(new FmeNodeUnaryOp(prio, unary_op, optxtx)); } if (!ok) { error = QString("parse stuck at \"%1\"").arg(QString(ch)); break; } } } else { if (!in_word) { word_start = i; } in_word = true; } } return ok; } FmeExpression::~FmeExpression() { delete root; root = nullptr; } bool FmeExpression::eval( FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) { if (!root) { return false; } return root->eval(value, symdb, error, inst_addr); } bool FmeExpression::insert(FmeNode *node) { root = node; return true; } FmeNode *FmeExpression::child() { return root; } QString FmeExpression::dump() { return "(" + (root ? root->dump() : "nullptr") + ")"; } ================================================ FILE: src/assembler/fixmatheval.h ================================================ #ifndef FIXMATHEVAL_H #define FIXMATHEVAL_H #include "memory/address.h" #include namespace fixmatheval { typedef int64_t FmeValue; class FmeSymbolDb { public: virtual ~FmeSymbolDb(); virtual bool getValue(FmeValue &value, QString name) = 0; }; class FmeNode { public: explicit FmeNode(int priority); virtual ~FmeNode(); virtual bool eval(FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) = 0; virtual bool insert(FmeNode *node); virtual FmeNode *child(); virtual QString dump() = 0; FmeNode *find_last_child(); [[nodiscard]] int priority() const; private: int prio; }; class FmeNodeConstant : public FmeNode { public: explicit FmeNodeConstant(FmeValue value); ~FmeNodeConstant() override; bool eval(FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) override; QString dump() override; private: FmeValue value; }; class FmeNodeSymbol : public FmeNode { public: explicit FmeNodeSymbol(QString &name); ~FmeNodeSymbol() override; bool eval(FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) override; QString dump() override; private: QString name; }; class FmeNodeUnaryOp : public FmeNode { public: FmeNodeUnaryOp( int priority, FmeValue (*op)(FmeValue &a, machine::Address inst_addr), QString description = "??"); ~FmeNodeUnaryOp() override; bool eval(FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) override; bool insert(FmeNode *node) override; FmeNode *child() override; QString dump() override; private: FmeValue (*op)(FmeValue &a, machine::Address inst_addr); FmeNode *operand_a; QString description; }; class FmeNodeBinaryOp : public FmeNode { public: FmeNodeBinaryOp( int priority, FmeValue (*op)(FmeValue &a, FmeValue &b), FmeNode *left, QString description = "??"); ~FmeNodeBinaryOp() override; bool eval(FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) override; bool insert(FmeNode *node) override; FmeNode *child() override; QString dump() override; private: FmeValue (*op)(FmeValue &a, FmeValue &b); FmeNode *operand_a; FmeNode *operand_b; QString description; }; class FmeExpression : public FmeNode { public: FmeExpression(); ~FmeExpression() override; virtual bool parse(const QString &expression, QString &error); bool eval(FmeValue &value, FmeSymbolDb *symdb, QString &error, machine::Address inst_addr) override; bool insert(FmeNode *node) override; FmeNode *child() override; QString dump() override; private: FmeNode *root; }; } // namespace fixmatheval #endif /*FIXMATHEVAL_H*/ ================================================ FILE: src/assembler/messagetype.h ================================================ #ifndef MESSAGETYPE_H #define MESSAGETYPE_H namespace messagetype { enum Type { MSG_START, MSG_FINISH, MSG_INFO, MSG_WARNING, MSG_ERROR, }; } #endif /*MESSAGETYPE*/ ================================================ FILE: src/assembler/simpleasm.cpp ================================================ #include "simpleasm.h" #include "machine/memory/address.h" #include "machine/memory/memory_utils.h" #include #include #include #include #include using namespace fixmatheval; using machine::Address; using ae = machine::AccessEffects; // For enum values, type is obvious from // context. SymbolTableDb::SymbolTableDb(machine::SymbolTable *symbol_table) { this->symbol_table = symbol_table; } bool SymbolTableDb::getValue(fixmatheval::FmeValue &value, QString name) { SymbolValue val; if (!symbol_table->name_to_value(val, name)) { return false; } value = val; return true; } void SymbolTableDb::setSymbol( const QString &name, SymbolValue value, SymbolSize size, SymbolInfo info, SymbolOther other) { symbol_table->set_symbol(name, value, size, info, other); } uint64_t SimpleAsm::string_to_uint64(const QString &str, int base, int *chars_taken) { int i; int64_t val; char *p, *r; char cstr[str.count() + 1]; for (i = 0; i < str.count(); i++) { cstr[i] = str.at(i).toLatin1(); } cstr[i] = 0; p = cstr; val = std::strtoll(p, &r, base); if (chars_taken != nullptr) { *chars_taken = r - p; } return val; } SimpleAsm::SimpleAsm(QObject *parent) : Super(parent) { clear(); } SimpleAsm::~SimpleAsm() { clear(); } void SimpleAsm::clear() { symtab = nullptr; mem = nullptr; while (!reloc.isEmpty()) { delete reloc.takeFirst(); } error_occured = false; fatal_occured = false; } void SimpleAsm::setup( machine::FrontendMemory *mem, SymbolTableDb *symtab, machine::Address address, machine::Xlen xlen) { this->mem = mem; this->symtab = symtab; this->address = address; this->symtab->setSymbol("XLEN", static_cast(xlen), sizeof(uint64_t)); } static const machine::BitArg wordArg = { { { 32, 0 } }, 0 }; bool SimpleAsm::process_line( const QString &line, const QString &filename, int line_number, QString *error_ptr) { QString error; QString label = ""; QString op = ""; QStringList operands; int pos; bool in_quotes = false; bool backslash = false; bool maybe_label = true; bool final = false; bool separator; bool space_separated = false; int token_beg = -1; int token_last = -1; int operand_num = -1; for (pos = 0; pos <= line.count(); pos++) { QChar ch = ' '; if (pos >= line.count()) { final = true; } if (!final) { ch = line.at(pos); } if (!in_quotes) { if (ch == '#') { if (line.mid(pos).startsWith("#include")) { if ((line.count() > pos + 8) && !line.at(pos + 8).isSpace()) { final = true; } } else if (line.mid(pos).startsWith("#pragma")) { if ((line.count() > pos + 7) && !line.at(pos + 7).isSpace()) { final = true; } else { space_separated = true; } } else { final = true; } } if (ch == ';') { final = true; } if (ch == '/') { if (pos + 1 < line.count()) { if (line.at(pos + 1) == '/') { final = true; } } } separator = final || (maybe_label && (ch == ':')) || ((operand_num >= 0) && ((ch == ',') || (space_separated && ch.isSpace() && (token_beg != -1)))); if (maybe_label && (ch == ':')) { maybe_label = false; if (token_beg == -1) { error = "empty label"; emit report_message( messagetype::MSG_ERROR, filename, line_number, pos, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } label = line.mid(token_beg, token_last - token_beg + 1); token_beg = -1; } else if ( ((!ch.isSpace() && (token_beg >= 0) && (token_last < pos - 1)) || final) && (operand_num == -1)) { maybe_label = false; if (token_beg != -1) { op = line.mid(token_beg, token_last - token_beg + 1).toLower(); } token_beg = -1; operand_num = 0; if (ch == ',') { error = "empty first operand"; emit report_message( messagetype::MSG_ERROR, filename, line_number, pos, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } } else if (separator || final) { if (token_beg == -1) { error = "empty operand"; emit report_message( messagetype::MSG_ERROR, filename, line_number, pos, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } operands.append(line.mid(token_beg, token_last - token_beg + 1)); token_beg = -1; operand_num++; } if (final) { break; } if (!ch.isSpace() && !separator) { if (token_beg == -1) { token_beg = pos; } token_last = pos; } backslash = false; if (ch == '"') { if (operand_num == -1) { error = "unexpected quoted text"; emit report_message( messagetype::MSG_ERROR, filename, line_number, pos, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } in_quotes = true; } } else { token_last = pos; if (final) { error = "unterminated quoted text"; emit report_message(messagetype::MSG_ERROR, filename, line_number, pos, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } if ((ch == '"') && !backslash) { in_quotes = false; } if ((ch == '\\') && !backslash) { backslash = true; } else { backslash = false; } } } if (!label.isEmpty()) { symtab->setSymbol(label, address.get_raw(), 4); } if (op.isEmpty()) { if (operands.count() != 0) { error = "operands for empty operation"; emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } return true; } if (op == "#pragma") { return process_pragma(operands, filename, line_number, error_ptr); } if (op == "#include") { bool res = true; QString incname; if ((operands.count() != 1) || operands.at(0).isEmpty()) { error = "the single file has to be specified for include"; emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } incname = operands.at(0); if (incname.at(0) == '"') { incname = incname.mid(1, incname.count() - 2); } QFileInfo fi(QFileInfo(filename).dir(), incname); incname = fi.filePath(); include_stack.append(filename); if (include_stack.contains(incname)) { error = QString("recursive include of file: \"%1\"").arg(incname); res = false; } else { if (!process_file(incname, error_ptr)) { res = false; error = QString("error in included file: \"%1\"").arg(incname); } } if (!res) { emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { if (error_ptr->isEmpty()) { *error_ptr = error; } } } include_stack.removeLast(); return res; } if ((op == ".text") || (op == ".data") || (op == ".bss") || (op == ".globl") || (op == ".end") || (op == ".ent") || (op == ".option")) { return true; } if (op == ".org") { bool ok; fixmatheval::FmeExpression expression; fixmatheval::FmeValue value; if (operands.count() != 1) { error = ".org unexpected number of operands"; emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } ok = expression.parse(operands.at(0), error); if (!ok) { fatal_occured = true; error = tr(".orig %1 parse error.").arg(line); emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } ok = expression.eval(value, symtab, error, address); if (!ok) { fatal_occured = true; error = tr(".orig %1 evaluation error.").arg(line); emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } address = machine::Address(value); return true; } if ((op == ".space") || (op == ".skip")) { bool ok; fixmatheval::FmeExpression expression; fixmatheval::FmeValue value; fixmatheval::FmeValue fill = 0; if ((operands.count() != 1) && (operands.count() != 2)) { error = ".space/.skip unexpected number of operands"; emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } if (operands.count() > 1) { ok = expression.parse(operands.at(1), error); if (!ok) { fatal_occured = true; error = tr(".space/.skip %1 parse error.").arg(line); emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } ok = expression.eval(fill, symtab, error, address); if (!ok) { fatal_occured = true; error = tr(".space/.skip %1 evaluation error.").arg(line); emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } } ok = expression.parse(operands.at(0), error); if (!ok) { fatal_occured = true; error = tr(".space/.skip %1 parse error.").arg(line); emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } ok = expression.eval(value, symtab, error, address); if (!ok) { fatal_occured = true; error = tr(".space/.skip %1 evaluation error.").arg(line); emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } while (value-- > 0) { if (!fatal_occured) { mem->write_u8(address, (uint8_t)fill, ae::INTERNAL); } address += 1; } return true; } if ((op == ".equ") || (op == ".set")) { if ((operands.count() > 2) || (operands.count() < 1)) { error = tr(".set or .equ incorrect arguments number."); emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } QString name = operands.at(0).trimmed(); if ((name == "noat") || (name == "noreored")) { return true; } bool ok; fixmatheval::FmeValue value = 1; if (operands.count() > 1) { fixmatheval::FmeExpression expression; ok = expression.parse(operands.at(1), error); if (ok) { ok = expression.eval(value, symtab, error, address); } if (!ok) { error = tr(".set or .equ %1 parse error.").arg(operands.at(1)); emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } } symtab->setSymbol(name, value, 0); return true; } if ((op == ".ascii") || (op == ".asciz")) { bool append_zero = op == ".asciz"; for (QString s : operands) { if (s.count() < 2) { error = "ascii empty string"; emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } if ((s.at(0) != '"') || (s.at(s.count() - 1) != '"')) { error = "ascii missing quotes"; emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } s = s.mid(1, s.count() - 2); for (pos = 0; pos < s.count(); pos++) { // target byte is in ASCII encoding uint8_t target_byte = 0x00; QChar host_char = s.at(pos); if (host_char == '\\') { // if the host encoding recognizes this as a backslash (escape char) // handle end of the string check if (pos + 1 >= s.count()) { error = "ascii - invalid escape sequence at the end of the string"; emit report_message( messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } // compare the host_char in host encoding but emit byte in ASCII encoding host_char = s.at(++pos); if (host_char == '0') { target_byte = 0x00; } else if (host_char == 'b') { target_byte = 0x08; } else if (host_char == 't') { target_byte = 0x09; } else if (host_char == 'n') { target_byte = 0x0A; } else if (host_char == 'r') { target_byte = 0x0D; } else if (host_char == '"') { target_byte = 0x22; } else if (host_char == '\\') { target_byte = 0x5C; } else { error = QString("ascii - incorrect escape sequence '\\") + host_char + "'"; emit report_message( messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } } else { // otherwise convert it to ASCII and write it as-is target_byte = host_char.toLatin1(); } if (!fatal_occured) { mem->write_u8(address, target_byte, ae::INTERNAL); } address += 1; } if (append_zero) { if (!fatal_occured) { mem->write_u8(address, 0, ae::INTERNAL); } address += 1; } } return true; } if (op == ".byte") { bool ok; for (const QString &s : operands) { uint32_t val = 0; int chars_taken; val = string_to_uint64(s, 0, &chars_taken); if (chars_taken != s.size()) { fixmatheval::FmeExpression expression; fixmatheval::FmeValue value; ok = expression.parse(s, error); if (!ok) { fatal_occured = true; error = tr(".byte %1 parse error.").arg(line); emit report_message( messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } ok = expression.eval(value, symtab, error, address); if (!ok) { fatal_occured = true; error = tr(".byte %1 evaluation error.").arg(line); emit report_message( messagetype::MSG_ERROR, filename, line_number, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } val = (uint8_t)value; } if (!fatal_occured) { mem->write_u8(address, (uint8_t)val, ae::INTERNAL); } address += 1; } return true; } while (address.get_raw() & 3) { if (!fatal_occured) { mem->write_u8(address, 0, ae::INTERNAL); } address += 1; } if (op == ".word") { for (QString s : operands) { s = s.simplified(); uint32_t val = 0; int chars_taken; val = string_to_uint64(s, 0, &chars_taken); if (chars_taken != s.size()) { val = 0; reloc.append(new machine::RelocExpression( address, s, 0, -0xffffffff, 0xffffffff, &wordArg, filename, line_number)); } if (!fatal_occured) { mem->write_u32(address, val, ae::INTERNAL); } address += 4; } return true; } uint32_t inst[2] = { 0, 0 }; size_t size = 0; try { machine::TokenizedInstruction inst_tok { op, operands, address, filename, static_cast(line_number) }; size = machine::Instruction::code_from_tokens(inst, 8, inst_tok, &reloc); } catch (machine::Instruction::ParseError &e) { error = tr("instruction %1 parse error - %2.").arg(line, e.message); emit report_message(messagetype::MSG_ERROR, filename, line_number, 0, e.message, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } uint32_t *p = inst; for (size_t l = 0; l < size; l += 4) { if (!fatal_occured) { mem->write_u32(address, *(p++), ae::INTERNAL); } address += 4; } return true; } bool SimpleAsm::process_file(const QString &filename, QString *error_ptr) { QString error; bool res = true; QFile srcfile(filename); if (!srcfile.open(QFile::ReadOnly | QFile::Text)) { error = QString("cannot open file: \"%1\"").arg(filename); emit report_message(messagetype::MSG_ERROR, "", 0, 0, error, ""); error_occured = true; if (error_ptr != nullptr) { *error_ptr = error; } return false; } for (int ln = 1; !srcfile.atEnd(); ln++) { QString line = srcfile.readLine(); if ((line.count() > 0) && (line.at(line.count() - 1) == '\n')) { line.truncate(line.count() - 1); } if (!process_line(line, filename, ln, error_ptr)) { res = false; } } srcfile.close(); return res; } bool SimpleAsm::finish(QString *error_ptr) { bool error_reported = false; for (machine::RelocExpression *r : reloc) { QString error; fixmatheval::FmeExpression expression; if (!expression.parse(r->expression, error)) { error = tr("expression parse error %1 at line %2, expression %3.") .arg(error, QString::number(r->line), expression.dump()); emit report_message(messagetype::MSG_ERROR, r->filename, r->line, 0, error, ""); if (error_ptr != nullptr && !error_reported) { *error_ptr = error; } error_occured = true; error_reported = true; } else { fixmatheval::FmeValue value; if (!expression.eval(value, symtab, error, r->location)) { error = tr("expression evalution error %1 at line %2 , " "expression %3.") .arg(error, QString::number(r->line), expression.dump()); emit report_message(messagetype::MSG_ERROR, r->filename, r->line, 0, error, ""); if (error_ptr != nullptr && !error_reported) { *error_ptr = error; } error_occured = true; error_reported = true; } else { if (false) { emit report_message( messagetype::MSG_INFO, r->filename, r->line, 0, expression.dump() + " -> " + QString::number(value), ""); } machine::Instruction inst(mem->read_u32(r->location, ae::INTERNAL)); if (!inst.update(value, r)) { error = tr("instruction update error %1 at line %2, " "expression %3 -> value %4.") .arg( error, QString::number(r->line), expression.dump(), QString::number(value)); emit report_message(messagetype::MSG_ERROR, r->filename, r->line, 0, error, ""); if (error_ptr != nullptr && !error_reported) { *error_ptr = error; } error_occured = true; error_reported = true; // Remove address } if (!fatal_occured) { mem->write_u32(Address(r->location), inst.data(), ae::INTERNAL); } } } } while (!reloc.isEmpty()) { delete reloc.takeFirst(); } emit mem->external_change_notify(mem, Address::null(), Address(0xffffffff), ae::INTERNAL); return !error_occured; } bool SimpleAsm::process_pragma( QStringList &operands, const QString &filename, int line_number, QString *error_ptr) { (void)operands; (void)filename; (void)line_number; (void)error_ptr; return true; } ================================================ FILE: src/assembler/simpleasm.h ================================================ #ifndef SIMPLEASM_H #define SIMPLEASM_H #include "fixmatheval.h" #include "machine/machine.h" #include "machine/memory/frontend_memory.h" #include "messagetype.h" #include #include using machine::SymbolInfo; using machine::SymbolOther; using machine::SymbolSize; using machine::SymbolValue; class SymbolTableDb : public fixmatheval::FmeSymbolDb { public: explicit SymbolTableDb(machine::SymbolTable *symbol_table); bool getValue(fixmatheval::FmeValue &value, QString name) override; void setSymbol( const QString &name, SymbolValue value, SymbolSize size, SymbolInfo info = 0, SymbolOther other = 0); private: machine::SymbolTable *symbol_table; }; class SimpleAsm : public QObject { Q_OBJECT using Super = QObject; signals: void report_message( messagetype::Type type, QString file, int line, int column, QString text, QString hint); public: explicit SimpleAsm(QObject *parent = nullptr); ~SimpleAsm() override; public: static uint64_t string_to_uint64(const QString &str, int base, int *chars_taken = nullptr); void clear(); void setup( machine::FrontendMemory *mem, SymbolTableDb *symtab, machine::Address address, machine::Xlen xlen); bool process_line( const QString &line, const QString &filename = "", int line_number = 0, QString *error_ptr = nullptr); virtual bool process_file(const QString &filename, QString *error_ptr = nullptr); bool finish(QString *error_ptr = nullptr); protected: virtual bool process_pragma( QStringList &operands, const QString &filename = "", int line_number = 0, QString *error_ptr = nullptr); bool error_occured {}; bool fatal_occured {}; SymbolTableDb *symtab {}; machine::Address address {}; private: QStringList include_stack; machine::FrontendMemory *mem {}; machine::RelocExpressionList reloc; }; #endif /*SIMPLEASM_H*/ ================================================ FILE: src/cli/CMakeLists.txt ================================================ project(cli LANGUAGES C CXX VERSION ${MAIN_PROJECT_VERSION} DESCRIPTION "Simulator command-line UI.") set(CMAKE_AUTOMOC ON) set(cli_SOURCES chariohandler.cpp main.cpp msgreport.cpp reporter.cpp tracer.cpp ) set(cli_HEADERS chariohandler.h msgreport.h reporter.h tracer.h ) add_executable(cli ${cli_SOURCES} ${cli_HEADERS}) target_link_libraries(cli PRIVATE ${QtLib}::Core machine os_emulation assembler) target_compile_definitions(cli PRIVATE APP_ORGANIZATION=\"${MAIN_PROJECT_ORGANIZATION}\" APP_ORGANIZATION_DOMAIN=\"${MAIN_PROJECT_HOMEPAGE_URL}\" APP_NAME=\"${MAIN_PROJECT_NAME}\" APP_VERSION=\"${PROJECT_VERSION}\" ENV_CONFIG_FILE_NAME=\"${MAIN_PROJECT_NAME_UPPER}_CONFIG_FILE\") set_target_properties(cli PROPERTIES OUTPUT_NAME "${MAIN_PROJECT_NAME_LOWER}_${PROJECT_NAME}") # ============================================================================= # Installation # ============================================================================= # Prior to CMake version 3.13, installation must be performed in the subdirectory, # there the target was created. Therefore executable installation is to be found # in corresponding CMakeLists.txt. install(TARGETS cli RUNTIME DESTINATION bin) include(../../cmake/TestingTools.cmake) enable_testing() add_cli_test( NAME stalls ARGS --asm "${CMAKE_SOURCE_DIR}/tests/cli/stalls/program.S" --dump-registers EXPECTED_OUTPUT "tests/cli/stalls/stdout.txt" ) add_cli_test( NAME asm_error ARGS --asm "${CMAKE_SOURCE_DIR}/tests/cli/asm-error/program.S" ) set_tests_properties(cli_asm_error PROPERTIES WILL_FAIL TRUE) add_cli_test( NAME modifiers ARGS --asm "${CMAKE_SOURCE_DIR}/tests/cli/modifiers/program.S" EXPECTED_OUTPUT "tests/cli/modifiers/stdout.txt" ) add_cli_test( NAME modifiers-pcrel ARGS --asm "${CMAKE_SOURCE_DIR}/tests/cli/modifiers-pcrel/program.S" EXPECTED_OUTPUT "tests/cli/modifiers-pcrel/stdout.txt" ) add_cli_test( NAME virtual_memory_template ARGS --asm "${CMAKE_SOURCE_DIR}/tests/cli/virtual_memory/template/program.S" --dump-registers EXPECTED_OUTPUT "tests/cli/virtual_memory/template/stdout.txt" ) add_cli_test( NAME virtual_memory_dtlb ARGS --asm "${CMAKE_SOURCE_DIR}/tests/cli/virtual_memory/dtlb/program.S" --dump-registers EXPECTED_OUTPUT "tests/cli/virtual_memory/dtlb/stdout.txt" ) add_cli_test( NAME virtual_memory_itlb ARGS --asm "${CMAKE_SOURCE_DIR}/tests/cli/virtual_memory/itlb/program.S" --dump-registers EXPECTED_OUTPUT "tests/cli/virtual_memory/itlb/stdout.txt" ) add_cli_test( NAME virtual_memory_memrw ARGS --asm "${CMAKE_SOURCE_DIR}/tests/cli/virtual_memory/memrw/program.S" --dump-registers EXPECTED_OUTPUT "tests/cli/virtual_memory/memrw/stdout.txt" ) add_cli_test( NAME virtual_memory_exec ARGS --asm "${CMAKE_SOURCE_DIR}/tests/cli/virtual_memory/exec/program.S" --dump-registers EXPECTED_OUTPUT "tests/cli/virtual_memory/exec/stdout.txt" ) ================================================ FILE: src/cli/chariohandler.cpp ================================================ #include "chariohandler.h" CharIOHandler::CharIOHandler(QIODevice *iodev, QObject *parent) : QIODevice(parent), fd_list() { this->iodev = iodev; if (!iodev->parent()) { iodev->setParent(this); } fd_specific = false; if (iodev->isOpen()) { Super::open(iodev->openMode()); } connect(iodev, &Super::aboutToClose, this, &CharIOHandler::aboutToClose); connect(iodev, &Super::bytesWritten, this, &CharIOHandler::bytesWritten); connect(iodev, &Super::channelBytesWritten, this, &CharIOHandler::channelBytesWritten); connect(iodev, &Super::channelReadyRead, this, &CharIOHandler::channelReadyRead); connect(iodev, &Super::readChannelFinished, this, &CharIOHandler::readChannelFinished); connect(iodev, &Super::readyRead, this, &CharIOHandler::readyRead); } CharIOHandler::~CharIOHandler() { if (iodev->parent() == this) { delete iodev; } } void CharIOHandler::writeByte(unsigned int data) { char ch = (char)data; write(&ch, 1); } void CharIOHandler::writeByte(int fd, unsigned int data) { if (!fd_specific || fd_list.contains(fd)) { writeByte(data); } } void CharIOHandler::readBytePoll(int fd, unsigned int &data, bool &available) { char ch; qint64 res; if (!fd_specific || fd_list.contains(fd)) { if (bytesAvailable() > 0) { res = read(&ch, 1); if (res > 0) { data = ch & 0xff; available = true; } } } } void CharIOHandler::insertFd(const int &fd) { fd_list.insert(fd); } void CharIOHandler::removeFd(const int &fd) { fd_list.remove(fd); } bool CharIOHandler::isSequential() const { return iodev->isSequential(); } bool CharIOHandler::open(OpenMode mode) { if (!iodev->open(mode)) { return false; } Super::open(mode); return true; } void CharIOHandler::close() { Super::close(); iodev->close(); } qint64 CharIOHandler::pos() const { return iodev->pos(); } qint64 CharIOHandler::size() const { return iodev->size(); } bool CharIOHandler::seek(qint64 pos) { return iodev->seek(pos); } bool CharIOHandler::atEnd() const { return iodev->atEnd(); } bool CharIOHandler::reset() { return iodev->reset(); } qint64 CharIOHandler::bytesAvailable() const { return iodev->bytesAvailable() + Super::bytesAvailable(); } qint64 CharIOHandler::bytesToWrite() const { return iodev->bytesToWrite() + Super::bytesToWrite(); } bool CharIOHandler::canReadLine() const { return iodev->canReadLine(); } bool CharIOHandler::waitForReadyRead(int msecs) { return iodev->waitForReadyRead(msecs); } bool CharIOHandler::waitForBytesWritten(int msecs) { return iodev->waitForBytesWritten(msecs); } qint64 CharIOHandler::readData(char *data, qint64 maxSize) { return iodev->read(data, maxSize); } qint64 CharIOHandler::readLineData(char *data, qint64 maxSize) { return iodev->readLine(data, maxSize); } qint64 CharIOHandler::writeData(const char *data, qint64 maxSize) { return iodev->write(data, maxSize); } ================================================ FILE: src/cli/chariohandler.h ================================================ #ifndef CHARIOHANDLER_H #define CHARIOHANDLER_H #include #include #include class CharIOHandler : public QIODevice { Q_OBJECT using Super = QIODevice; public: explicit CharIOHandler(QIODevice *iodev, QObject *parent = nullptr); ~CharIOHandler() override; public slots: void writeByte(unsigned int data); void writeByte(int fd, unsigned int data); void readBytePoll(int fd, unsigned int &data, bool &available); public: void insertFd(const int &fd); void removeFd(const int &fd); [[nodiscard]] bool isSequential() const override; bool open(OpenMode mode) override; void close() override; [[nodiscard]] qint64 pos() const override; [[nodiscard]] qint64 size() const override; bool seek(qint64 pos) override; [[nodiscard]] bool atEnd() const override; bool reset() override; [[nodiscard]] qint64 bytesAvailable() const override; [[nodiscard]] qint64 bytesToWrite() const override; [[nodiscard]] bool canReadLine() const override; bool waitForReadyRead(int msecs) override; bool waitForBytesWritten(int msecs) override; protected: qint64 readData(char *data, qint64 maxSize) override; qint64 readLineData(char *data, qint64 maxSize) override; qint64 writeData(const char *data, qint64 maxSize) override; private: QIODevice *iodev; bool fd_specific; QSet fd_list; }; #endif // CHARIOHANDLER_H ================================================ FILE: src/cli/main.cpp ================================================ #include "assembler/simpleasm.h" #include "chariohandler.h" #include "common/logging.h" #include "common/logging_format_colors.h" #include "machine/machineconfig.h" #include "msgreport.h" #include "os_emulation/ossyscall.h" #include "reporter.h" #include "tracer.h" #include #include #include #include #include #include using namespace machine; using namespace std; using ae = machine::AccessEffects; // For enum values, type is obvious from // context. void create_parser(QCommandLineParser &p) { p.setApplicationDescription("QtRvSim CLI machine simulator"); p.addHelpOption(); p.addVersionOption(); p.addPositionalArgument("FILE", "Input ELF executable file or assembler source"); // p.addOptions({}); available only from Qt 5.4+ p.addOption({ "asm", "Treat provided file argument as assembler source." }); p.addOption({ "pipelined", "Configure CPU to use five stage pipeline." }); p.addOption({ "no-delay-slot", "Disable jump delay slot." }); p.addOption( { "hazard-unit", "Specify hazard unit implementation [none|stall|forward].", "HUKIND" }); p.addOption( { { "trace-fetch", "tr-fetch" }, "Trace fetched instruction (for both pipelined and not core)." }); p.addOption( { { "trace-decode", "tr-decode" }, "Trace instruction in decode stage. (only for pipelined core)" }); p.addOption( { { "trace-execute", "tr-execute" }, "Trace instruction in execute stage. (only for pipelined core)" }); p.addOption( { { "trace-memory", "tr-memory" }, "Trace instruction in memory stage. (only for pipelined core)" }); p.addOption( { { "trace-writeback", "tr-writeback" }, "Trace instruction in write back stage. (only for pipelined core)" }); p.addOption({ { "trace-pc", "tr-pc" }, "Print program counter register changes." }); p.addOption({ { "trace-wrmem", "tr-wr" }, "Trace writes into memory." }); p.addOption({ { "trace-rdmem", "tr-rd" }, "Trace reads from memory." }); p.addOption( { { "trace-gp", "tr-gp" }, "Print general purpose register changes. You can use * for " "all registers.", "REG" }); p.addOption({ "dump-to-json", "Configure reportor dump to json file.", "FNAME" }); p.addOption({ "only-dump", "Do not start the processor." }); p.addOption({ "disable-console-dump", "Configure reporter not to dump to console." }); p.addOption({ { "dump-registers", "d-regs" }, "Dump registers state at program exit." }); p.addOption({ "dump-cache-stats", "Dump cache statistics at program exit." }); p.addOption({ "dump-cycles", "Dump number of CPU cycles till program end." }); p.addOption({ "dump-range", "Dump memory range.", "START,LENGTH,FNAME" }); p.addOption({ "dump-symbol-table", "Dump the symbol table." }); p.addOption({ "dump-branch-predictor", "Dump branch predictor statistics at program exit." }); p.addOption({ "dump-all", "Dump all available information at program exit." }); p.addOption({ "load-range", "Load memory range.", "START,FNAME" }); p.addOption({ "expect-fail", "Expect that program causes CPU trap and fail if it doesn't." }); p.addOption( { "fail-match", "Program should exit with exactly this CPU TRAP. Possible values are " "I(unsupported Instruction), A(Unsupported ALU operation), " "O(Overflow/underflow) and J(Unaligned Jump). You can freely combine " "them. Using this implies expect-fail option.", "TRAP" }); p.addOption( { "d-cache", "Data cache. Format policy,sets,words_in_blocks,associativity[,writeback] where " "policy is random/lru/lfu " "and writeback is optional wb/wt/wtna/wta", "DCACHE" }); p.addOption( { "i-cache", "Instruction cache. Format policy,sets,words_in_blocks,associativity " "where policy is random/lru/lfu", "ICACHE" }); p.addOption( { "l2-cache", "L2 cache. Format policy,sets,words_in_blocks,associativity[,writeback] where " "policy is random/lru/lfu " "and writeback is optional wb/wt/wtna/wta", "L2CACHE" }); p.addOption( { "branch-predictor", "Branch predictor. Format type,init_state,btb,bhr,bht\n" "type is always_not_taken/always_taken/btfnt/smith_1_bit/smith_2_bit/smith_2_bit_hysteresis\n" "init_state is (for smith 1) not_taken/taken, (for smith 2) strongly_not_taken/weakly_not_taken/weakly_taken/strongly_taken\n" "for other predictors it is ignored/undefined\n" "btb is number of branch target buffer bits\n" "bhr is number of branch history register bits\n" "bht is number of branch history table address bits", "BPRED" }); p.addOption({ "read-time", "Memory read access time (cycles).", "RTIME" }); p.addOption({ "write-time", "Memory read access time (cycles).", "WTIME" }); p.addOption({ "burst-time", "Memory read access time (cycles).", "BTIME" }); p.addOption({ "l2-time", "L2 cache access time (cycles).", "L2TIME" }); p.addOption({ { "serial-in", "serin" }, "File connected to the serial port input.", "FNAME" }); p.addOption( { { "serial-out", "serout" }, "File connected to the serial port output.", "FNAME" }); p.addOption({ { "os-emulation", "osemu" }, "Operating system emulation." }); p.addOption( { { "std-in", "stdin" }, "File connected to the syscall standard input.", "FNAME" }); p.addOption( { { "std-out", "stdout" }, "File connected to the syscall standard output.", "FNAME" }); p.addOption( { { "os-fs-root", "osfsroot" }, "Emulated system root/prefix for opened files", "DIR" }); p.addOption( { { "isa-variant", "isavariant" }, "Instruction set to emulate (default RV32IMA)", "STR" }); p.addOption({ "cycle-limit", "Limit execution to specified maximum clock cycles", "NUMBER" }); } void configure_cache(CacheConfig &cacheconf, const QStringList &cachearg, const QString &which) { if (cachearg.empty()) { return; } cacheconf.set_enabled(true); QStringList pieces = cachearg.at(cachearg.size() - 1).split(","); if (pieces.size() < 3) { fprintf( stderr, "Parameters %s cache incorrect (correct lru,4,2,2,wb).\n", qPrintable(which)); exit(EXIT_FAILURE); } if (pieces.at(0).size() < 1) { fprintf(stderr, "Policy for %s cache is incorrect.\n", qPrintable(which)); exit(EXIT_FAILURE); } if (!pieces.at(0).at(0).isDigit()) { if (pieces.at(0).toLower() == "random") { cacheconf.set_replacement_policy(CacheConfig::RP_RAND); } else if (pieces.at(0).toLower() == "lru") { cacheconf.set_replacement_policy(CacheConfig::RP_LRU); } else if (pieces.at(0).toLower() == "lfu") { cacheconf.set_replacement_policy(CacheConfig::RP_LFU); } else { fprintf(stderr, "Policy for %s cache is incorrect.\n", qPrintable(which)); exit(EXIT_FAILURE); } pieces.removeFirst(); } if (pieces.size() < 3) { fprintf( stderr, "Parameters for %s cache incorrect (correct lru,4,2,2,wb). \n", qPrintable(which)); exit(EXIT_FAILURE); } cacheconf.set_set_count(pieces.at(0).toLong()); cacheconf.set_block_size(pieces.at(1).toLong()); cacheconf.set_associativity(pieces.at(2).toLong()); if (cacheconf.set_count() == 0 || cacheconf.block_size() == 0 || cacheconf.associativity() == 0) { fprintf( stderr, "Parameters for %s cache cannot have zero component. \n", qPrintable(which)); exit(EXIT_FAILURE); } if (pieces.size() > 3) { if (pieces.at(3).toLower() == "wb") { cacheconf.set_write_policy(CacheConfig::WP_BACK); } else if (pieces.at(3).toLower() == "wt" || pieces.at(3).toLower() == "wtna") { cacheconf.set_write_policy(CacheConfig::WP_THROUGH_NOALLOC); } else if (pieces.at(3).toLower() == "wta") { cacheconf.set_write_policy(CacheConfig::WP_THROUGH_ALLOC); } else { fprintf( stderr, "Write policy for %s cache is incorrect (correct wb/wt/wtna/wta).\n", qPrintable(which)); exit(EXIT_FAILURE); } } } void configure_branch_predictor(MachineConfig &config, const QStringList &bpred) { if (bpred.empty()) { return; } config.set_bp_enabled(true); QStringList pieces = bpred.at(bpred.size() - 1).split(","); if (pieces.size() < 5) { fprintf( stderr, "Parameters for branch predictor are incorrect.\n"); exit(EXIT_FAILURE); } PredictorType ptype = pieces.at(0).toLower() == "always_not_taken" ? PredictorType::ALWAYS_NOT_TAKEN : pieces.at(0).toLower() == "always_taken" ? PredictorType::ALWAYS_TAKEN : pieces.at(0).toLower() == "btfnt" ? PredictorType::BTFNT : pieces.at(0).toLower() == "smith_1_bit" ? PredictorType::SMITH_1_BIT : pieces.at(0).toLower() == "smith_2_bit" ? PredictorType::SMITH_2_BIT : pieces.at(0).toLower() == "smith_2_bit_hysteresis" ? PredictorType::SMITH_2_BIT_HYSTERESIS : PredictorType::UNDEFINED; PredictorState init_state = pieces.at(1).toLower() == "not_taken" ? PredictorState::NOT_TAKEN : pieces.at(1).toLower() == "taken" ? PredictorState::TAKEN : pieces.at(1).toLower() == "strongly_not_taken" ? PredictorState::STRONGLY_NOT_TAKEN : pieces.at(1).toLower() == "weakly_not_taken" ? PredictorState::WEAKLY_NOT_TAKEN : pieces.at(1).toLower() == "weakly_taken" ? PredictorState::WEAKLY_TAKEN : pieces.at(1).toLower() == "strongly_taken" ? PredictorState::STRONGLY_TAKEN : PredictorState::UNDEFINED; switch (ptype) { case PredictorType::ALWAYS_NOT_TAKEN: case PredictorType::ALWAYS_TAKEN: case PredictorType::BTFNT: init_state = PredictorState::UNDEFINED; break; case PredictorType::SMITH_1_BIT: if (init_state != PredictorState::NOT_TAKEN && init_state != PredictorState::TAKEN) { fprintf(stderr, "Initial state for Smith 1 bit predictor must be " "not_taken/taken.\n"); exit(EXIT_FAILURE); } break; case PredictorType::SMITH_2_BIT: case PredictorType::SMITH_2_BIT_HYSTERESIS: if (init_state != PredictorState::STRONGLY_NOT_TAKEN && init_state != PredictorState::WEAKLY_NOT_TAKEN && init_state != PredictorState::WEAKLY_TAKEN && init_state != PredictorState::STRONGLY_TAKEN) { fprintf(stderr, "Initial state for Smith 2 bit predictor must be " "strongly_not_taken/weakly_not_taken/weakly_taken/" "strongly_taken.\n"); exit(EXIT_FAILURE); } break; default: fprintf(stderr, "Unknown predictor type specified.\n"); exit(EXIT_FAILURE); } config.set_bp_type(ptype); config.set_bp_init_state(init_state); config.set_bp_btb_bits(pieces.at(2).toLong()); config.set_bp_bhr_bits(pieces.at(3).toLong()); config.set_bp_bht_addr_bits(pieces.at(4).toLong()); } void parse_u32_option( QCommandLineParser &parser, const QString &option_name, MachineConfig &config, void (MachineConfig::*setter)(uint32_t value)) { auto values = parser.values(option_name); if (!values.empty()) { bool ok = true; // Try to parse supplied value. uint32_t value = values.last().toUInt(&ok); if (ok) { // Set the value if successfully parsed. (config.*setter)(value); } else { fprintf( stderr, "Value of option %s is not a valid unsigned integer.\n", qPrintable(option_name)); exit(EXIT_FAILURE); } } } void configure_machine(QCommandLineParser &parser, MachineConfig &config) { QStringList arguments = parser.positionalArguments(); if (arguments.size() != 1) { fprintf(stderr, "Single ELF file has to be specified\n"); parser.showHelp(); } config.set_elf(arguments[0]); config.set_delay_slot(!parser.isSet("no-delay-slot")); config.set_pipelined(parser.isSet("pipelined")); auto hazard_unit_values = parser.values("hazard-unit"); if (!hazard_unit_values.empty()) { if (!config.set_hazard_unit(hazard_unit_values.last().toLower())) { fprintf(stderr, "Unknown kind of hazard unit specified\n"); exit(EXIT_FAILURE); } } parse_u32_option(parser, "read-time", config, &MachineConfig::set_memory_access_time_read); parse_u32_option(parser, "write-time", config, &MachineConfig::set_memory_access_time_write); parse_u32_option(parser, "burst-time", config, &MachineConfig::set_memory_access_time_burst); parse_u32_option(parser, "l2-time", config, &MachineConfig::set_memory_access_time_level2); if (!parser.values("burst-time").empty()) config.set_memory_access_enable_burst(true); configure_cache(*config.access_cache_data(), parser.values("d-cache"), "data"); configure_cache(*config.access_cache_program(), parser.values("i-cache"), "instruction"); configure_cache(*config.access_cache_level2(), parser.values("l2-cache"), "level2"); configure_branch_predictor(config, parser.values("branch-predictor")); config.set_osemu_enable(parser.isSet("os-emulation")); config.set_osemu_known_syscall_stop(false); int siz = parser.values("os-fs-root").size(); if (siz >= 1) { QString osemu_fs_root = parser.values("os-fs-root").at(siz - 1); if (osemu_fs_root.length() > 0) { config.set_osemu_fs_root(osemu_fs_root); } } siz = parser.values("isa-variant").size(); for (int i = 0; i < siz; i++) { int pos = 0; bool first = true; bool subtract = false; QString isa_str = parser.values("isa-variant").at(i).toUpper(); if (isa_str.startsWith("RV32")) { config.set_simulated_xlen(machine::Xlen::_32); pos = 4; } else if (isa_str.startsWith("RV64")) { config.set_simulated_xlen(machine::Xlen::_64); pos = 4; } for (; pos < isa_str.size(); pos++, first = false) { char ch = isa_str.at(pos).toLatin1(); if (ch == '+') { subtract = false; continue; } else if (ch == '-') { subtract = true; continue; } auto flag = machine::ConfigIsaWord::byChar(ch); if (flag.isEmpty()) continue; if (first) config.modify_isa_word( ~machine::ConfigIsaWord::empty(), machine::ConfigIsaWord::empty()); if (subtract) config.modify_isa_word(flag, machine::ConfigIsaWord::empty()); else config.modify_isa_word(flag, flag); } } } void configure_tracer(QCommandLineParser &p, Tracer &tr) { if (p.isSet("trace-fetch")) { tr.trace_fetch = true; } if (p.isSet("pipelined")) { // Following are added only if we have stages if (p.isSet("trace-decode")) { tr.trace_decode = true; } if (p.isSet("trace-execute")) { tr.trace_execute = true; } if (p.isSet("trace-memory")) { tr.trace_memory = true; } if (p.isSet("trace-writeback")) { tr.trace_writeback = true; } } if (p.isSet("trace-pc")) { tr.trace_pc = true; } if (p.isSet("trace-gp")) { tr.trace_regs_gp = true; } QStringList gps = p.values("trace-gp"); for (const auto &gp : gps) { if (gp == "*") { tr.regs_to_trace.fill(true); } else { bool res; size_t num = gp.toInt(&res); if (res && num <= machine::REGISTER_COUNT) { tr.regs_to_trace.at(num) = true; } else { fprintf(stderr, "Unknown register number given for trace-gp: %s\n", qPrintable(gp)); exit(EXIT_FAILURE); } } } if (p.isSet("trace-rdmem")) { tr.trace_rdmem = true; } if (p.isSet("trace-wrmem")) { tr.trace_wrmem = true; } QStringList clim = p.values("cycle-limit"); if (!clim.empty()) { bool ok; tr.cycle_limit = clim.at(clim.size() - 1).toLong(&ok); if (!ok) { fprintf(stderr, "Cycle limit parse error\n"); exit(EXIT_FAILURE); } } // TODO } void configure_reporter(QCommandLineParser &p, Reporter &r, const SymbolTable *symtab) { if (p.isSet("dump-to-json")) { r.dump_format = (DumpFormat)(r.dump_format | DumpFormat::JSON); r.dump_file_json = p.value("dump-to-json"); } if (p.isSet("disable-console-dump")) { r.dump_format = (DumpFormat)(r.dump_format & ~(DumpFormat::CONSOLE)); } if (p.isSet("dump-registers")) { r.enable_regs_reporting(); } if (p.isSet("dump-cache-stats")) { r.enable_cache_stats(); } if (p.isSet("dump-cycles")) { r.enable_cycles_reporting(); } if (p.isSet("dump-symbol-table")) { r.enable_symbol_table_reporting(); } if (p.isSet("dump-branch-predictor")) { r.enable_branch_predictor_stats(); } if (p.isSet("dump-all")) { r.enable_all_reporting(); } QStringList fail = p.values("fail-match"); for (const auto &i : fail) { for (int y = 0; y < i.length(); y++) { enum Reporter::FailReason reason; switch (tolower(i.toStdString()[y])) { case 'i': reason = Reporter::FR_UNSUPPORTED_INSTR; break; default: fprintf(stderr, "Unknown fail condition: %c\n", qPrintable(i)[y]); exit(EXIT_FAILURE); } r.expect_fail(reason); } } if (p.isSet("expect-fail") && !p.isSet("fail-match")) { r.expect_fail(Reporter::FailAny); } foreach (QString range_arg, p.values("dump-range")) { uint64_t len; bool ok1 = true; bool ok2 = true; QString str; int comma1 = range_arg.indexOf(","); if (comma1 < 0) { fprintf(stderr, "Range start missing\n"); exit(EXIT_FAILURE); } int comma2 = range_arg.indexOf(",", comma1 + 1); if (comma2 < 0) { fprintf(stderr, "Range length/name missing\n"); exit(EXIT_FAILURE); } str = range_arg.mid(0, comma1); Address start; if (str.size() >= 1 && !str.at(0).isDigit() && symtab != nullptr) { SymbolValue _start; ok1 = symtab->name_to_value(_start, str); start = Address(_start); } else { start = Address(str.toULong(&ok1, 0)); } str = range_arg.mid(comma1 + 1, comma2 - comma1 - 1); if (str.size() >= 1 && !str.at(0).isDigit() && symtab != nullptr) { ok2 = symtab->name_to_value(len, str); } else { len = str.toULong(&ok2, 0); } if (!ok1 || !ok2) { fprintf(stderr, "Range start/length specification error.\n"); exit(EXIT_FAILURE); } r.add_dump_range(start, len, range_arg.mid(comma2 + 1)); } // TODO } void configure_serial_port(QCommandLineParser &p, SerialPort *ser_port) { CharIOHandler *ser_in = nullptr; CharIOHandler *ser_out = nullptr; int siz; if (!ser_port) { return; } siz = p.values("serial-in").size(); if (siz >= 1) { QIODevice::OpenMode mode = QFile::ReadOnly; auto *qf = new QFile(p.values("serial-in").at(siz - 1)); ser_in = new CharIOHandler(qf, ser_port); siz = p.values("serial-out").size(); if (siz) { if (p.values("serial-in").at(siz - 1) == p.values("serial-out").at(siz - 1)) { mode = QFile::ReadWrite; ser_out = ser_in; } } if (!ser_in->open(mode)) { fprintf(stderr, "Serial port input file cannot be open for read.\n"); exit(EXIT_FAILURE); } } if (!ser_out) { siz = p.values("serial-out").size(); if (siz >= 1) { auto *qf = new QFile(p.values("serial-out").at(siz - 1)); ser_out = new CharIOHandler(qf, ser_port); if (!ser_out->open(QFile::WriteOnly)) { fprintf(stderr, "Serial port output file cannot be open for write.\n"); exit(EXIT_FAILURE); } } } if (ser_in) { QObject::connect(ser_in, &QIODevice::readyRead, ser_port, &SerialPort::rx_queue_check); QObject::connect(ser_port, &SerialPort::rx_byte_pool, ser_in, &CharIOHandler::readBytePoll); if (ser_in->bytesAvailable()) { ser_port->rx_queue_check(); } } if (ser_out) { QObject::connect( ser_port, &SerialPort::tx_byte, ser_out, QOverload::of(&CharIOHandler::writeByte)); } } void configure_osemu(QCommandLineParser &p, MachineConfig &config, Machine *machine) { CharIOHandler *std_out = nullptr; QScopedPointer std_in_path; int siz; siz = p.values("std-in").size(); if (siz >= 1) { std_in_path.reset(new QString(p.values("std-in").at(siz - 1))); } siz = p.values("std-out").size(); if (siz >= 1) { auto *qf = new QFile(p.values("std-out").at(siz - 1)); std_out = new CharIOHandler(qf, machine); if (!std_out->open(QFile::WriteOnly)) { fprintf(stderr, "Emulated system standard output file cannot be open for write.\n"); exit(EXIT_FAILURE); } } const static machine::ExceptionCause ecall_variats[] = { machine::EXCAUSE_ECALL_ANY, machine::EXCAUSE_ECALL_M, machine::EXCAUSE_ECALL_S, machine::EXCAUSE_ECALL_U }; if (config.osemu_enable()) { auto *osemu_handler = new osemu::OsSyscallExceptionHandler( config.osemu_known_syscall_stop(), config.osemu_unknown_syscall_stop(), config.osemu_fs_root()); if (!std_in_path.isNull() && !std_in_path->isEmpty()) { if (!osemu_handler->map_stdin_to_hostfile(*std_in_path)) { fprintf(stderr, "Failed to map stdin to host file '%s'\n", std_in_path->toLatin1().data()); exit(EXIT_FAILURE); } } if (std_out) { machine::Machine::connect( osemu_handler, &osemu::OsSyscallExceptionHandler::char_written, std_out, QOverload::of(&CharIOHandler::writeByte)); } /*connect( osemu_handler, &osemu::OsSyscallExceptionHandler::rx_byte_pool, terminal, &TerminalDock::rx_byte_pool);*/ for (auto ecall_variat : ecall_variats) { machine->register_exception_handler(ecall_variat, osemu_handler); machine->set_step_over_exception(ecall_variat, true); machine->set_stop_on_exception(ecall_variat, false); } } else { for (auto ecall_variat : ecall_variats) { machine->set_step_over_exception(ecall_variat, false); machine->set_stop_on_exception(ecall_variat, config.osemu_exception_stop()); } } } void load_ranges(Machine &machine, const QStringList &ranges) { for (const QString &range_arg : ranges) { bool ok = true; QString str; int comma1 = range_arg.indexOf(","); if (comma1 < 0) { fprintf(stderr, "Range start missing\n"); exit(EXIT_FAILURE); } str = range_arg.mid(0, comma1); Address start; if (str.size() >= 1 && !str.at(0).isDigit() && machine.symbol_table() != nullptr) { SymbolValue _start; ok = machine.symbol_table()->name_to_value(_start, str); start = Address(_start); } else { start = Address(str.toULong(&ok, 0)); } if (!ok) { fprintf(stderr, "Range start/length specification error.\n"); exit(EXIT_FAILURE); } ifstream in; in.open(range_arg.mid(comma1 + 1).toLocal8Bit().data(), ios::in); Address addr = start; for (std::string line; getline(in, line);) { size_t end_pos = line.find_last_not_of(" \t\n"); if (std::string::npos == end_pos) { continue; } size_t start_pos = line.find_first_not_of(" \t\n"); line = line.substr(0, end_pos + 1); line = line.substr(start_pos); size_t idx; uint32_t val = stoul(line, &idx, 0); if (idx != line.size()) { fprintf(stderr, "cannot parse load range data.\n"); exit(EXIT_FAILURE); } machine.memory_data_bus_rw()->write_u32(addr, val, ae::INTERNAL); addr += 4; } in.close(); } } bool assemble(Machine &machine, MsgReport &msgrep, const QString &filename) { SymbolTableDb symbol_table_db(machine.symbol_table_rw(true)); machine::FrontendMemory *mem = machine.memory_data_bus_rw(); if (mem == nullptr) { return false; } machine.cache_sync(); SimpleAsm assembler; SimpleAsm::connect(&assembler, &SimpleAsm::report_message, &msgrep, &MsgReport::report_message); assembler.setup(mem, &symbol_table_db, 0x00000200_addr, machine.core()->get_xlen()); if (!assembler.process_file(filename)) { return false; } return assembler.finish(); } int main(int argc, char *argv[]) { QCoreApplication app(argc, argv); QCoreApplication::setApplicationName(APP_NAME); QCoreApplication::setApplicationVersion(APP_VERSION); set_default_log_pattern(); QCommandLineParser p; create_parser(p); p.process(app); MachineConfig config; configure_machine(p, config); bool asm_source = p.isSet("asm"); Machine machine(config, !asm_source, !asm_source); Tracer tr(&machine); configure_tracer(p, tr); configure_serial_port(p, machine.serial_port()); configure_osemu(p, config, &machine); if (asm_source) { MsgReport msg_report(&app); if (!assemble(machine, msg_report, p.positionalArguments()[0])) { exit(EXIT_FAILURE); } } Reporter r(&app, &machine); configure_reporter(p, r, machine.symbol_table()); QObject::connect(&tr, &Tracer::cycle_limit_reached, &r, &Reporter::cycle_limit_reached); load_ranges(machine, p.values("load-range")); if (p.isSet("only-dump")) { QMetaObject::invokeMethod(&machine, &Machine::program_exit, Qt::QueuedConnection); } else { // QTimer::singleShot(0, &machine, &Machine::play); alternative QMetaObject::invokeMethod(&machine, &Machine::play, Qt::QueuedConnection); } return QCoreApplication::exec(); } ================================================ FILE: src/cli/msgreport.cpp ================================================ #include "msgreport.h" #include using namespace std; MsgReport::MsgReport(QCoreApplication *app) : Super(app) {} void MsgReport::report_message( messagetype::Type type, const QString &file, int line, int column, const QString &text, const QString &hint) { (void)hint; QString typestr = "error"; switch (type) { case messagetype::MSG_ERROR: typestr = "error"; break; case messagetype::MSG_WARNING: typestr = "warning"; break; case messagetype::MSG_INFO: typestr = "info"; break; default: return; } printf("%s:", qPrintable(file)); if (line != 0) { printf("%d:", line); } if (column != 0) { printf("%d:", column); } printf("%s:%s\n", qPrintable(typestr), qPrintable(text)); } ================================================ FILE: src/cli/msgreport.h ================================================ #ifndef MSGREPORT_H #define MSGREPORT_H #include "assembler/messagetype.h" #include #include #include #include class MsgReport : public QObject { Q_OBJECT using Super = QObject; public: explicit MsgReport(QCoreApplication *app); public slots: static void report_message( messagetype::Type type, const QString &file, int line, int column, const QString &text, const QString &hint); }; #endif // MSGREPORT_H ================================================ FILE: src/cli/reporter.cpp ================================================ #include "reporter.h" #include using namespace machine; using namespace std; Reporter::Reporter(QCoreApplication *app, Machine *machine) : QObject() , app(app) , machine(machine) { connect(machine, &Machine::program_exit, this, &Reporter::machine_exit); connect(machine, &Machine::program_trap, this, &Reporter::machine_trap); connect( machine->core(), &Core::stop_on_exception_reached, this, &Reporter::machine_exception_reached); } void Reporter::add_dump_range(Address start, size_t len, const QString &path_to_write) { dump_ranges.append({ start, len, path_to_write }); } void Reporter::machine_exit() { report(); if (e_fail != 0) { printf("Machine was expected to fail but it didn't.\n"); exit(1); } else { exit(0); } } /* TODO: Decide whether this should be moved to machine to avoid duplication or kept aside as a visualization concern */ constexpr const char *get_exception_name(ExceptionCause exception_cause) { switch (exception_cause) { case EXCAUSE_NONE: return "NONE"; case EXCAUSE_INSN_FAULT: return "INSN_FAULT"; case EXCAUSE_INSN_ILLEGAL: return "INSN_ILLEGAL"; case EXCAUSE_BREAK: return "BREAK"; case EXCAUSE_LOAD_MISALIGNED: return "LOAD_MISALIGNED"; case EXCAUSE_LOAD_FAULT: return "LOAD_FAULT"; case EXCAUSE_STORE_MISALIGNED: return "STORE_MISALIGNED"; case EXCAUSE_STORE_FAULT: return "STORE_FAULT"; case EXCAUSE_ECALL_U: return "ECALL_U"; case EXCAUSE_ECALL_S: return "ECALL_S"; case EXCAUSE_RESERVED_10: return "RESERVED_10"; case EXCAUSE_ECALL_M: return "ECALL_M"; case EXCAUSE_INSN_PAGE_FAULT: return "INSN_PAGE_FAULT"; case EXCAUSE_LOAD_PAGE_FAULT: return "LOAD_PAGE_FAULT"; case EXCAUSE_RESERVED_14: return "RESERVED_14"; case EXCAUSE_STORE_PAGE_FAULT: return "STORE_PAGE_FAULT"; // Simulator specific exception cause codes, alliases case EXCAUSE_HWBREAK: return "HWBREAK"; case EXCAUSE_ECALL_ANY: return "ECALL_ANY"; case EXCAUSE_INT: return "INT"; default: UNREACHABLE } } void Reporter::machine_exception_reached() { ExceptionCause excause = machine->get_exception_cause(); printf("Machine stopped on %s exception.\n", get_exception_name(excause)); report(); exit(0); } void Reporter::cycle_limit_reached() { printf("Specified cycle limit reached\n"); report(); exit(0); } void Reporter::machine_trap(SimulatorException &e) { report(); bool expected = false; if (typeid(e) == typeid(SimulatorExceptionUnsupportedInstruction)) { expected = e_fail & FR_UNSUPPORTED_INSTR; } printf("Machine trapped: %s\n", qPrintable(e.msg(false))); exit(expected ? 0 : 1); } void Reporter::report() { if (dump_format & DumpFormat::CONSOLE) { if (e_regs | e_cycles | e_cycles | e_fail) { printf("Machine state report:\n"); } } if (e_regs) { report_regs(); } if (e_cache_stats) { report_caches(); } if (e_cycles) { QString cycle_count = QString::asprintf("%" PRIu32, machine->core()->get_cycle_count()); QString stall_count = QString::asprintf("%" PRIu32, machine->core()->get_stall_count()); if (dump_format & DumpFormat::JSON) { QJsonObject temp = {}; temp["cycles"] = cycle_count; temp["stalls"] = stall_count; dump_data_json["cycles"] = temp; } if (dump_format & DumpFormat::CONSOLE) { printf("cycles: %s\n", qPrintable(cycle_count)); printf("stalls: %s\n", qPrintable(stall_count)); } } for (const DumpRange &range : dump_ranges) { report_range(range); } if (e_symtab) { QJsonObject symtab_json = {}; for (const auto &name : machine->symbol_table()->names()) { SymbolValue sym_val; machine->symbol_table()->name_to_value(sym_val, name); QString value = QString::asprintf( machine->core()->get_xlen() == Xlen::_32 ? "0x%08" PRIx64 : "0x%016" PRIx64, sym_val); if (dump_format & DumpFormat::JSON) { symtab_json[name] = value; } if (dump_format & DumpFormat::CONSOLE) { printf("SYM[%s]: %s\n", qPrintable(name), qPrintable(value)); } } if (dump_format & DumpFormat::JSON) { dump_data_json["symbols"] = symtab_json; } } if (e_predictor) { report_predictor(); } if (dump_format & DumpFormat::JSON) { QFile file(dump_file_json); QByteArray bytes = QJsonDocument(dump_data_json).toJson(QJsonDocument::Indented); if (file.open(QIODevice::WriteOnly)) { file.write(bytes); file.close(); printf("JSON object written to file\n"); } else { printf("Could not open file for writing\n"); } } } void Reporter::report_regs() { if (dump_format & DumpFormat::JSON) { dump_data_json["regs"] = {}; } report_pc(); for (unsigned i = 0; i < REGISTER_COUNT; i++) { report_gp_reg(i, (i == REGISTER_COUNT - 1)); } for (size_t i = 0; i < CSR::REGISTERS.size(); i++) { report_csr_reg(i, (i == CSR::REGISTERS.size() - 1)); } } void Reporter::report_pc() { QString value = QString::asprintf("0x%08" PRIx64, machine->registers()->read_pc().get_raw()); if (dump_format & DumpFormat::JSON) { QJsonObject regs = dump_data_json["regs"].toObject(); regs["PC"] = value; dump_data_json["regs"] = regs; } if (dump_format & DumpFormat::CONSOLE) { printf("PC:%s\n", qPrintable(value)); } } void Reporter::report_gp_reg(unsigned int i, bool last) { QString key = QString::asprintf("R%u", i); QString value = QString::asprintf("0x%08" PRIx64, machine->registers()->read_gp(i).as_u64()); if (dump_format & DumpFormat::JSON) { QJsonObject regs = dump_data_json["regs"].toObject(); regs[key] = value; dump_data_json["regs"] = regs; } if (dump_format & DumpFormat::CONSOLE) { printf("%s:%s%s", qPrintable(key), qPrintable(value), (last) ? "\n" : " "); } } void Reporter::report_csr_reg(size_t internal_id, bool last) { QString key = QString::asprintf("%s", CSR::REGISTERS[internal_id].name); QString value = QString::asprintf( "0x%08" PRIx64, machine->control_state()->read_internal(internal_id).as_u64()); if (dump_format & DumpFormat::JSON) { QJsonObject regs = dump_data_json["regs"].toObject(); regs[key] = value; dump_data_json["regs"] = regs; } if (dump_format & DumpFormat::CONSOLE) { printf("%s: %s%s", qPrintable(key), qPrintable(value), (last) ? "\n" : " "); } } void Reporter::report_caches() { if (dump_format & DumpFormat::JSON) { dump_data_json["caches"] = {}; } printf("Cache statistics report:\n"); report_cache("i-cache", *machine->cache_program()); report_cache("d-cache", *machine->cache_data()); if (machine->config().cache_level2().enabled()) { report_cache("l2-cache", *machine->cache_level2()); } } void Reporter::report_cache(const char *cache_name, const Cache &cache) { if (dump_format & DumpFormat::JSON) { QJsonObject caches = dump_data_json["caches"].toObject(); QJsonObject temp = {}; temp["reads"] = QString::asprintf("%" PRIu32, cache.get_read_count()); temp["hit"] = QString::asprintf("%" PRIu32, cache.get_hit_count()); temp["miss"] = QString::asprintf("%" PRIu32, cache.get_miss_count()); temp["hit_rate"] = QString::asprintf("%.3lf", cache.get_hit_rate()); temp["stalled_cycles"] = QString::asprintf("%" PRIu32, cache.get_stall_count()); temp["improved_speed"] = QString::asprintf("%.3lf", cache.get_speed_improvement()); caches[cache_name] = temp; dump_data_json["caches"] = caches; } if (dump_format & DumpFormat::CONSOLE) { printf("%s:reads: %" PRIu32 "\n", cache_name, cache.get_read_count()); printf("%s:hit: %" PRIu32 "\n", cache_name, cache.get_hit_count()); printf("%s:miss: %" PRIu32 "\n", cache_name, cache.get_miss_count()); printf("%s:hit-rate: %.3lf\n", cache_name, cache.get_hit_rate()); printf("%s:stalled-cycles: %" PRIu32 "\n", cache_name, cache.get_stall_count()); printf("%s:improved-speed: %.3lf\n", cache_name, cache.get_speed_improvement()); } } void Reporter::report_predictor() { const BranchPredictor *predictor = machine->branch_predictor(); if (predictor == nullptr) { return; } const PredictionStatistics *stats = predictor->get_stats(); if (stats == nullptr) { return; } if (dump_format & DumpFormat::JSON) { QJsonObject predictorObj = dump_data_json["predictor"].toObject(); QJsonObject temp = {}; temp["total_predictions"] = QString::asprintf("%" PRIu32, stats->total); temp["hits"] = QString::asprintf("%" PRIu32, stats->correct); temp["misses"] = QString::asprintf("%" PRIu32, stats->wrong); temp["accuracy"] = QString::asprintf("%.3lf", stats->accuracy); predictorObj = temp; dump_data_json["predictor"] = predictorObj; } if (dump_format & DumpFormat::CONSOLE) { printf("branch predictor:total predictions: %" PRIu32 "\n", stats->total); printf("branch predictor:hits: %" PRIu32 "\n", stats->correct); printf("branch predictor:misses: %" PRIu32 "\n", stats->wrong); printf("branch predictor:accuracy: %.3lf\n", stats->accuracy); } } void Reporter::report_range(const Reporter::DumpRange &range) { FILE *out = fopen(range.path_to_write.toLocal8Bit().data(), "w"); if (out == nullptr) { fprintf( stderr, "Failed to open %s for writing\n", range.path_to_write.toLocal8Bit().data()); return; } Address start = range.start & ~3; Address end = range.start + range.len; if (end < start) { end = 0xffffffff_addr; } // TODO: report also cached memory? const MemoryDataBus *mem = machine->memory_data_bus(); for (Address addr = start; addr < end; addr += 4) { fprintf(out, "0x%08" PRIx32 "\n", mem->read_u32(addr, ae::INTERNAL)); } if (fclose(out)) { fprintf(stderr, "Failure closing %s\n", range.path_to_write.toLocal8Bit().data()); } } void Reporter::exit(int retcode) { // Exit might not happen immediately, so we need to stop the machine explicitly. machine->pause(); QCoreApplication::exit(retcode); } ================================================ FILE: src/cli/reporter.h ================================================ #ifndef REPORTER_H #define REPORTER_H #include "common/memory_ownership.h" #include "machine/machine.h" #include #include #include #include #include #include using machine::Address; enum DumpFormat { CONSOLE = 1 << 0, JSON = 1 << 1, }; /** * Watches for special events in the machine (e.g. stop, exception, trap) and prints related * information. */ class Reporter : public QObject { Q_OBJECT public: Reporter(QCoreApplication *app, machine::Machine *machine); void enable_regs_reporting() { e_regs = true; }; void enable_cache_stats() { e_cache_stats = true; }; void enable_cycles_reporting() { e_cycles = true; }; void enable_symbol_table_reporting() { e_symtab = true; }; void enable_branch_predictor_stats() { e_predictor = true; }; void enable_all_reporting() { e_regs = true; e_cache_stats = true; e_cycles = true; e_symtab = true; e_predictor = true; } enum FailReason { FR_NONE = 0, FR_UNSUPPORTED_INSTR = (1 << 0), }; static const enum FailReason FailAny = FR_UNSUPPORTED_INSTR; void expect_fail(enum FailReason reason) { e_fail = (FailReason)(e_fail | reason); }; struct DumpRange { Address start; size_t len; /** Path to file, where this range will be dumped. */ QString path_to_write; }; void add_dump_range(Address start, size_t len, const QString &path_to_write); public slots: void cycle_limit_reached(); private slots: void machine_exit(); void machine_trap(machine::SimulatorException &e); void machine_exception_reached(); private: BORROWED QCoreApplication *const app; BORROWED machine::Machine *const machine; QVector dump_ranges; bool e_regs = false; bool e_cache_stats = false; bool e_cycles = false; bool e_symtab = false; bool e_predictor = false; FailReason e_fail = FR_NONE; void report(); void report_pc(); void report_regs(); void report_caches(); void report_range(const DumpRange &range); void report_csr_reg(size_t internal_id, bool last); void report_gp_reg(unsigned int i, bool last); void report_cache(const char *cache_name, const machine::Cache &cache); void report_predictor(); void exit(int retcode); public: DumpFormat dump_format = DumpFormat::CONSOLE; QString dump_file_json; QJsonObject dump_data_json = {}; }; #endif // REPORTER_H ================================================ FILE: src/cli/tracer.cpp ================================================ #include "tracer.h" #include using namespace machine; Tracer::Tracer(Machine *machine) : core_state(machine->core()->get_state()) { cycle_limit = 0; connect(machine->core(), &Core::step_done, this, &Tracer::step_output); } template void trace_instruction_in_stage( const char *stage_name, const StageStruct &stage, const WritebackInternalState &wb) { printf( "%s: %s%s\n", stage_name, (stage.excause != EXCAUSE_NONE) ? "!" : "", qPrintable(wb.inst.to_str(stage.inst_addr))); } void Tracer::step_output() { const auto &if_id = core_state.pipeline.fetch.final; const auto &id_ex = core_state.pipeline.decode.final; const auto &ex_mem = core_state.pipeline.execute.final; const auto &mem = core_state.pipeline.memory.internal; const auto &mem_wb = core_state.pipeline.memory.final; const auto &wb = core_state.pipeline.writeback.internal; if (trace_fetch) { trace_instruction_in_stage("Fetch", if_id, wb); } if (trace_decode) { trace_instruction_in_stage("Decode", id_ex, wb); } if (trace_execute) { trace_instruction_in_stage("Execute", ex_mem, wb); } if (trace_memory) { trace_instruction_in_stage("Memory", mem_wb, wb); } if (trace_writeback) { // All exceptions are resolved in memory, therefore there is no excause field in WB. printf("Writeback: %s\n", qPrintable(wb.inst.to_str(wb.inst_addr))); } if (trace_pc) { printf("PC: %" PRIx64 "\n", if_id.inst_addr.get_raw()); } if (trace_regs_gp && wb.regwrite && regs_to_trace.at(wb.num_rd)) { printf("GP %zu: %" PRIx64 "\n", size_t(wb.num_rd), wb.value.as_u64()); } if (trace_rdmem && mem_wb.memtoreg) { printf( "MEM[%" PRIx64 "]: RD %" PRIx64 "\n", mem_wb.mem_addr.get_raw(), mem_wb.towrite_val.as_u64()); } if (trace_wrmem && mem.memwrite) { printf( "MEM[%" PRIx64 "]: WR %" PRIx64 "\n", mem_wb.mem_addr.get_raw(), mem.mem_write_val.as_u64()); } if ((cycle_limit != 0) && (core_state.cycle_count >= cycle_limit)) { emit cycle_limit_reached(); } } ================================================ FILE: src/cli/tracer.h ================================================ #ifndef TRACER_H #define TRACER_H #include "machine/instruction.h" #include "machine/machine.h" #include "machine/memory/address.h" #include "machine/registers.h" #include /** * Watches the step by step execution of the machine and prints requested state. */ class Tracer final : public QObject { Q_OBJECT public: explicit Tracer(machine::Machine *machine); signals: void cycle_limit_reached(); private slots: void step_output(); private: const machine::CoreState &core_state; public: std::array regs_to_trace = {}; bool trace_fetch = false, trace_decode = false, trace_execute = false, trace_memory = false, trace_writeback = false, trace_pc = false, trace_wrmem = false, trace_rdmem = false, trace_regs_gp = false; quint64 cycle_limit; }; #endif // TRACER_H ================================================ FILE: src/common/CMakeLists.txt ================================================ project(common DESCRIPTION "Utils shared between multiple subprojects.") add_subdirectory(polyfills) set(common_HEADERS endian.h string_utils.h logging.h logging_format_colors.h containers/cvector.h math/bit_ops.h memory_ownership.h type_utils/lens.h ) # ============================================================================= # Target for usage of this header library # - this is header only library and it is implicitly available in the whole # project # ============================================================================= # ============================================================================= # Tests # - It is highly recommended to run these tests anytime you use a different # compiler or platform as this code is platform/compiler specific. # ============================================================================= enable_testing() set(CMAKE_AUTOMOC ON) # Put tests here... add_custom_target(common_unit_tests DEPENDS mulh64_test) ================================================ FILE: src/common/containers/cvector.h ================================================ /* * Based on: * Frozen * Copyright 2016 QuarksLab * * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF licenses this file * to you under the Apache License, Version 2.0 (the * "License"); you may not use this file except in compliance * with the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. */ #ifndef QTRVSIM_CVECTOR_H #define QTRVSIM_CVECTOR_H #include #include #include #include /** Constexpr vector with inline storage */ template class cvector { T data[N]; // Uninitialized storage cannot be used in constexpr. std::size_t dsize = 0; public: // Container typdefs using value_type = T; using reference = value_type &; using const_reference = const value_type &; using pointer = value_type *; using const_pointer = const value_type *; using iterator = pointer; using const_iterator = const_pointer; using size_type = std::size_t; using difference_type = std::ptrdiff_t; // Constructors constexpr cvector() = default; constexpr cvector(size_type count, const T &value) : dsize(count) { for (std::size_t i = 0; i < N; ++i) data[i] = value; } constexpr cvector(std::initializer_list initializer_list) { for (auto item : initializer_list) { push_back(item); } } // Iterators constexpr iterator begin() noexcept { return data; } constexpr const_iterator begin() const noexcept { return data; } constexpr const_iterator cbegin() const noexcept { return data; } constexpr iterator end() noexcept { return data + dsize; } constexpr const_iterator end() const noexcept { return data + dsize; } constexpr const_iterator cend() const noexcept { return data + dsize; } // Capacity [[nodiscard]] constexpr size_type size() const { return dsize; } // Element access constexpr reference operator[](std::size_t index) { return data[index]; } constexpr const_reference operator[](std::size_t index) const { return data[index]; } constexpr reference back() { return data[dsize - 1]; } constexpr const_reference back() const { return data[dsize - 1]; } // Modifiers constexpr void push_back(const T &a) { data[dsize++] = a; } constexpr void push_back(T &&a) { data[dsize++] = std::move(a); } constexpr void pop_back() { --dsize; } constexpr void clear() { dsize = 0; } }; #endif // QTRVSIM_CVECTOR_H ================================================ FILE: src/common/endian.h ================================================ #ifndef ENDIAN_H #define ENDIAN_H #include "polyfills/byteswap.h" #include "polyfills/endian_detection.h" #include "utils.h" #include #include #include #include #include #include #include /** * Memory endian type (used for bot simulator and host machine). * Standard enum is not available until c++20. */ enum Endian { LITTLE, BIG }; inline const char *to_string(Endian val) { switch (val) { case LITTLE: return "LITTLE"; case BIG: return "BIG"; } UNREACHABLE; } inline constexpr Endian get_native_endian() { #if (defined(__BIG_ENDIAN__)) return BIG; #elif (defined(__LITTLE_ENDIAN__)) return LITTLE; #else static_assert(false, "Could not detect endian or endian is neither big nor little."); #endif } constexpr Endian NATIVE_ENDIAN = get_native_endian(); /** * Full generic byteswap. Used in generic functions. * Optimized specializations are provided bellow. */ template inline T byteswap(T val) { static_assert(sizeof(T) <= 8, "Byteswap of large types is implementation dependant."); union U { T val; std::array raw; } src, dst; src.val = val; std::reverse_copy(src.raw.begin(), src.raw.end(), dst.raw.begin()); return dst.val; } template<> inline uint8_t byteswap(uint8_t val) { // NOP for single byte return val; } template<> inline uint16_t byteswap(uint16_t val) { return bswap16(val); } template<> inline uint32_t byteswap(uint32_t val) { return bswap32(val); } template<> inline uint64_t byteswap(uint64_t val) { return bswap64(val); } /** * Conditionally byteswap value. Condition if usually mismatch of endian on * interface of two memory levels or memory and core. */ template T byteswap_if(T value, bool condition) { return (condition) ? byteswap(value) : value; } Q_DECLARE_METATYPE(Endian) #endif // ENDIAN_H ================================================ FILE: src/common/logging.h ================================================ /** * Wrapper for QT logging library. * * Each source file is expected to declare a log category name what is * implicitly used for all logging macros. When logging in header files take * precaution not to pollute global scope. Either log manually or declare the * log within class scope. * Log categories can be structured using dots in name: `machine.core * .decode`. * * @see * https://www.kdab.com/wp-content/uploads/stories/slides/Day2/KaiKoehne_Qt%20Logging%20Framework%2016_9_0.pdf */ #ifndef LOGGING_H #define LOGGING_H #include #define LOG_CATEGORY(NAME) static QLoggingCategory _loging_category_(NAME) #if !defined(QT_NO_QDEBUG_MACRO) #define QT_NO_QDEBUG_MACRO \ while (false) \ QMessageLogger().noDebug #endif #if defined(QT_NO_DEBUG_OUTPUT) #define DEBUG QT_NO_QDEBUG_MACRO #else #define DEBUG(...) qCDebug(_loging_category_, __VA_ARGS__) #endif #define LOG(...) qCInfo(_loging_category_, __VA_ARGS__) #define WARN(...) qCWarning(_loging_category_, __VA_ARGS__) #define ERROR(...) qCCritical(_loging_category_, __VA_ARGS__) #endif ================================================ FILE: src/common/logging_format_colors.h ================================================ #ifndef LOGGING_FORMAT_COLORS_H #define LOGGING_FORMAT_COLORS_H #include static void set_default_log_pattern() { qSetMessagePattern( "%{if-info}\033[34m[INFO] %{endif}" "%{if-debug}\033[37m[DEBUG] %{endif}" "%{if-warning}\033[33m[WARN] %{endif}" "%{if-critical}\033[31m[ERROR] %{endif}" "%{if-fatal}\033[31m[FATAL ERROR] %{endif}" "%{if-category}%{category}:%{endif}" "\033[0m\t%{message}"); } #endif // LOGGING_FORMAT_COLORS_H ================================================ FILE: src/common/math/bit_ops.h ================================================ /** * Bit manipulation library * * @file */ #ifndef QTRVSIM_BIT_OPS_H #define QTRVSIM_BIT_OPS_H #include #include #include using std::size_t; /** * Get value of single bit as lowest bit * * Corresponds to instruction notation `SYMBOL[]`. */ template constexpr inline T get_bit(T val, size_t bit_index) { return (val >> bit_index) & 1; } /** * Generates a bitmask to mask a range of bits. */ template constexpr inline T get_bitmask(size_t start, size_t end) { const size_t len = start - end + 1; return ((1 << len) - 1) << end; } /** * Mask a range of bits in an integer-like value. */ template constexpr inline T mask_bits(T val, size_t start, size_t end) { return val & get_bitmask(start, end); } /** * Extracts range of bits from an integer-like value. * * Corresponds to instruction notation `SYMBOL[start:end]`. */ template constexpr inline T get_bits(T val, size_t start, size_t end) { assert(start >= end); return mask_bits(val >> end, start - end, 0); } /** * Sign extend arbitrary bit range. */ template constexpr inline T sign_extend(T val, size_t size) { size_t T_size = std::numeric_limits::digits; if (std::numeric_limits::is_signed) T_size += 1; size_t shift = T_size - size; return ((int64_t)val << shift) >> shift; } #endif // QTRVSIM_BIT_OPS_H ================================================ FILE: src/common/memory_ownership.h ================================================ /** * Manual memory ownership toolkit. */ #ifndef QTRVSIM_MEMORY_OWNERSHIP_H #define QTRVSIM_MEMORY_OWNERSHIP_H #include /** * Tag for pointer owned by someone else. It is recommended to mention owner * in comment to make lifetimes manually verifiable. */ #define BORROWED /** * Pointer with static lifetime. */ #define STATIC /** * Pointer is owned and managed by Qt. */ #define QT_OWNED /** * Owned pointer deallocated by automatic destructor. */ template using Box = QScopedPointer; #endif // QTRVSIM_MEMORY_OWNERSHIP_H ================================================ FILE: src/common/polyfills/CMakeLists.txt ================================================ project(polyfills DESCRIPTION "Helper code to unify access to compiler intrinsics and provide fallback implementations.") set(polyfills_HEADERS endian_detection.h mulh64.h clz32.h byteswap.h qstring_hash.h qt5/qfontmetrics.h qt5/qlinef.h qt5/qtableview.h ) # ============================================================================= # Target for usage of this header library # - this is header only library and it is implicitly available in the whole # project # ============================================================================= # ============================================================================= # Tests # - It is highly recommended to run these tests anytime you use a different # compiler or platform as this code is platform/compiler specific. # ============================================================================= if(NOT "${WASM}") set(CMAKE_AUTOMOC ON) add_executable(mulh64_test mulh64.h mulh64.test.h mulh64.test.cpp ) target_link_libraries(mulh64_test PRIVATE ${QtLib}::Test) add_test(NAME mulh64 COMMAND mulh64_test) endif() ================================================ FILE: src/common/polyfills/byteswap.h ================================================ #ifndef BYTESWAP_H #define BYTESWAP_H /* Define byte-swap functions, using fast processor-native built-ins where * possible */ #if defined(_MSC_VER) // needs to be first because msvc doesn't short-circuit // after failing defined(__has_builtin) #define bswap16(x) _byteswap_ushort((x)) #define bswap32(x) _byteswap_ulong((x)) #define bswap64(x) _byteswap_uint64((x)) #elif (__GNUC__ > 4) || (__GNUC__ == 4 && __GNUC_MINOR__ >= 8) #define bswap16(x) __builtin_bswap16((x)) #define bswap32(x) __builtin_bswap32((x)) #define bswap64(x) __builtin_bswap64((x)) #elif defined(__has_builtin) && __has_builtin(__builtin_bswap64) /* for clang; gcc 5 fails on this \ and \ && short circuit fails; must \ be after GCC check */ #define bswap16(x) __builtin_bswap16((x)) #define bswap32(x) __builtin_bswap32((x)) #define bswap64(x) __builtin_bswap64((x)) #else /* even in this case, compilers often optimize by using native instructions */ static inline uint16_t bswap16(uint16_t x) { return (val << 8) | (val >> 8); } static inline uint32_t bswap32(uint32_t x) { val = ((val << 8) & 0xFF00FF00) | ((val >> 8) & 0xFF00FF); return (val << 16) | (val >> 16); } static inline uint64_t bswap64(uint64_t x) { val = ((val << 8) & 0xFF00FF00FF00FF00ULL) | ((val >> 8) & 0x00FF00FF00FF00FFULL); val = ((val << 16) & 0xFFFF0000FFFF0000ULL) | ((val >> 16) & 0x0000FFFF0000FFFFULL); return (val << 32) | (val >> 32); } #endif #endif // BYTESWAP_H ================================================ FILE: src/common/polyfills/clz32.h ================================================ #ifndef CLZ32_H #define CLZ32_H #if defined(__GNUC__) && __GNUC__ >= 4 static inline uint32_t clz32(uint32_t n) { int intbits = sizeof(int) * CHAR_BIT; if (n == 0) { return 32; } return __builtin_clz(n) - (intbits - 32); } #else /* Fallback for generic compiler */ // see https://en.wikipedia.org/wiki/Find_first_set#CLZ static const uint8_t sig_table_4bit[16] = { 0, 1, 2, 2, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4 }; static inline uint32_t clz32(uint32_t n) { int len = 32; if (n & 0xFFFF0000) { len -= 16; n >>= 16; } if (n & 0xFF00) { len -= 8; n >>= 8; } if (n & 0xF0) { len -= 4; n >>= 4; } len -= sig_table_4bit[n]; return len; } #endif #endif // CLZ32_H ================================================ FILE: src/common/polyfills/endian_detection.h ================================================ #ifndef ENDIAN_DETECTION_H #define ENDIAN_DETECTION_H /** * Including this file ensures that GCC macros `__LITTLE_ENDIAN__` or * `__BIG_ENDIAN__` are defined regardless of OS and compiler. If used * system/compiler is not supported, it will stop the compilation using a * static assert. Precisely one of the given macros is always guaranteed to be * defined. * * @file */ // Cross-platform endian detection source // https://gist.github.com/jtbr/7a43e6281e6cca353b33ee501421860c (MIT license) #ifdef __has_include // C++17, supported as extension to C++11 in clang, GCC 5+, // vs2015 #if __has_include() #include // gnu libc normally provides, linux #elif __has_include() #include //open bsd, macos #elif __has_include() #include // mingw, some bsd (not open/macos) #elif __has_include() #include // solaris #endif #endif #if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__) #if (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__) \ || (defined(__BYTE_ORDER) && __BYTE_ORDER == __BIG_ENDIAN) \ || (defined(_BYTE_ORDER) && _BYTE_ORDER == _BIG_ENDIAN) \ || (defined(BYTE_ORDER) && BYTE_ORDER == BIG_ENDIAN) \ || (defined(__sun) && defined(__SVR4) && defined(_BIG_ENDIAN)) || defined(__ARMEB__) \ || defined(__THUMBEB__) || defined(__AARCH64EB__) || defined(_MIBSEB) || defined(__MIBSEB) \ || defined(__MIBSEB__) || defined(_M_PPC) #define __BIG_ENDIAN__ #elif (defined(__BYTE_ORDER__) && __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__) || /* gcc */ \ (defined(__BYTE_ORDER) && __BYTE_ORDER == __LITTLE_ENDIAN) /* linux \ header \ */ \ || (defined(_BYTE_ORDER) && _BYTE_ORDER == _LITTLE_ENDIAN) \ || (defined(BYTE_ORDER) && BYTE_ORDER == LITTLE_ENDIAN) /* mingw \ header */ \ || (defined(__sun) && defined(__SVR4) && defined(_LITTLE_ENDIAN)) || /* solaris */ \ defined(__ARMEL__) || defined(__THUMBEL__) || defined(__AARCH64EL__) || defined(_MIPSEL) \ || defined(__MIPSEL) || defined(__MIPSEL__) || defined(_M_IX86) || defined(_M_X64) \ || defined(_M_IA64) || /* msvc for intel \ processors */ \ defined(_M_ARM) /* msvc code on arm executes in little endian mode */ #define __LITTLE_ENDIAN__ #endif #endif #if !defined(__LITTLE_ENDIAN__) && !defined(__BIG_ENDIAN__) static_assert( false, "Current compiler/system is not supported by the endian " "detection polyfill code or it uses unsupported endian.\n" "Supported endians are: BIG | LITTLE."); #endif #if defined(__LITTLE_ENDIAN__) && defined(__BIG_ENDIAN__) static_assert( false, "Both endianness macros are defined. This is a bug of endian " "detection. Please report it via a github issue."); #endif #endif // ENDIAN_DETECTION_H ================================================ FILE: src/common/polyfills/mulh64.h ================================================ /** * Provides set of functions to calculate high bits of 64 bit multiplication. * Naming reflects RISC-V specs. * * `mulh64` = signed x signed * `mulhu64` = unsigned x unsigned * `mulhsu64` = signed x unsigned * * @file */ #ifndef MULH64_H #define MULH64_H #include /** * Get high bits of 64 bit unsigned multiplication. * * Source (BSD-3 in the referred project): * https://stackoverflow.com/questions/28868367/getting-the-high-part-of-64-bit-integer-multiplication */ static inline constexpr uint64_t mulhu64_fallback(uint64_t a, uint64_t b) { const uint64_t a_lower = (uint32_t)a; const uint64_t a_upper = (uint32_t)(a >> 32); const uint64_t b_lower = (uint32_t)b; const uint64_t b_upper = (uint32_t)(b >> 32); const uint64_t p11 = a_upper * b_upper, p01 = a_lower * b_upper; const uint64_t p10 = a_upper * b_lower, p00 = a_lower * b_lower; /* This is implementing schoolbook multiplication: x1 x0 X y1 y0 ------------- 00 LOW PART ------------- 00 10 10 MIDDLE PART + 01 ------------- 01 + 11 11 HIGH PART ------------- */ const uint64_t middle = p10 + (p00 >> 32) + (uint32_t)p01; return p11 + (middle >> 32) + (p01 >> 32); } /** * mulhu64_fallback modified for signed multiplication * * SIGN marks show where sign extension has been added. * `*_upper` variables are considered signed and everything multiplied with them * is also signed. * * We use the fact, that in most compilers right shift of signed value is done * by arithmetic shift. */ static inline constexpr uint64_t mulh64_fallback(int64_t a, int64_t b) { const uint64_t a_lower = (uint32_t)a; const int64_t a_upper = (int32_t)(a >> 32); // SIGN const uint64_t b_lower = (uint32_t)b; const int64_t b_upper = (int32_t)(b >> 32); // SIGN const int64_t p11 = a_upper * b_upper; const int64_t p01 = a_lower * b_upper; const int64_t p10 = a_upper * b_lower; const uint64_t p00 = a_lower * b_lower; const int64_t middle = p10 + (p00 >> 32) + (uint32_t)p01; return p11 + (middle >> 32) + (p01 >> 32); // SIGN } /** * mulh64_fallback modified for mixed sign multiplication * * SIGN marks show where sign extension has been added. * `a_upper` is considered signed and everything multiplied with it is also * signed. * * We use the fact, that in most compilers right shift of signed value is done * by arithmetic shift. */ static inline constexpr uint64_t mulhsu64_fallback(int64_t a, uint64_t b) { const uint64_t a_lower = (uint32_t)a; const int64_t a_upper = (int32_t)(a >> 32); // SIGN const uint64_t b_lower = (uint32_t)b; const uint64_t b_upper = (uint32_t)(b >> 32); const int64_t p11 = a_upper * b_upper; const uint64_t p01 = a_lower * b_upper; const int64_t p10 = a_upper * b_lower; const uint64_t p00 = a_lower * b_lower; const int64_t middle = p10 + (p00 >> 32) + (uint32_t)p01; return p11 + (middle >> 32) + (p01 >> 32); // SIGN } #if defined(__SIZEOF_INT128__) // GNU C static inline constexpr uint64_t mulh64(int64_t a, int64_t b) { unsigned __int128 prod = (unsigned __int128)a * (unsigned __int128)b; return (uint64_t)(prod >> 64); } static inline constexpr uint64_t mulhu64(uint64_t a, uint64_t b) { unsigned __int128 prod = (unsigned __int128)a * (unsigned __int128)b; return (uint64_t)(prod >> 64); } static inline constexpr uint64_t mulhsu64(int64_t a, uint64_t b) { unsigned __int128 prod = (unsigned __int128)a * (unsigned __int128)b; return (uint64_t)(prod >> 64); } #elif defined(_M_X64) || defined(_M_ARM64) // MSVC // MSVC for x86-64 or AArch64 // possibly also || defined(_M_IA64) || defined(_WIN64) // but the docs only guarantee x86-64! Don't use *just* _WIN64; it doesn't // include AArch64 Android / Linux // https://docs.microsoft.com/en-gb/cpp/intrinsics/arm64-intrinsics #include // https://docs.microsoft.com/en-gb/cpp/intrinsics/umulh #define mulhu64 __umulh // https://docs.microsoft.com/en-gb/cpp/intrinsics/mulh #define mulh64 __mulh // Not provided by MVSC #define mulhsu64 mulhsu64_fallback #else #define mulh64 mulh64_fallback #define mulhu64 mulhu64_fallback #define mulhsu64 mulhsu64_fallback #endif #endif // MULH64_H ================================================ FILE: src/common/polyfills/mulh64.test.cpp ================================================ #include "mulh64.h" #include "mulh64.test.h" void TestMULH64::test_mulh64() { QCOMPARE(mulh64(15, 10), (uint64_t)0); QCOMPARE(mulh64(15, -10), (uint64_t)0xffffffffffffffffULL); QCOMPARE(mulh64(-10, 15), (uint64_t)0xffffffffffffffffULL); QCOMPARE(mulh64(-15, -10), (uint64_t)0); } void TestMULH64::test_mulhu64() { QCOMPARE(mulhu64(15, 10), (uint64_t)0); QCOMPARE(mulhu64(15, -10), (uint64_t)14); QCOMPARE(mulhu64(-10, 15), (uint64_t)14); QCOMPARE(mulhu64(-10, -15), (uint64_t)0xffffffffffffffe7ULL); } void TestMULH64::test_mulhsu64() { QCOMPARE(mulhsu64(15, 10), (uint64_t)0); QCOMPARE(mulhsu64(15, -10), (uint64_t)14); QCOMPARE(mulhsu64(-10, 15), (uint64_t)0xffffffffffffffffULL); QCOMPARE(mulhsu64(-15, -10), (uint64_t)0xfffffffffffffff1ULL); } void TestMULH64::test_mulh64_fallback() { QCOMPARE(mulh64_fallback(15, 10), (uint64_t)0); QCOMPARE(mulh64_fallback(15, -10), (uint64_t)0xffffffffffffffffULL); QCOMPARE(mulh64_fallback(-10, 15), (uint64_t)0xffffffffffffffffULL); QCOMPARE(mulh64_fallback(-15, -10), (uint64_t)0); } void TestMULH64::test_mulhu64_fallback() { QCOMPARE(mulhu64_fallback(15, 10), (uint64_t)0); QCOMPARE(mulhu64_fallback(15, -10), (uint64_t)14); QCOMPARE(mulhu64_fallback(-10, 15), (uint64_t)14); QCOMPARE(mulhu64_fallback(-10, -15), (uint64_t)0xffffffffffffffe7ULL); } void TestMULH64::test_mulhsu64_fallback() { QCOMPARE(mulhsu64_fallback(15, 10), (uint64_t)0); QCOMPARE(mulhsu64_fallback(15, -10), (uint64_t)14); QCOMPARE(mulhsu64_fallback(-10, 15), (uint64_t)0xffffffffffffffffULL); QCOMPARE(mulhsu64_fallback(-15, -10), (uint64_t)0xfffffffffffffff1ULL); } QTEST_APPLESS_MAIN(TestMULH64) ================================================ FILE: src/common/polyfills/mulh64.test.h ================================================ #ifndef MULH64_TEST_H #define MULH64_TEST_H #include #include class TestMULH64 : public QObject { Q_OBJECT private slots: static void test_mulh64(); static void test_mulhu64(); static void test_mulhsu64(); static void test_mulh64_fallback(); static void test_mulhu64_fallback(); static void test_mulhsu64_fallback(); }; #endif // MULH64_TEST_H ================================================ FILE: src/common/polyfills/qstring_hash.h ================================================ #ifndef QTRVSIM_QSTRING_HASH_H #define QTRVSIM_QSTRING_HASH_H #include #include #if QT_VERSION < QT_VERSION_CHECK(5, 14, 0) namespace std { template<> struct hash { std::size_t operator()(const QString &s) const noexcept { return qHash(s); } }; #if QT_VERSION >= QT_VERSION_CHECK(5, 10, 0) template<> struct hash { std::size_t operator()(const QStringView &s) const noexcept { return qHash(s); } }; #endif } // namespace std #if QT_VERSION < QT_VERSION_CHECK(5, 10, 0) // Supported since Qt5.10 using QStringView = QString; #endif #endif #endif // QTRVSIM_QSTRING_HASH_H ================================================ FILE: src/common/polyfills/qt5/qfontmetrics.h ================================================ #ifndef POLYFILLS_QFONTMETRICS_H #define POLYFILLS_QFONTMETRICS_H int QFontMetrics_horizontalAdvance(const QFontMetrics &self, const QString &str, int len = -1) { #if QT_VERSION >= QT_VERSION_CHECK(5, 11, 0) return self.horizontalAdvance(str, len); #else return self.width(str, len); #endif } #endif // QTMIPS_QFONTMETRICS_H ================================================ FILE: src/common/polyfills/qt5/qlinef.h ================================================ #ifndef POLYFILLS_QLINEF_H #define POLYFILLS_QLINEF_H #include QLineF::IntersectType QLineF_intersect(const QLineF &l1, const QLineF &l2, QPointF *intersectionPoint) { #if QT_VERSION >= QT_VERSION_CHECK(5, 14, 0) return l1.intersects(l2, intersectionPoint); #else return l1.intersect(l2, intersectionPoint); #endif } #endif // QTMIPS_QLINEF_H ================================================ FILE: src/common/polyfills/qt5/qtableview.h ================================================ #ifndef POLYFILLS_QTABLEVIEW_H #define POLYFILLS_QTABLEVIEW_H #include /** * QTableView polyfill * * initViewItemOption is protected, therefore the whole class needs to be wrapped. */ class Poly_QTableView : public QTableView { public: explicit Poly_QTableView(QWidget *parent) : QTableView(parent) {} #if QT_VERSION < QT_VERSION_CHECK(6, 0, 0) protected: void initViewItemOption(QStyleOptionViewItem *viewOpts) { *viewOpts = QTableView::viewOptions(); } #endif }; #endif // QTMIPS_QTABLEVIEW_H ================================================ FILE: src/common/string_utils.h ================================================ #ifndef QTRVSIM_STRING_UTILS_H #define QTRVSIM_STRING_UTILS_H #include namespace str { template QString asHex(T number) { if (number < 0) { return QString::asprintf("-0x%x", -number); } else { return QString::asprintf("0x%x", number); } } } // namespace str #endif // QTRVSIM_STRING_UTILS_H ================================================ FILE: src/common/type_utils/lens.h ================================================ /** * Tiny lenses library * * TL;DR: Getter function which works on nested structs. * * Example: * ``` * struct A { * int b, * struct { * int d; * } c; * } * * Lens lens = LENS(A, c.d); * ``` * `Lens` is a function pointer to a function with a signature * `const A& -> const int&` * int`, i.e., it takes a reference to an instance of type A and "shows" you * something somewhere inside the object -- the integer `d`. * * In functional programming, the term lens is used and a generalized and * more flexible form of getter method. First, it is a function. Second, it * works on arbitrarily nested structures and it hides the inner structure. * * @file */ #ifndef QTRVSIM_LENS_H #define QTRVSIM_LENS_H template using Lens = const FIELD_TYPE &(*const)(const BASE &); #define LENS(BASE_TYPE, MEMBER) ([](const BASE_TYPE &base) -> const auto & { return base.MEMBER; }) template using LensPair = std::pair (*const)(const BASE &); #define LENS_PAIR(BASE_TYPE, MEMBER_A, MEMBER_B) \ [](const BASE_TYPE &base) -> std::pair { \ return { base.MEMBER_A, base.MEMBER_B }; \ } #endif // QTRVSIM_LENS_H ================================================ FILE: src/gui/CMakeLists.txt ================================================ project(gui LANGUAGES C CXX VERSION ${MAIN_PROJECT_VERSION} DESCRIPTION "Graphical UI for the simulator") set(CMAKE_AUTOMOC ON) set(CMAKE_AUTORCC ON) set(CMAKE_AUTOUIC ON) set(gui_SOURCES dialogs/about/aboutdialog.cpp windows/cache/cachedock.cpp windows/cache/cacheview.cpp windows/csr/csrdock.cpp windows/coreview/scene.cpp extprocess.cpp fontsize.cpp dialogs/gotosymbol/gotosymboldialog.cpp graphicsview.cpp windows/memory/memorydock.cpp windows/memory/memorymodel.cpp windows/memory/memorytableview.cpp windows/messages/messagesdock.cpp windows/messages/messagesmodel.cpp windows/messages/messagesview.cpp dialogs/new/newdialog.cpp ui/hexlineedit.cpp ui/pow2spinbox.cpp windows/tlb/tlbview.cpp windows/tlb/tlbdock.cpp windows/editor/highlighterasm.cpp windows/editor/highlighterc.cpp windows/editor/linenumberarea.cpp windows/editor/editordock.cpp windows/editor/editortab.cpp hinttabledelegate.cpp windows/lcd/lcddisplaydock.cpp windows/lcd/lcddisplayview.cpp main.cpp mainwindow/mainwindow.cpp windows/peripherals/peripheralsdock.cpp windows/peripherals/peripheralsview.cpp windows/program/programdock.cpp windows/program/programmodel.cpp windows/program/programtableview.cpp windows/registers/registersdock.cpp dialogs/savechanged/savechangeddialog.cpp windows/editor/srceditor.cpp statictable.cpp windows/terminal/terminaldock.cpp textsignalaction.cpp windows/coreview/components/value_handlers.cpp windows/coreview/components/cache.cpp widgets/hidingtabwidget.cpp windows/predictor/predictor_btb_dock.cpp windows/predictor/predictor_bht_dock.cpp windows/predictor/predictor_info_dock.cpp ) set(gui_HEADERS dialogs/about/aboutdialog.h windows/cache/cachedock.h windows/cache/cacheview.h windows/csr/csrdock.h windows/coreview/scene.h extprocess.h fontsize.h dialogs/gotosymbol/gotosymboldialog.h graphicsview.h windows/memory/memorydock.h windows/memory/memorymodel.h windows/memory/memorytableview.h windows/messages/messagesdock.h windows/messages/messagesmodel.h windows/messages/messagesview.h dialogs/new/newdialog.h ui/hexlineedit.h ui/pow2spinbox.h windows/tlb/tlbview.h windows/tlb/tlbdock.h windows/editor/highlighterasm.h windows/editor/highlighterc.h windows/editor/linenumberarea.h windows/editor/editordock.h hinttabledelegate.h windows/lcd/lcddisplaydock.h windows/lcd/lcddisplayview.h mainwindow/mainwindow.h windows/peripherals/peripheralsdock.h windows/peripherals/peripheralsview.h windows/program/programdock.h windows/program/programmodel.h windows/program/programtableview.h windows/registers/registersdock.h dialogs/savechanged/savechangeddialog.h windows/editor/srceditor.h statictable.h windows/terminal/terminaldock.h textsignalaction.h windows/coreview/components/value_handlers.h windows/coreview/data.h windows/coreview/components/cache.h helper/async_modal.h widgets/hidingtabwidget.h windows/predictor/predictor_btb_dock.h windows/predictor/predictor_bht_dock.h windows/predictor/predictor_info_dock.h ) set(gui_UI dialogs/gotosymbol/gotosymboldialog.ui dialogs/new/NewDialog.ui windows/peripherals/peripheralsview.ui mainwindow/MainWindow.ui dialogs/new/NewDialogCache.ui ) set(gui_RESOURCES resources/icons/icons.qrc resources/samples/samples.qrc windows/coreview/schemas/schemas.qrc ) if ("${WASM}") message(STATUS "gui :: Including WASM only files.") list(APPEND gui_SOURCES qhtml5file_html5.cpp) list(APPEND gui_HEADERS qhtml5file.h) endif () # MACOS set(ICON_NAME gui) set(ICON_PATH ${CMAKE_SOURCE_DIR}/data/icons/macos/${ICON_NAME}.icns) # END MACOS add_executable(gui ${ICON_PATH} ${gui_SOURCES} ${gui_HEADERS} ${gui_UI} ${gui_RESOURCES}) target_include_directories(gui PUBLIC . windows/coreview) target_link_libraries(gui PRIVATE ${QtLib}::Core ${QtLib}::Widgets ${QtLib}::Gui PRIVATE machine os_emulation assembler svgscene) target_compile_definitions(gui PRIVATE APP_ORGANIZATION=\"${MAIN_PROJECT_ORGANIZATION}\" APP_ORGANIZATION_DOMAIN=\"${MAIN_PROJECT_HOMEPAGE_URL}\" APP_GIT=\"${MAIN_PROJECT_HOMEPAGE_URL}\" APP_NAME=\"${MAIN_PROJECT_NAME}\" APP_VERSION=\"${MAIN_PROJECT_VERSION}\" APP_USER_MANUAL_URL=\"https://comparch.edu.cvut.cz/qtrvsim/manual\" APP_REPORT_PROBLEM_URL=\"https://github.com/cvut/qtrvsim/issues/new\" ENV_CONFIG_FILE_NAME=\"${MAIN_PROJECT_NAME_UPPER}_CONFIG_FILE\") set_target_properties(gui PROPERTIES OUTPUT_NAME "${MAIN_PROJECT_NAME_LOWER}_${PROJECT_NAME}") if (${${QtLib}PrintSupport_FOUND} AND NOT ${WASM}) target_link_libraries(gui PRIVATE ${QtLib}::PrintSupport) target_compile_definitions(gui PRIVATE WITH_PRINTING=1) endif () # MACOS set_property(SOURCE ${ICON_PATH} PROPERTY MACOSX_PACKAGE_LOCATION Resources) set_target_properties(gui PROPERTIES MACOSX_BUNDLE true MACOSX_BUNDLE_GUI_IDENTIFIER cz.cvut.fel.${MAIN_PROJECT_ORGANIZATION}.gui MACOSX_BUNDLE_BUNDLE_NAME ${MAIN_PROJECT_NAME} MACOSX_BUNDLE_BUNDLE_VERSION "${MAIN_PROJECT_VERSION}" MACOSX_BUNDLE_SHORT_VERSION_STRING "${MAIN_PROJECT_VERSION}" MACOSX_BUNDLE_ICONFILE ${ICON_NAME} ) # END MACOS # ============================================================================= # Installation # ============================================================================= # Prior to CMake version 3.13, installation must be performed in the subdirectory, # there the target was created. Therefore executable installation is to be found # in corresponding CMakeLists.txt. install(TARGETS gui RUNTIME DESTINATION bin BUNDLE DESTINATION ${EXECUTABLE_OUTPUT_PATH} ) ================================================ FILE: src/gui/dialogs/about/aboutdialog.cpp ================================================ #include "aboutdialog.h" #include "project_info.h" #include #include #include #include #include #include #include AboutDialog::AboutDialog(QWidget *parent) : QDialog(parent) { QLabel *lbl; setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_ShowModal); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setWindowTitle(tr("About " APP_NAME)); all = new QVBoxLayout(this); auto *hbox = new QWidget(); auto *hl = new QHBoxLayout(hbox); hl->setContentsMargins(0, 0, 0, 0); all->addWidget(hbox); auto *vbox = new QWidget(); auto *vl = new QVBoxLayout(vbox); hl->addWidget(vbox); QString versionText; versionText = "Version " APP_VERSION "\n"; vl->addWidget(new QLabel( "" APP_NAME " " "- RISC-V Architecture Simulator")); lbl = new QLabel(versionText); lbl->setAlignment(Qt::AlignHCenter); lbl->setOpenExternalLinks(true); vl->addWidget(lbl); vl->addWidget(new QLabel(COPYRIGHT_HTML)); QString supportText; supportText = "Home Page : " APP_GIT "
" "Implemented for Computer Architectures and Advanced Computer Architectures courses " "at Czech Technical " "University in Prague" " Faculty of Electrical " "Engineering
" "QtRvSim on-line version and links to course materials at
" "https://comparch.edu.cvut.cz/
"; auto *supportBrowser = new QTextBrowser; supportBrowser->setOpenExternalLinks(true); supportBrowser->setHtml(supportText); vl->addWidget(supportBrowser); auto *licenseBrowser = new QTextBrowser; licenseBrowser->setOpenExternalLinks(true); licenseBrowser->setHtml(LICENCE_HTML); vl->addWidget(licenseBrowser); auto *hbBtn = new QWidget(); auto *hlBtn = new QHBoxLayout(hbBtn); hlBtn->setContentsMargins(0, 0, 0, 0); vl->addWidget(hbBtn); auto *okButton = new QPushButton(tr("&OK"), parent); okButton->setFocus(); connect(okButton, &QAbstractButton::clicked, this, &QWidget::close); hlBtn->addStretch(); hlBtn->addWidget(okButton); setMinimumSize(480, 500); // the first Tab is selected by default } ================================================ FILE: src/gui/dialogs/about/aboutdialog.h ================================================ #ifndef ABOUTDIALOG_H #define ABOUTDIALOG_H #include #include #include #include class QString; class QTextBrowser; class AboutDialog : public QDialog { Q_OBJECT public: explicit AboutDialog(QWidget *parent = nullptr); private: QVBoxLayout *all; }; #endif ================================================ FILE: src/gui/dialogs/gotosymbol/gotosymboldialog.cpp ================================================ #include "gotosymboldialog.h" #include "ui_gotosymboldialog.h" GoToSymbolDialog::GoToSymbolDialog(QWidget *parent, const QStringList &symbol_names) : QDialog(parent) , ui(new Ui::GoToSymbolDialog) { ui->setupUi(this); connect(ui->pushShowProg, &QAbstractButton::clicked, this, &GoToSymbolDialog::show_prog); connect(ui->pushShowMem, &QAbstractButton::clicked, this, &GoToSymbolDialog::show_mem); connect(ui->pushClose, &QAbstractButton::clicked, this, &QWidget::close); ui->listSymbols->addItems(symbol_names); } void GoToSymbolDialog::show_prog() { uint64_t address = 0; emit obtain_value_for_name(address, ui->listSymbols->currentItem()->text()); emit program_focus_addr(machine::Address(address)); } void GoToSymbolDialog::show_mem() { uint64_t address = 0; emit obtain_value_for_name(address, ui->listSymbols->currentItem()->text()); emit memory_focus_addr(machine::Address(address)); } ================================================ FILE: src/gui/dialogs/gotosymbol/gotosymboldialog.h ================================================ #ifndef GOTOSYMBOLDIALOG_H #define GOTOSYMBOLDIALOG_H #include "common/memory_ownership.h" #include "machine/memory/address.h" #include "ui_gotosymboldialog.h" #include #include #include class GoToSymbolDialog : public QDialog { Q_OBJECT public: GoToSymbolDialog(QWidget *parent, const QStringList &symbol_names); signals: void program_focus_addr(machine::Address); void memory_focus_addr(machine::Address); bool obtain_value_for_name(uint64_t &value, const QString &name) const; public slots: void show_prog(); void show_mem(); private: Box ui {}; }; #endif // GOTOSYMBOLDIALOG_H ================================================ FILE: src/gui/dialogs/gotosymbol/gotosymboldialog.ui ================================================ GoToSymbolDialog 0 0 400 331 Go To Symbol DIalog QLayout::SetMaximumSize Show program Qt::Horizontal 40 20 Show memory Qt::Horizontal 40 20 Close listSymbols pushShowProg pushShowMem pushClose ================================================ FILE: src/gui/dialogs/new/NewDialog.ui ================================================ NewDialog 0 0 745 472 Dialog true 0 0 0 135 0 Config Pages 0 0 4 0 Page Name Presets and ELF File Preset No pipeline no cache No pipeline with cache Pipelined without hazard unit and without cache Pipelined with hazard unit and cache Custom Reset at compile time (reload after make) Qt::Vertical 20 40 Qt::Horizontal Elf executable: Browse Core ISA and Hazards Pipelined XLEN 64-bit Atomic (A) Delay slot Multiply (M) Hazard unit true false Stall when hazard is detected Stall or forward when hazard is detected true Qt::Vertical 20 40 Branch Predictor Branch Predictor true true 0 Predictor type: 0 Initial state: Branch Target Buffer (BTB) false Qt::Horizontal 40 0 0 Qt::AlignCenter PC - Program Counter register Bits from PC address: 8 Qt::Horizontal false QSlider::TicksBothSides 140 0 Number of entries: 40 0 1 Qt::AlignCenter Qt::Horizontal 40 20 Qt::Vertical 140 0 Number of bits: 40 0 0 Qt::AlignCenter Branch History Table (BHT) 40 0 0 Qt::AlignCenter 40 0 0 Qt::AlignCenter 8 Qt::Horizontal QSlider::TicksBothSides 8 Qt::Horizontal QSlider::TicksBothSides 140 0 BHR - Branch History Register Bits in BHR: Qt::Horizontal 140 0 PC - Program Counter register Bits from PC address: 140 0 Number of entries: 40 0 1 Qt::AlignCenter Qt::Horizontal 40 20 Qt::Vertical 140 0 Number of bits: 40 0 0 Qt::AlignCenter Qt::Vertical 20 40 Virtual Memory true Virtual Memory true true true Program Translation Lookaside Buffer (TLB) false Number of sets: 1 1024 1 QAbstractSpinBox::DefaultStepType 1 10 Degree of associativity: 1 1 1 Replacement policy: Random Least Recently Used (LRU) Least Frequently Used (LFU) Pseudo Least Recently Used (PLRU) true Data Translation Lookaside Buffer (TLB) false Number of sets: 1 1024 1 QAbstractSpinBox::DefaultStepType 1 10 Degree of associativity: 1 1 1 Replacement policy: Random Least Recently Used (LRU) Least Frequently Used (LFU) Pseudo Least Recently Used (PLRU) Qt::Vertical 21 40 Memory Timings Program memory write protection Data memory executable protection Access time (in cycles) Read: 1 999999999 Write: 1 999999999 Burst enable: false Burst: 0 999999999 L2 Access: 0 999999999 Qt::Vertical 20 40 L1 Program Cache L1 Data Cache L2 Cache System Emulation and IRQ Enable emulation of operating system services true Stop on known system call true Stop on unknown system call true Stop on interrupt entry true Stop and step over exceptions (overflow, etc.) true Filesystem root: Browse Qt::Vertical 21 40 9 0 9 0 Qt::Horizontal 40 20 Example Start empty Load machine true Cancel Pow2SpinBox QSpinBox
ui/pow2spinbox.h
================================================ FILE: src/gui/dialogs/new/NewDialogCache.ui ================================================ NewDialogCache 0 0 435 204 Form Enable cache true Number of sets: 1 1024 Block size: 1 Degree of associativity: 1 Replacement policy: Random Least Recently Used (LRU) Least Frequently Used (LFU) Pseudo Least Recently Used (PLRU) Not Most Recently Used (NMRU) Writeback policy: Write through - noallocate Write through - write allocate Write back Qt::Vertical 20 40 ================================================ FILE: src/gui/dialogs/new/newdialog.cpp ================================================ #include "newdialog.h" #include "helper/async_modal.h" #include "machine/simulator_exception.h" #include "mainwindow/mainwindow.h" #include #ifdef __EMSCRIPTEN__ #include "qhtml5file.h" #include #endif NewDialog::NewDialog(QWidget *parent, QSettings *settings) : QDialog(parent) { setWindowTitle("New machine"); this->settings = settings; config.reset(); ui.reset(new Ui::NewDialog()); ui->setupUi(this); ui_cache_p.reset(new Ui::NewDialogCache()); ui_cache_p->setupUi(ui->tab_cache_program); ui_cache_p->writeback_policy->hide(); ui_cache_p->label_writeback->hide(); ui_cache_d.reset(new Ui::NewDialogCache()); ui_cache_d->setupUi(ui->tab_cache_data); ui_cache_l2.reset(new Ui::NewDialogCache()); ui_cache_l2->setupUi(ui->tab_cache_level2); QList config_pages_items; for (int i = 0; i < ui->config_pages->count(); ++i) { QString page_id = ui->config_pages->widget(i)->objectName(); QString page_name = ui->config_pages->widget(i)->accessibleName(); config_pages_items.append(new QTreeWidgetItem( static_cast(nullptr), QStringList { page_name, page_id })); } ui->page_select_tree->insertTopLevelItems(0, config_pages_items); connect(ui->page_select_tree, &QTreeWidget::currentItemChanged, this, &NewDialog::switch2page); connect(ui->pushButton_example, &QAbstractButton::clicked, this, &NewDialog::create_example); connect(ui->pushButton_start_empty, &QAbstractButton::clicked, this, &NewDialog::create_empty); connect(ui->pushButton_load, &QAbstractButton::clicked, this, &NewDialog::create); connect(ui->pushButton_cancel, &QAbstractButton::clicked, this, &NewDialog::cancel); connect(ui->pushButton_browse, &QAbstractButton::clicked, this, &NewDialog::browse_elf); connect(ui->elf_file, &QLineEdit::textChanged, this, &NewDialog::elf_change); connect(ui->preset_no_pipeline, &QAbstractButton::toggled, this, &NewDialog::set_preset); connect(ui->preset_no_pipeline_cache, &QAbstractButton::toggled, this, &NewDialog::set_preset); connect(ui->preset_pipelined_bare, &QAbstractButton::toggled, this, &NewDialog::set_preset); connect(ui->preset_pipelined, &QAbstractButton::toggled, this, &NewDialog::set_preset); connect( ui->reset_at_compile, &QAbstractButton::clicked, this, &NewDialog::reset_at_compile_change); connect(ui->xlen_64bit, &QAbstractButton::clicked, this, &NewDialog::xlen_64bit_change); connect(ui->isa_atomic, &QAbstractButton::clicked, this, &NewDialog::isa_atomic_change); connect(ui->isa_multiply, &QAbstractButton::clicked, this, &NewDialog::isa_multiply_change); connect(ui->pipelined, &QAbstractButton::clicked, this, &NewDialog::pipelined_change); connect(ui->delay_slot, &QAbstractButton::clicked, this, &NewDialog::delay_slot_change); connect(ui->hazard_unit, &QGroupBox::clicked, this, &NewDialog::hazard_unit_change); connect(ui->hazard_stall, &QAbstractButton::clicked, this, &NewDialog::hazard_unit_change); connect( ui->hazard_stall_forward, &QAbstractButton::clicked, this, &NewDialog::hazard_unit_change); connect( ui->mem_protec_exec, &QAbstractButton::clicked, this, &NewDialog::mem_protec_exec_change); connect( ui->mem_protec_write, &QAbstractButton::clicked, this, &NewDialog::mem_protec_write_change); connect( ui->mem_time_read, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_read_change); connect( ui->mem_time_write, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_write_change); connect( ui->mem_enable_burst, &QAbstractButton::clicked, this, &NewDialog::mem_enable_burst_change); connect( ui->mem_time_burst, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_burst_change); connect( ui->mem_time_level2, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::mem_time_level2_change); connect(ui->osemu_enable, &QAbstractButton::clicked, this, &NewDialog::osemu_enable_change); connect( ui->osemu_known_syscall_stop, &QAbstractButton::clicked, this, &NewDialog::osemu_known_syscall_stop_change); connect( ui->osemu_unknown_syscall_stop, &QAbstractButton::clicked, this, &NewDialog::osemu_unknown_syscall_stop_change); connect( ui->osemu_interrupt_stop, &QAbstractButton::clicked, this, &NewDialog::osemu_interrupt_stop_change); connect( ui->osemu_exception_stop, &QAbstractButton::clicked, this, &NewDialog::osemu_exception_stop_change); connect( ui->osemu_fs_root_browse, &QAbstractButton::clicked, this, &NewDialog::browse_osemu_fs_root); connect(ui->osemu_fs_root, &QLineEdit::textChanged, this, &NewDialog::osemu_fs_root_change); // Branch predictor connect( ui->group_bp, QOverload::of(&QGroupBox::toggled), this, &NewDialog::bp_enabled_change); connect( ui->select_bp_type, QOverload::of(&QComboBox::activated), this, &NewDialog::bp_type_change); connect( ui->select_bp_init_state, QOverload::of(&QComboBox::activated), this, &NewDialog::bp_init_state_change); connect( ui->slider_bp_btb_addr_bits, &QAbstractSlider::valueChanged, this, &NewDialog::bp_btb_addr_bits_change); connect( ui->slider_bp_bht_bhr_bits, &QAbstractSlider::valueChanged, this, &NewDialog::bp_bht_bhr_bits_change); connect( ui->slider_bp_bht_addr_bits, &QAbstractSlider::valueChanged, this, &NewDialog::bp_bht_addr_bits_change); // Virtual Memory connect( ui->group_vm, QOverload::of(&QGroupBox::toggled), this, &NewDialog::vm_enabled_change); connect( ui->itlb_number_of_sets, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::itlb_num_sets_changed); connect( ui->itlb_degree_of_associativity, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::itlb_assoc_changed); connect( ui->itlb_replacement_policy, QOverload::of(&QComboBox::activated), this, &NewDialog::itlb_policy_changed); connect( ui->dtlb_number_of_sets, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::dtlb_num_sets_changed); connect( ui->dtlb_degree_of_associativity, QOverload::of(&QSpinBox::valueChanged), this, &NewDialog::dtlb_assoc_changed); connect( ui->dtlb_replacement_policy, QOverload::of(&QComboBox::activated), this, &NewDialog::dtlb_policy_changed); cache_handler_d = new NewDialogCacheHandler(this, ui_cache_d.data()); cache_handler_p = new NewDialogCacheHandler(this, ui_cache_p.data()); cache_handler_l2 = new NewDialogCacheHandler(this, ui_cache_l2.data()); // TODO remove this block when protections are implemented ui->mem_protec_exec->setVisible(false); ui->mem_protec_write->setVisible(false); load_settings(); // Also configures gui ui->config_page_title->setStyleSheet("font-weight: bold"); switch2page(config_pages_items.at(0)); } void NewDialog::switch2page(QTreeWidgetItem *current, QTreeWidgetItem *previous) { (void)previous; QWidget *page = ui->config_pages->findChild(current->text(1), Qt::FindDirectChildrenOnly); if (page != nullptr) { ui->config_pages->setCurrentWidget(page); ui->config_page_title->setText(current->text(0)); } } void NewDialog::switch_to_custom() { if (!ui->preset_custom->isChecked()) { ui->preset_custom->setChecked(true); // Select "Custom" preset and refresh GUI (no-op if // already selected). config_gui(); } } void NewDialog::closeEvent(QCloseEvent *) { load_settings(); // Reset from settings // Close the main window if not already configured auto *prnt = (MainWindow *)parent(); if (!prnt->configured()) { prnt->close(); } } void NewDialog::cancel() { this->close(); } void NewDialog::create() { auto *p_window = (MainWindow *)parent(); try { p_window->create_core(*config, true, false); } catch (const machine::SimulatorExceptionInput &e) { showAsyncCriticalBox( this, "Error while initializing new machine", e.msg(false), e.msg(true), "Please check that ELF executable really exists and is in correct format."); return; } store_settings(); // Save to settings this->close(); } void NewDialog::create_empty() { auto *p_window = (MainWindow *)parent(); p_window->create_core(*config, false, true); store_settings(); // Save to settings this->close(); } void NewDialog::create_example() { auto *p_window = (MainWindow *)parent(); QString example(":/samples/template.S"); p_window->create_core(*config, false, true); store_settings(); // Save to settings p_window->close_source_by_name(example, false); p_window->example_source(example); p_window->show_program(); p_window->compile_source(); this->close(); } void NewDialog::browse_elf() { #ifndef __EMSCRIPTEN__ QFileDialog elf_dialog(this); elf_dialog.setFileMode(QFileDialog::ExistingFile); if (elf_dialog.exec()) { QString path = elf_dialog.selectedFiles()[0]; ui->elf_file->setText(path); config->set_elf(path); } // Elf shouldn't have any other effect, so we skip config_gui here #else QHtml5File::load("*", [&](const QByteArray &content, const QString &fileName) { QFileInfo fi(fileName); QString elf_name = fi.fileName(); QFile file(elf_name); file.open(QIODevice::WriteOnly | QIODevice::Truncate); file.write(content); file.close(); ui->elf_file->setText(elf_name); config->set_elf(elf_name); }); #endif } void NewDialog::elf_change(QString val) { config->set_elf(std::move(val)); } void NewDialog::set_preset() { unsigned pres_n = preset_number(); if (pres_n > 0) { config->preset((enum machine::ConfigPresets)(pres_n - 1)); config_gui(); } } void NewDialog::xlen_64bit_change(bool val) { if (val) config->set_simulated_xlen(machine::Xlen::_64); else config->set_simulated_xlen(machine::Xlen::_32); switch_to_custom(); } void NewDialog::isa_atomic_change(bool val) { auto isa_mask = machine::ConfigIsaWord::byChar('A'); if (val) config->modify_isa_word(isa_mask, isa_mask); else config->modify_isa_word(isa_mask, machine::ConfigIsaWord::empty()); switch_to_custom(); } void NewDialog::isa_multiply_change(bool val) { auto isa_mask = machine::ConfigIsaWord::byChar('M'); if (val) config->modify_isa_word(isa_mask, isa_mask); else config->modify_isa_word(isa_mask, machine::ConfigIsaWord::empty()); switch_to_custom(); } void NewDialog::pipelined_change(bool val) { config->set_pipelined(val); ui->hazard_unit->setEnabled(config->pipelined()); switch_to_custom(); } void NewDialog::delay_slot_change(bool val) { config->set_delay_slot(val); switch_to_custom(); } void NewDialog::hazard_unit_change() { if (ui->hazard_unit->isChecked()) { config->set_hazard_unit( ui->hazard_stall->isChecked() ? machine::MachineConfig::HU_STALL : machine::MachineConfig::HU_STALL_FORWARD); } else { config->set_hazard_unit(machine::MachineConfig::HU_NONE); } switch_to_custom(); } void NewDialog::mem_protec_exec_change(bool v) { config->set_memory_execute_protection(v); switch_to_custom(); } void NewDialog::mem_protec_write_change(bool v) { config->set_memory_write_protection(v); switch_to_custom(); } void NewDialog::mem_time_read_change(int v) { if (config->memory_access_time_read() != (unsigned)v) { config->set_memory_access_time_read(v); switch_to_custom(); } } void NewDialog::mem_time_write_change(int v) { if (config->memory_access_time_write() != (unsigned)v) { config->set_memory_access_time_write(v); switch_to_custom(); } } void NewDialog::mem_enable_burst_change(bool v) { if (config->memory_access_enable_burst() != v) { config->set_memory_access_enable_burst(v); switch_to_custom(); } } void NewDialog::mem_time_burst_change(int v) { if (config->memory_access_time_burst() != (unsigned)v) { config->set_memory_access_time_burst(v); switch_to_custom(); } } void NewDialog::mem_time_level2_change(int v) { if (config->memory_access_time_level2() != (unsigned)v) { config->set_memory_access_time_level2(v); switch_to_custom(); } } void NewDialog::osemu_enable_change(bool v) { config->set_osemu_enable(v); } void NewDialog::osemu_known_syscall_stop_change(bool v) { config->set_osemu_known_syscall_stop(v); } void NewDialog::osemu_unknown_syscall_stop_change(bool v) { config->set_osemu_unknown_syscall_stop(v); } void NewDialog::osemu_interrupt_stop_change(bool v) { config->set_osemu_interrupt_stop(v); } void NewDialog::osemu_exception_stop_change(bool v) { config->set_osemu_exception_stop(v); } void NewDialog::browse_osemu_fs_root() { auto osemu_fs_root_dialog = new QFileDialog(this); osemu_fs_root_dialog->setFileMode(QFileDialog::Directory); osemu_fs_root_dialog->setOption(QFileDialog::ShowDirsOnly, true); QFileDialog::connect(osemu_fs_root_dialog, &QFileDialog::finished, [=](int result) { if (result > 0) { QString path = osemu_fs_root_dialog->selectedFiles()[0]; ui->osemu_fs_root->setText(path); config->set_osemu_fs_root(path); delete osemu_fs_root_dialog; } }); osemu_fs_root_dialog->open(); } void NewDialog::osemu_fs_root_change(QString val) { config->set_osemu_fs_root(std::move(val)); } void NewDialog::reset_at_compile_change(bool v) { config->set_reset_at_compile(v); } void NewDialog::bp_toggle_widgets() { // Enables or disables all branch predictor widgets // depending on the setting const machine::PredictorType predictor_type { config->get_bp_type() }; const bool is_predictor_dynamic { machine::is_predictor_type_dynamic(predictor_type) }; const bool is_predictor_enabled { config->get_bp_enabled() }; ui->group_bp_bht->setEnabled(is_predictor_enabled && is_predictor_dynamic); ui->text_bp_init_state->setEnabled(is_predictor_enabled && is_predictor_dynamic); ui->select_bp_init_state->setEnabled(is_predictor_enabled && is_predictor_dynamic); } void NewDialog::bp_type_change() { // Read branch predictor type from GUI and store it in the config const machine::PredictorType predictor_type { ui->select_bp_type->currentData().value() }; bool need_switch2custom = (config->get_bp_type() != predictor_type); config->set_bp_type(predictor_type); // Remove all items from init state list ui->select_bp_init_state->clear(); // Configure GUI based on predictor selection switch (predictor_type) { case machine::PredictorType::SMITH_1_BIT: { // Add items to the combo box ui->select_bp_init_state->addItem( predictor_state_to_string(machine::PredictorState::NOT_TAKEN, false).toString(), QVariant::fromValue(machine::PredictorState::NOT_TAKEN)); ui->select_bp_init_state->addItem( predictor_state_to_string(machine::PredictorState::TAKEN, false).toString(), QVariant::fromValue(machine::PredictorState::TAKEN)); // Set selected value, or set default if not found const int index { ui->select_bp_init_state->findData( QVariant::fromValue(config->get_bp_init_state())) }; if (index >= 0) { ui->select_bp_init_state->setCurrentIndex(index); } else { ui->select_bp_init_state->setCurrentIndex(ui->select_bp_init_state->findData( QVariant::fromValue(machine::PredictorState::NOT_TAKEN))); config->set_bp_init_state(machine::PredictorState::NOT_TAKEN); } } break; case machine::PredictorType::SMITH_2_BIT: case machine::PredictorType::SMITH_2_BIT_HYSTERESIS: { // Add items to the combo box ui->select_bp_init_state->addItem( predictor_state_to_string(machine::PredictorState::STRONGLY_NOT_TAKEN, false).toString(), QVariant::fromValue(machine::PredictorState::STRONGLY_NOT_TAKEN)); ui->select_bp_init_state->addItem( predictor_state_to_string(machine::PredictorState::WEAKLY_NOT_TAKEN, false).toString(), QVariant::fromValue(machine::PredictorState::WEAKLY_NOT_TAKEN)); ui->select_bp_init_state->addItem( predictor_state_to_string(machine::PredictorState::WEAKLY_TAKEN, false).toString(), QVariant::fromValue(machine::PredictorState::WEAKLY_TAKEN)); ui->select_bp_init_state->addItem( predictor_state_to_string(machine::PredictorState::STRONGLY_TAKEN, false).toString(), QVariant::fromValue(machine::PredictorState::STRONGLY_TAKEN)); // Set selected value, or set default if not found const int index { ui->select_bp_init_state->findData( QVariant::fromValue(config->get_bp_init_state())) }; if (index >= 0) { ui->select_bp_init_state->setCurrentIndex(index); } else { ui->select_bp_init_state->setCurrentIndex(ui->select_bp_init_state->findData( QVariant::fromValue(machine::PredictorState::WEAKLY_NOT_TAKEN))); config->set_bp_init_state(machine::PredictorState::WEAKLY_NOT_TAKEN); } } break; default: break; } bp_toggle_widgets(); if (need_switch2custom) switch_to_custom(); } void NewDialog::bp_enabled_change(bool v) { if (config->get_bp_enabled() != v) { config->set_bp_enabled(v); bp_toggle_widgets(); switch_to_custom(); } } void NewDialog::bp_init_state_change(void) { auto v = ui->select_bp_init_state->currentData().value(); if (v != config->get_bp_init_state()) { config->set_bp_init_state(v); switch_to_custom(); } } void NewDialog::bp_btb_addr_bits_change(int v) { if (config->get_bp_btb_bits() != v) { config->set_bp_btb_bits((uint8_t)v); switch_to_custom(); } ui->text_bp_btb_addr_bits_number->setText(QString::number(config->get_bp_btb_bits())); ui->text_bp_btb_bits_number->setText(QString::number(config->get_bp_btb_bits())); ui->text_bp_btb_entries_number->setText(QString::number(qPow(2, config->get_bp_btb_bits()))); } void NewDialog::bp_bht_bits_texts_update(void) { ui->text_bp_bht_bhr_bits_number->setText(QString::number(config->get_bp_bhr_bits())); ui->text_bp_bht_addr_bits_number->setText(QString::number(config->get_bp_bht_addr_bits())); ui->text_bp_bht_bits_number->setText(QString::number(config->get_bp_bht_bits())); ui->text_bp_bht_entries_number->setText(QString::number(qPow(2, config->get_bp_bht_bits()))); } void NewDialog::bp_bht_bhr_bits_change(int v) { if (config->get_bp_bhr_bits() != v) { config->set_bp_bhr_bits((uint8_t)v); switch_to_custom(); } bp_bht_bits_texts_update(); } void NewDialog::bp_bht_addr_bits_change(int v) { if (config->get_bp_bht_addr_bits() != v) { config->set_bp_bht_addr_bits((uint8_t)v); switch_to_custom(); } bp_bht_bits_texts_update(); } void NewDialog::vm_enabled_change(bool v) { if (config->get_vm_enabled() != v) { config->set_vm_enabled(v); switch_to_custom(); } } void NewDialog::itlb_num_sets_changed(int v) { unsigned u = v >= 0 ? v : 0; if (config->access_tlb_program()->get_tlb_num_sets() != u) { config->access_tlb_program()->set_tlb_num_sets(u); switch_to_custom(); } } void NewDialog::itlb_assoc_changed(int v) { unsigned u = v >= 0 ? v : 0; if (config->access_tlb_program()->get_tlb_associativity() != u) { config->access_tlb_program()->set_tlb_associativity(u); switch_to_custom(); } } void NewDialog::itlb_policy_changed(int idx) { machine::TLBConfig::ReplacementPolicy pol; pol = static_cast(idx); if (config->access_tlb_program()->get_tlb_replacement_policy() != pol) { config->access_tlb_program()->set_tlb_replacement_policy(pol); switch_to_custom(); } } void NewDialog::dtlb_num_sets_changed(int v) { unsigned u = v >= 0 ? v : 0; if (config->access_tlb_data()->get_tlb_num_sets() != u) { config->access_tlb_data()->set_tlb_num_sets(u); switch_to_custom(); } } void NewDialog::dtlb_assoc_changed(int v) { unsigned u = v >= 0 ? v : 0; if (config->access_tlb_data()->get_tlb_associativity() != u) { config->access_tlb_data()->set_tlb_associativity(u); switch_to_custom(); } } void NewDialog::dtlb_policy_changed(int idx) { machine::TLBConfig::ReplacementPolicy pol; pol = static_cast(idx); if (config->access_tlb_data()->get_tlb_replacement_policy() != pol) { config->access_tlb_data()->set_tlb_replacement_policy(pol); switch_to_custom(); } } void NewDialog::config_gui() { // Basic ui->elf_file->setText(config->elf()); ui->reset_at_compile->setChecked(config->reset_at_compile()); // Core ui->xlen_64bit->setChecked(config->get_simulated_xlen() == machine::Xlen::_64); ui->isa_multiply->setChecked(config->get_isa_word().contains('M')); ui->isa_atomic->setChecked(config->get_isa_word().contains('A')); ui->pipelined->setChecked(config->pipelined()); ui->delay_slot->setChecked(config->delay_slot()); ui->hazard_unit->setChecked(config->hazard_unit() != machine::MachineConfig::HU_NONE); ui->hazard_stall->setChecked(config->hazard_unit() == machine::MachineConfig::HU_STALL); ui->hazard_stall_forward->setChecked( config->hazard_unit() == machine::MachineConfig::HU_STALL_FORWARD); // Branch predictor ui->group_bp->setChecked(config->get_bp_enabled()); ui->select_bp_type->clear(); ui->select_bp_type->addItem( predictor_type_to_string(machine::PredictorType::ALWAYS_NOT_TAKEN).toString(), QVariant::fromValue(machine::PredictorType::ALWAYS_NOT_TAKEN)); ui->select_bp_type->addItem( predictor_type_to_string(machine::PredictorType::ALWAYS_TAKEN).toString(), QVariant::fromValue(machine::PredictorType::ALWAYS_TAKEN)); ui->select_bp_type->addItem( predictor_type_to_string(machine::PredictorType::BTFNT).toString(), QVariant::fromValue(machine::PredictorType::BTFNT)); ui->select_bp_type->addItem( predictor_type_to_string(machine::PredictorType::SMITH_1_BIT).toString(), QVariant::fromValue(machine::PredictorType::SMITH_1_BIT)); ui->select_bp_type->addItem( predictor_type_to_string(machine::PredictorType::SMITH_2_BIT).toString(), QVariant::fromValue(machine::PredictorType::SMITH_2_BIT)); ui->select_bp_type->addItem( predictor_type_to_string(machine::PredictorType::SMITH_2_BIT_HYSTERESIS).toString(), QVariant::fromValue(machine::PredictorType::SMITH_2_BIT_HYSTERESIS)); const int index { ui->select_bp_type->findData(QVariant::fromValue(config->get_bp_type())) }; if (index >= 0) { ui->select_bp_type->setCurrentIndex(index); } else { ui->select_bp_type->setCurrentIndex( ui->select_bp_type->findData(QVariant::fromValue(machine::PredictorType::SMITH_1_BIT))); config->set_bp_type(machine::PredictorType::SMITH_1_BIT); } ui->slider_bp_btb_addr_bits->setMaximum(BP_MAX_BTB_BITS); ui->slider_bp_btb_addr_bits->setValue(config->get_bp_btb_bits()); ui->text_bp_btb_addr_bits_number->setText(QString::number(config->get_bp_btb_bits())); ui->text_bp_btb_bits_number->setText(QString::number(config->get_bp_btb_bits())); ui->text_bp_btb_entries_number->setText(QString::number(qPow(2, config->get_bp_btb_bits()))); ui->slider_bp_bht_bhr_bits->setMaximum(BP_MAX_BHR_BITS); ui->slider_bp_bht_bhr_bits->setValue(config->get_bp_bhr_bits()); ui->text_bp_bht_bhr_bits_number->setText(QString::number(config->get_bp_bhr_bits())); ui->slider_bp_bht_addr_bits->setMaximum(BP_MAX_BHT_ADDR_BITS); ui->slider_bp_bht_addr_bits->setValue(config->get_bp_bht_addr_bits()); ui->text_bp_bht_addr_bits_number->setText(QString::number(config->get_bp_bht_addr_bits())); ui->text_bp_bht_bits_number->setText(QString::number(config->get_bp_bht_bits())); ui->text_bp_bht_entries_number->setText(QString::number(qPow(2, config->get_bp_bht_bits()))); bp_type_change(); // Virtual ui->group_vm->setChecked(config->get_vm_enabled()); ui->itlb_number_of_sets->setValue(config->tlbc_program().get_tlb_num_sets()); ui->itlb_degree_of_associativity->setValue(config->tlbc_program().get_tlb_associativity()); ui->itlb_replacement_policy->setCurrentIndex( (int)config->tlbc_program().get_tlb_replacement_policy()); ui->dtlb_number_of_sets->setValue(config->tlbc_data().get_tlb_num_sets()); ui->dtlb_degree_of_associativity->setValue(config->tlbc_data().get_tlb_associativity()); ui->dtlb_replacement_policy->setCurrentIndex( (int)config->tlbc_data().get_tlb_replacement_policy()); // Memory ui->mem_protec_exec->setChecked(config->memory_execute_protection()); ui->mem_protec_write->setChecked(config->memory_write_protection()); ui->mem_time_read->setValue((int)config->memory_access_time_read()); ui->mem_time_write->setValue((int)config->memory_access_time_write()); ui->mem_time_burst->setValue((int)config->memory_access_time_burst()); ui->mem_time_level2->setValue((int)config->memory_access_time_level2()); ui->mem_enable_burst->setChecked((int)config->memory_access_enable_burst()); // Cache cache_handler_d->config_gui(); cache_handler_p->config_gui(); cache_handler_l2->config_gui(); // Operating system and exceptions ui->osemu_enable->setChecked(config->osemu_enable()); ui->osemu_known_syscall_stop->setChecked(config->osemu_known_syscall_stop()); ui->osemu_unknown_syscall_stop->setChecked(config->osemu_unknown_syscall_stop()); ui->osemu_interrupt_stop->setChecked(config->osemu_interrupt_stop()); ui->osemu_exception_stop->setChecked(config->osemu_exception_stop()); ui->osemu_fs_root->setText(config->osemu_fs_root()); // Disable various sections according to configuration ui->delay_slot->setEnabled(false); ui->hazard_unit->setEnabled(config->pipelined()); } unsigned NewDialog::preset_number() { enum machine::ConfigPresets preset; if (ui->preset_no_pipeline->isChecked()) preset = machine::CP_SINGLE; else if (ui->preset_no_pipeline_cache->isChecked()) preset = machine::CP_SINGLE_CACHE; else if (ui->preset_pipelined_bare->isChecked()) preset = machine::CP_PIPE_NO_HAZARD; else if (ui->preset_pipelined->isChecked()) preset = machine::CP_PIPE; else return 0; return (unsigned)preset + 1; } void NewDialog::load_settings() { // Load config config.reset(new machine::MachineConfig(settings)); cache_handler_d->set_config(config->access_cache_data()); cache_handler_p->set_config(config->access_cache_program()); cache_handler_l2->set_config(config->access_cache_level2()); // Load preset unsigned preset = settings->value("Preset", 1).toUInt(); if (preset != 0) { auto p = (enum machine::ConfigPresets)(preset - 1); config->preset(p); switch (p) { case machine::CP_SINGLE: ui->preset_no_pipeline->setChecked(true); break; case machine::CP_SINGLE_CACHE: ui->preset_no_pipeline_cache->setChecked(true); break; case machine::CP_PIPE_NO_HAZARD: ui->preset_pipelined_bare->setChecked(true); break; case machine::CP_PIPE: ui->preset_pipelined->setChecked(true); break; } } else { ui->preset_custom->setChecked(true); } config_gui(); } void NewDialog::store_settings() { config->store(settings); // Presets are not stored in settings, so we have to store them explicitly if (ui->preset_custom->isChecked()) { settings->setValue("Preset", 0); } else { settings->setValue("Preset", preset_number()); } } NewDialogCacheHandler::NewDialogCacheHandler(NewDialog *nd, Ui::NewDialogCache *cui) : Super(nd) { this->nd = nd; this->ui = cui; this->config = nullptr; connect(ui->enabled, &QGroupBox::clicked, this, &NewDialogCacheHandler::enabled); connect( ui->number_of_sets, &QAbstractSpinBox::editingFinished, this, &NewDialogCacheHandler::numsets); connect( ui->block_size, &QAbstractSpinBox::editingFinished, this, &NewDialogCacheHandler::blocksize); connect( ui->degree_of_associativity, &QAbstractSpinBox::editingFinished, this, &NewDialogCacheHandler::degreeassociativity); connect( ui->replacement_policy, QOverload::of(&QComboBox::activated), this, &NewDialogCacheHandler::replacement); connect( ui->writeback_policy, QOverload::of(&QComboBox::activated), this, &NewDialogCacheHandler::writeback); } void NewDialogCacheHandler::set_config(machine::CacheConfig *cache_config) { this->config = cache_config; } void NewDialogCacheHandler::config_gui() { ui->enabled->setChecked(config->enabled()); ui->number_of_sets->setValue((int)config->set_count()); ui->block_size->setValue((int)config->block_size()); ui->degree_of_associativity->setValue((int)config->associativity()); ui->replacement_policy->setCurrentIndex((int)config->replacement_policy()); ui->writeback_policy->setCurrentIndex((int)config->write_policy()); } void NewDialogCacheHandler::enabled(bool val) { config->set_enabled(val); nd->switch_to_custom(); } void NewDialogCacheHandler::numsets() { config->set_set_count(ui->number_of_sets->value()); nd->switch_to_custom(); } void NewDialogCacheHandler::blocksize() { config->set_block_size(ui->block_size->value()); nd->switch_to_custom(); } void NewDialogCacheHandler::degreeassociativity() { config->set_associativity(ui->degree_of_associativity->value()); nd->switch_to_custom(); } void NewDialogCacheHandler::replacement(int val) { config->set_replacement_policy((enum machine::CacheConfig::ReplacementPolicy)val); nd->switch_to_custom(); } void NewDialogCacheHandler::writeback(int val) { config->set_write_policy((enum machine::CacheConfig::WritePolicy)val); nd->switch_to_custom(); } ================================================ FILE: src/gui/dialogs/new/newdialog.h ================================================ #ifndef NEWDIALOG_H #define NEWDIALOG_H #include "common/memory_ownership.h" #include "machine/machineconfig.h" #include "predictor_types.h" #include "ui_NewDialog.h" #include "ui_NewDialogCache.h" #include #include #include #include class NewDialogCacheHandler; class NewDialog : public QDialog { Q_OBJECT public: NewDialog(QWidget *parent, QSettings *settings); void switch_to_custom(); protected: void closeEvent(QCloseEvent *) override; private slots: void cancel(); void create(); void create_empty(); void create_example(); void browse_elf(); void elf_change(QString val); void set_preset(); void xlen_64bit_change(bool); void isa_atomic_change(bool); void isa_multiply_change(bool); void pipelined_change(bool); void delay_slot_change(bool); void hazard_unit_change(); void mem_protec_exec_change(bool); void mem_protec_write_change(bool); void mem_time_read_change(int); void mem_time_write_change(int); void mem_enable_burst_change(bool); void mem_time_burst_change(int); void mem_time_level2_change(int); void osemu_enable_change(bool); void osemu_known_syscall_stop_change(bool); void osemu_unknown_syscall_stop_change(bool); void osemu_interrupt_stop_change(bool); void osemu_exception_stop_change(bool); void browse_osemu_fs_root(); void osemu_fs_root_change(QString val); void reset_at_compile_change(bool); void switch2page(QTreeWidgetItem *current, QTreeWidgetItem *previous = nullptr); // Branch Predictor void bp_toggle_widgets(); void bp_enabled_change(bool); void bp_type_change(void); void bp_init_state_change(void); void bp_btb_addr_bits_change(int); void bp_bht_bhr_bits_change(int); void bp_bht_addr_bits_change(int); // Virtual Memory void vm_enabled_change(bool); void itlb_num_sets_changed(int); void itlb_assoc_changed(int); void itlb_policy_changed(int); void dtlb_num_sets_changed(int); void dtlb_assoc_changed(int); void dtlb_policy_changed(int); private: Box ui {}; Box ui_cache_p {}, ui_cache_d {}, ui_cache_l2 {}; QSettings *settings; Box config; void config_gui(); // Apply configuration to gui void bp_bht_bits_texts_update(void); unsigned preset_number(); void load_settings(); void store_settings(); NewDialogCacheHandler *cache_handler_p {}, *cache_handler_d {}, *cache_handler_l2 {}; }; class NewDialogCacheHandler : public QObject { Q_OBJECT using Super = QObject; public: NewDialogCacheHandler(NewDialog *nd, Ui::NewDialogCache *ui); void set_config(machine::CacheConfig *cache_config); void config_gui(); private slots: void enabled(bool); void numsets(); void blocksize(); void degreeassociativity(); void replacement(int); void writeback(int); private: NewDialog *nd; Ui::NewDialogCache *ui {}; machine::CacheConfig *config; }; #endif // NEWDIALOG_H ================================================ FILE: src/gui/dialogs/savechanged/savechangeddialog.cpp ================================================ #include "savechangeddialog.h" #include #include #include #include #include #include #include SaveChangedDialog::SaveChangedDialog(QStringList &changedlist, QWidget *parent) : QDialog(parent) { setAttribute(Qt::WA_DeleteOnClose); setAttribute(Qt::WA_ShowModal); setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); setWindowTitle(tr("Save next modified files?")); model = new QStandardItemModel(this); bool unknown_inserted = false; for (const auto &fname : changedlist) { int row = model->rowCount(); auto *item = new QStandardItem(); item->setData(fname, Qt::UserRole); if (!fname.isEmpty()) { item->setText(fname); } else { if (!unknown_inserted) { item->setText("Unknown"); unknown_inserted = true; } } item->setFlags(Qt::ItemIsUserCheckable | Qt::ItemIsEnabled); item->setCheckState(Qt::Checked); model->setItem(row, 0, item); } auto *all = new QVBoxLayout(this); auto *listview = new QListView(this); listview->setModel(model); listview->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding); all->addWidget(listview); auto *hbBtn = new QWidget(); auto *hlBtn = new QHBoxLayout(hbBtn); auto *cancelButton = new QPushButton(tr("&Cancel"), parent); auto *ignoreButton = new QPushButton(tr("&Ignore"), parent); auto *saveButton = new QPushButton(tr("&Save"), parent); saveButton->setFocus(); connect(cancelButton, &QAbstractButton::clicked, this, &SaveChangedDialog::cancel_clicked); connect(ignoreButton, &QAbstractButton::clicked, this, &SaveChangedDialog::ignore_clicked); connect(saveButton, &QAbstractButton::clicked, this, &SaveChangedDialog::save_clicked); hlBtn->addWidget(cancelButton); hlBtn->addStretch(); hlBtn->addWidget(ignoreButton); hlBtn->addStretch(); hlBtn->addWidget(saveButton); all->addWidget(hbBtn); setMinimumSize(400, 300); } void SaveChangedDialog::cancel_clicked() { QStringList list; emit user_decision(true, list); close(); } void SaveChangedDialog::ignore_clicked() { QStringList list; emit user_decision(false, list); close(); } void SaveChangedDialog::save_clicked() { QStringList list; for (int r = 0; r < model->rowCount(); ++r) { if (model->item(r)->checkState() == Qt::Checked) { list.append(model->item(r)->data(Qt::UserRole).toString()); } } emit user_decision(false, list); close(); } ================================================ FILE: src/gui/dialogs/savechanged/savechangeddialog.h ================================================ #ifndef SAVECHANGED_H #define SAVECHANGED_H #include #include #include #include class SaveChangedDialog : public QDialog { Q_OBJECT public: explicit SaveChangedDialog(QStringList &changedlist, QWidget *parent = nullptr); signals: void user_decision(bool cancel, QStringList tosavelist); private slots: void cancel_clicked(); void ignore_clicked(); void save_clicked(); private: QStandardItemModel *model; }; #endif // SAVECHANGED_H ================================================ FILE: src/gui/extprocess.cpp ================================================ #include "extprocess.h" #include #include using namespace std; ExtProcess::ExtProcess(QObject *parent) : Super(parent) { setProcessChannelMode(QProcess::MergedChannels); connect(this, &QProcess::readyReadStandardOutput, this, &ExtProcess::process_output); connect( this, QOverload::of(&QProcess::finished), this, &ExtProcess::report_finished); connect(this, &QProcess::started, this, &ExtProcess::report_started); } void ExtProcess::report_finished(int exitCode, QProcess::ExitStatus exitStatus) { if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) { report_message( messagetype::MSG_FINISH, "", 0, 0, program() + ": failed - exit code " + QString::number(exitCode), ""); } else { report_message(messagetype::MSG_FINISH, "", 0, 0, program() + ": finished", ""); } deleteLater(); } void ExtProcess::report_started() { report_message(messagetype::MSG_START, "", 0, 0, program() + ": started", ""); } void ExtProcess::process_output() { QString file = ""; int ln = 0; int col = 0; messagetype::Type type = messagetype::MSG_INFO; while (canReadLine()) { QString line = QString::fromLocal8Bit(readLine()); while (line.count() > 0) { if (line.at(line.count() - 1) != '\n' && line.at(line.count() - 1) != '\r') { break; } line.truncate(line.count() - 1); } int pos = line.indexOf(':'); if (pos >= 0) { QFileInfo fi(QDir(workingDirectory()), line.mid(0, pos)); line = line.mid(pos + 1); file = fi.absoluteFilePath(); } for (pos = 0; line.count() > pos && line.at(pos).isDigit(); pos++) {} if ((pos < line.count()) && (line.at(pos) == ':')) { ln = line.mid(0, pos).toInt(); line = line.mid(pos + 1); } for (pos = 0; line.count() > pos && line.at(pos).isDigit(); pos++) {} if ((pos < line.count()) && (line.at(pos) == ':')) { col = line.mid(0, pos).toInt(); line = line.mid(pos + 1); } if (line.startsWith(' ')) { line = line.mid(1); } if (line.startsWith('\t')) { line = line.mid(1); } if (line.startsWith("error:", Qt::CaseInsensitive)) { type = messagetype::MSG_ERROR; } else if (line.startsWith("warning:", Qt::CaseInsensitive)) { type = messagetype::MSG_WARNING; } report_message(type, file, ln, col, line, ""); } } ================================================ FILE: src/gui/extprocess.h ================================================ #ifndef MSGREPORT_H #define MSGREPORT_H #include "assembler/messagetype.h" #include #include class ExtProcess : public QProcess { Q_OBJECT using Super = QProcess; public: explicit ExtProcess(QObject *parent = nullptr); signals: void report_message( messagetype::Type type, QString file, int line, int column, QString text, QString hint); protected slots: void process_output(); void report_started(); void report_finished(int exitCode, QProcess::ExitStatus exitStatus); protected: QByteArray m_buffer; }; #endif // MSGREPORT_H ================================================ FILE: src/gui/fontsize.cpp ================================================ #include "fontsize.h" #include "common/logging.h" #include #include #include LOG_CATEGORY("gui.font"); int FontSize::SIZE5 = 5; int FontSize::SIZE6 = 6; int FontSize::SIZE7 = 7; int FontSize::SIZE8 = 8; void FontSize::init() { int h = QFontMetrics(QApplication::font()).height(); LOG() << "Font size:" << h; h /= 3; int d = h / 4 + 1; FontSize::SIZE5 = h - 2 * d; FontSize::SIZE6 = h - d; FontSize::SIZE7 = h; FontSize::SIZE8 = h + d / 2; } ================================================ FILE: src/gui/fontsize.h ================================================ #ifndef FONTSIZES_H #define FONTSIZES_H struct FontSize { static int SIZE5; static int SIZE6; static int SIZE7; static int SIZE8; static void init(); }; #endif // FONTSIZES_H ================================================ FILE: src/gui/graphicsview.cpp ================================================ #include "graphicsview.h" GraphicsView::GraphicsView(QWidget *parent) : Super(parent) { prev_height = 0; prev_width = 0; setRenderHints(QPainter::Antialiasing | QPainter::SmoothPixmapTransform); } void GraphicsView::setScene(QGraphicsScene *scene) { Super::setScene(scene); update_scale(); } void GraphicsView::resizeEvent(QResizeEvent *event) { Super::resizeEvent(event); if ((width() != prev_height) || (height() != prev_width)) { update_scale(); } } void GraphicsView::update_scale() { if (scene() == nullptr) { return; // Skip if we have no scene } // Note: there is somehow a three-pixel error when viewing, so we have to // always compensate const int w = scene()->width() + 3; const int h = scene()->height() + 3; prev_height = width(); prev_width = height(); qreal scale_x = (qreal)width() / w; qreal scale_y = (qreal)height() / h; qreal scale = qMin(scale_x, scale_y); QTransform t; t.scale(scale, scale); setTransform(t, false); } void GraphicsView::wheelEvent(QWheelEvent *event) { if (event->modifiers() & Qt::ControlModifier) { // zoom const ViewportAnchor anchor = transformationAnchor(); setTransformationAnchor(QGraphicsView::AnchorUnderMouse); int angle = event->angleDelta().y(); qreal factor; if (angle > 0) { factor = 1.1; } else { factor = 0.9; } scale(factor, factor); setTransformationAnchor(anchor); } else { Super::wheelEvent(event); } } void GraphicsView::keyPressEvent(QKeyEvent *event) { qreal factor = 1.1; if (event->matches(QKeySequence::ZoomIn) || (event->key() == Qt::Key_Equal) || (event->key() == Qt::Key_Plus)) { scale(factor, factor); } else if (event->matches(QKeySequence::ZoomOut) || (event->key() == Qt::Key_Minus)) { scale(1 / factor, 1 / factor); } else { Super::keyPressEvent(event); } } ================================================ FILE: src/gui/graphicsview.h ================================================ #ifndef GRAPHICSVIEW_H #define GRAPHICSVIEW_H #include #include #include #include #include class GraphicsView : public QGraphicsView { Q_OBJECT using Super = QGraphicsView; public: explicit GraphicsView(QWidget *parent); void setScene(QGraphicsScene *scene); protected: void resizeEvent(QResizeEvent *event) override; void wheelEvent(QWheelEvent *event) override; void keyPressEvent(QKeyEvent *event) override; private: void update_scale(); int prev_height; int prev_width; }; #endif // GRAPHICSVIEW_H ================================================ FILE: src/gui/helper/async_modal.h ================================================ /** * This file provides API for modal windows (dialogs, message boxes) which runs asynchronously * and therefore is safe in WASM, where blocking will cause the app to freeze! * * None of these classes shall be used directly, unless special callback is required. In such * cases, high caution needs to be applied. * * NOTE: This code could be optimized by adding async behavior only to emscripten, however modals * are typically not performance sensitive and an special case would complicate debugging. * * @file */ #ifndef ASYNC_MODAL_H #define ASYNC_MODAL_H #include inline void showAsyncMessageBox( QWidget *parent, QMessageBox::Icon icon, const QString &title, const QString &text, const QString &detailed_text = QString(), const QString &tooltip_text = QString()) { auto msg = new QMessageBox(icon, title, text, QMessageBox::Ok, parent); msg->setDetailedText(detailed_text); msg->setToolTip(tooltip_text); // This is necessary as WASM does not support blocking APIs. msg->setAttribute(Qt::WA_DeleteOnClose); msg->open(); } inline void showAsyncCriticalBox( QWidget *parent, const QString &title, const QString &text, const QString &detailed_text = QString(), const QString &tooltip_text = QString()) { showAsyncMessageBox(parent, QMessageBox::Critical, title, text, detailed_text, tooltip_text); } #endif ================================================ FILE: src/gui/hinttabledelegate.cpp ================================================ #include "hinttabledelegate.h" #include QSize HintTableDelegate::sizeHintForText( const QStyleOptionViewItem &option, const QModelIndex &index, const QString &str) const { QStyleOptionViewItem opt = option; initStyleOption(&opt, index); const QWidget *widget = option.widget; QStyle *style = widget ? widget->style() : QApplication::style(); opt.features |= QStyleOptionViewItem::HasDisplay; opt.text = str; return style->sizeFromContents(QStyle::CT_ItemViewItem, &opt, QSize(), widget); } ================================================ FILE: src/gui/hinttabledelegate.h ================================================ #ifndef HINTTABLEDELEGATE_H #define HINTTABLEDELEGATE_H #include #include class HintTableDelegate : public QStyledItemDelegate { Q_OBJECT using Super = QStyledItemDelegate; public: explicit HintTableDelegate(QWidget *parent = nullptr) : Super(parent) {} [[nodiscard]] QSize sizeHintForText( const QStyleOptionViewItem &option, const QModelIndex &index, const QString &str) const; }; #endif // HINTTABLEDELEGATE_H ================================================ FILE: src/gui/main.cpp ================================================ #include "common/logging.h" #include "common/logging_format_colors.h" #include "mainwindow/mainwindow.h" #include LOG_CATEGORY("gui.main"); int main(int argc, char *argv[]) { QApplication app(argc, argv); set_default_log_pattern(); // There constants are set in CMake. QApplication::setApplicationName(APP_NAME); QApplication::setOrganizationName(APP_ORGANIZATION); QApplication::setOrganizationDomain(APP_ORGANIZATION_DOMAIN); QApplication::setApplicationVersion(APP_VERSION); LOG("Started %s version %s.", APP_NAME, APP_VERSION); LOG("Developed at %s (%s).", APP_ORGANIZATION, APP_ORGANIZATION_DOMAIN); /* * If environment variable specified in define ENV_CONFIG_FILE_NAME is * present, the app should behave in a portable manner and use ini file on the * specified location. Otherwise, Qt defaults are used. */ auto env = QProcessEnvironment::systemEnvironment(); QSettings *settings; if (env.contains(ENV_CONFIG_FILE_NAME)) { // Behave as a portable app. settings = new QSettings(env.value(ENV_CONFIG_FILE_NAME), QSettings::IniFormat); } else { // Qt defaults settings = new QSettings(APP_ORGANIZATION, APP_NAME); } MainWindow w(settings); // Moving settings ownership. w.start(); return QApplication::exec(); } ================================================ FILE: src/gui/mainwindow/MainWindow.ui ================================================ MainWindow 0 0 900 600 MainWindow :/icons/gui.png :/icons/gui.png false true 0 0 0 0 0 0 0 900 19 &File &Examples &Windows M&achine &Help Options toolBar false Qt::TopToolBarArea TopToolBarArea false :/icons/document-import.png:/icons/document-import.png &New simulation... Ctrl+N R&estart :/icons/new.png:/icons/new.png Ne&w source New source Ctrl+F :/icons/open.png:/icons/open.png &Open source Open source Ctrl+O false :/icons/save.png:/icons/save.png &Save source Save source Ctrl+S false Save source &as Save source as false :/icons/closetab.png:/icons/closetab.png &Close source Close source Ctrl+W false :/icons/compfile-256.png:/icons/compfile-256.png &Compile Source Compile source and update memory Ctrl+E true :/icons/build-256.png:/icons/build-256.png &Build Executable Build executable by external make Ctrl+B :/icons/application-exit.png:/icons/application-exit.png E&xit Exit program Ctrl+Q :/icons/play.png:/icons/play.png &Run Ctrl+R :/icons/next.png:/icons/next.png &Step Ctrl+T :/icons/pause.png:/icons/pause.png &Pause Ctrl+P true &1 instruction per second 1x Run CPU in speed of single instruction per second Ctrl+1 true &5 instructions per second 5x Run CPU in speed of 5 instructions per second Ctrl+5 true 1&0 instructions per second 10x Run CPU in speed of 10 instructions per second Ctrl+0 true 2&5 instructions per second 25x Run CPU in speed of 25 instructions per second Ctrl+F5 true &Unlimited Run CPU without any clock constrains Ctrl+U &Memory Data memory view Ctrl+M &Program Program memory view Ctrl+P &Registers Ctrl+D C&ontrol and Status Registers Ctrl+I :/icons/reload.png:/icons/reload.png &Reload simulation Ctrl+Shift+R &Print Ctrl+Alt+P Pro&gram Cache Ctrl+Shift+P &Data Cache Ctrl+Shift+M L2 Cache Reset Windows true &2 instructions per second 2x Run CPU in speed of two instructions per second Ctrl+2 About Qt&RVSim true &Max Run at maximal speed, skip visualization for 100 msec Ctrl+A About &Qt &User Manual &Report a Problem... P&eripherals &Terminal &LCD display Sh&ow Symbol Show Symbol true true &Core View Me&ssages Show compile/build messages true false M&nemonics Registers true true Show &Line Numbers Show line number in the code editor. Branch Predictor (History table) Branch Predictor (Target table) Branch Predictor (Info) &Data TLB Data TLB Instruction TLB Instruction TLB ================================================ FILE: src/gui/mainwindow/mainwindow.cpp ================================================ #include "mainwindow.h" #include "assembler/fixmatheval.h" #include "assembler/simpleasm.h" #include "dialogs/about/aboutdialog.h" #include "dialogs/gotosymbol/gotosymboldialog.h" #include "dialogs/savechanged/savechangeddialog.h" #include "extprocess.h" #include "helper/async_modal.h" #include "os_emulation/ossyscall.h" #include "textsignalaction.h" #include "windows/editor/editordock.h" #include "windows/editor/editortab.h" #include #include #include #include #include #include #include #include #include #include #include #include LOG_CATEGORY("gui.mainwindow"); #ifdef __EMSCRIPTEN__ #include "qhtml5file.h" #include constexpr bool WEB_ASSEMBLY = true; #else constexpr bool WEB_ASSEMBLY = false; #endif MainWindow::MainWindow(QSettings *settings, QWidget *parent) : QMainWindow(parent) , settings(settings) { machine.reset(); corescene.reset(); ui.reset(new Ui::MainWindow()); ui->setupUi(this); setWindowTitle(APP_NAME); setDockNestingEnabled(true); frequency_label.reset(new QLabel(this)); ui->statusBar->addPermanentWidget(frequency_label.data()); // Setup central widget central_widget_tabs.reset(new HidingTabWidget(this)); central_widget_tabs->setTabBarAutoHide(true); this->setCentralWidget(central_widget_tabs.data()); coreview.reset(new GraphicsView(this)); coreview->setWindowTitle("&Core"); central_widget_tabs->addTab(coreview.data(), coreview->windowTitle()); // Setup editor editor_tabs.reset(new EditorDock(this->settings, central_widget_tabs.data())); editor_tabs->setTabBarAutoHide(true); editor_tabs->setWindowTitle("&Editor"); ui->actionBuildExe->setEnabled(false); connect(ui->actionNew, &QAction::triggered, editor_tabs.data(), &EditorDock::create_empty_tab); connect(ui->actionOpen, &QAction::triggered, editor_tabs.data(), &EditorDock::open_file_dialog); connect(ui->actionSave, &QAction::triggered, editor_tabs.data(), &EditorDock::save_current_tab); connect( ui->actionSaveAs, &QAction::triggered, editor_tabs.data(), &EditorDock::save_current_tab_as); connect( ui->actionClose, &QAction::triggered, editor_tabs.data(), &EditorDock::close_current_tab); connect( editor_tabs.data(), &EditorDock::requestAddRemoveTab, central_widget_tabs.data(), &HidingTabWidget::addRemoveTabRequested); connect( editor_tabs.data(), &EditorDock::editor_available_changed, this, [this](bool available) { ui->actionSave->setEnabled(available); ui->actionSaveAs->setEnabled(available); ui->actionClose->setEnabled(available); ui->actionCompileSource->setEnabled(available); }); if constexpr (!WEB_ASSEMBLY) { // Only enable build action if we know there to look for the Makefile. connect(editor_tabs.data(), &EditorDock::currentChanged, this, [this](int index) { bool has_elf_file = machine != nullptr && !machine->config().elf().isEmpty(); bool current_tab_is_file = (index >= 0) && !editor_tabs->get_tab(index)->get_editor()->filename().isEmpty(); ui->actionBuildExe->setEnabled(has_elf_file || current_tab_is_file); }); } connect( ui->actionEditorShowLineNumbers, &QAction::triggered, editor_tabs.data(), &EditorDock::set_show_line_numbers); bool line_numbers_visible = settings->value("EditorShowLineNumbers", true).toBool(); editor_tabs->set_show_line_numbers(line_numbers_visible); ui->actionEditorShowLineNumbers->setChecked(line_numbers_visible); // Create/prepare other widgets ndialog.reset(new NewDialog(this, settings)); registers.reset(new RegistersDock(this, machine::Xlen::_32)); registers->hide(); program.reset(new ProgramDock(this, settings)); addDockWidget(Qt::LeftDockWidgetArea, program.data()); program->show(); memory.reset(new MemoryDock(this, settings)); memory->hide(); cache_program.reset(new CacheDock(this, "Program")); cache_program->hide(); cache_data.reset(new CacheDock(this, "Data")); cache_data->hide(); cache_level2.reset(new CacheDock(this, "L2")); cache_level2->hide(); tlb_program.reset(new TLBDock(this, "Instruction")); tlb_program->hide(); tlb_data.reset(new TLBDock(this, "Data")); tlb_data->hide(); bp_btb.reset(new DockPredictorBTB(this)); bp_btb->hide(); bp_bht.reset(new DockPredictorBHT(this)); bp_bht->hide(); bp_info.reset(new DockPredictorInfo(this)); bp_info->hide(); peripherals.reset(new PeripheralsDock(this, settings)); peripherals->hide(); terminal.reset(new TerminalDock(this, settings)); terminal->hide(); lcd_display.reset(new LcdDisplayDock(this, settings)); lcd_display->hide(); csrdock = new CsrDock(this); csrdock->hide(); messages = new MessagesDock(this, settings); messages->hide(); // Execution speed actions speed_group = new QActionGroup(this); speed_group->addAction(ui->ips1); speed_group->addAction(ui->ips2); speed_group->addAction(ui->ips5); speed_group->addAction(ui->ips10); speed_group->addAction(ui->ips25); speed_group->addAction(ui->ipsUnlimited); speed_group->addAction(ui->ipsMax); ui->ips1->setChecked(true); // Connect signals from menu connect(ui->actionExit, &QAction::triggered, this, &QWidget::close); connect(ui->actionNewMachine, &QAction::triggered, this, &MainWindow::new_machine); connect(ui->actionReload, &QAction::triggered, this, [this] { machine_reload(false, false); }); connect(ui->actionPrint, &QAction::triggered, this, &MainWindow::print_action); connect( ui->actionMnemonicRegisters, &QAction::triggered, this, &MainWindow::view_mnemonics_registers); connect(ui->actionCompileSource, &QAction::triggered, this, &MainWindow::compile_source); connect(ui->actionBuildExe, &QAction::triggered, this, &MainWindow::build_execute); connect(ui->actionShow_Symbol, &QAction::triggered, this, &MainWindow::show_symbol_dialog); connect(ui->actionRegisters, &QAction::triggered, this, &MainWindow::show_registers); connect(ui->actionProgram_memory, &QAction::triggered, this, &MainWindow::show_program); connect(ui->actionMemory, &QAction::triggered, this, &MainWindow::show_memory); connect(ui->actionProgram_Cache, &QAction::triggered, this, &MainWindow::show_cache_program); connect(ui->actionData_Cache, &QAction::triggered, this, &MainWindow::show_cache_data); connect(ui->actionL2_Cache, &QAction::triggered, this, &MainWindow::show_cache_level2); connect(ui->actionInstruction_TLB, &QAction::triggered, this, &MainWindow::show_tlb_program); connect(ui->actionData_TLB, &QAction::triggered, this, &MainWindow::show_tlb_data); // Branch predictor connect( ui->actionBranch_Predictor_History_table, &QAction::triggered, this, &MainWindow::show_bp_bht); connect( ui->actionBranch_Predictor_Target_table, &QAction::triggered, this, &MainWindow::show_bp_btb); connect(ui->actionBranch_Predictor_Info, &QAction::triggered, this, &MainWindow::show_bp_info); connect(ui->actionPeripherals, &QAction::triggered, this, &MainWindow::show_peripherals); connect(ui->actionTerminal, &QAction::triggered, this, &MainWindow::show_terminal); connect(ui->actionLcdDisplay, &QAction::triggered, this, &MainWindow::show_lcd_display); connect(ui->actionCsrShow, &QAction::triggered, this, &MainWindow::show_csrdock); connect(ui->actionCore_View_show, &QAction::triggered, this, &MainWindow::show_hide_coreview); connect(ui->actionMessages, &QAction::triggered, this, &MainWindow::show_messages); connect(ui->actionResetWindows, &QAction::triggered, this, &MainWindow::reset_windows); connect(ui->actionUserManual, &QAction::triggered, this, [] { QDesktopServices::openUrl(QUrl(APP_USER_MANUAL_URL)); }); connect(ui->actionReportProblem, &QAction::triggered, this, [] { const QString version = QString::fromUtf8(APP_VERSION); const QString qtVersion = QString::fromUtf8(qVersion()); const QString os = QSysInfo::prettyProductName(); const QString arch = QSysInfo::currentCpuArchitecture(); QString body; body += "## Describe the problem\n"; body += "\n"; body += "## Steps to reproduce\n"; body += "1.\n"; body += "\n"; body += "## Expected behavior\n"; body += "\n"; body += "## Actual behavior\n"; body += "\n"; body += "## Environment\n"; body += "- QtRVSim version: " + version + "\n"; body += "- OS: " + os + "\n"; body += "- Arch: " + arch + "\n"; body += "- Qt: " + qtVersion + "\n"; QUrl url(QString::fromUtf8(APP_REPORT_PROBLEM_URL)); QUrlQuery query; query.addQueryItem("title", "Bug report"); query.addQueryItem("body", body); url.setQuery(query); QDesktopServices::openUrl(url); }); connect(ui->actionAbout, &QAction::triggered, this, &MainWindow::about_program); connect(ui->actionAboutQt, &QAction::triggered, this, &MainWindow::about_qt); connect(ui->ips1, &QAction::toggled, this, &MainWindow::set_speed); connect(ui->ips2, &QAction::toggled, this, &MainWindow::set_speed); connect(ui->ips5, &QAction::toggled, this, &MainWindow::set_speed); connect(ui->ips10, &QAction::toggled, this, &MainWindow::set_speed); connect(ui->ips25, &QAction::toggled, this, &MainWindow::set_speed); connect(ui->ipsUnlimited, &QAction::toggled, this, &MainWindow::set_speed); connect(ui->ipsMax, &QAction::toggled, this, &MainWindow::set_speed); connect(this, &MainWindow::report_message, messages, &MessagesDock::insert_line); connect(this, &MainWindow::clear_messages, messages, &MessagesDock::clear_messages); connect(messages, &MessagesDock::message_selected, this, &MainWindow::message_selected); // Restore application state from settings restoreState(settings->value("windowState").toByteArray()); restoreGeometry(settings->value("windowGeometry").toByteArray()); if (settings->value("viewMnemonicRegisters").toBool()) { ui->actionMnemonicRegisters->trigger(); } for (const QString &file_name : settings->value("openSrcFiles").toStringList()) { editor_tabs->open_file(file_name); } QDir samples_dir(":/samples"); for (const QString &fname : samples_dir.entryList(QDir::Files)) { auto *textsigac = new TextSignalAction(fname, ":/samples/" + fname, ui->menuExamples); ui->menuExamples->addAction(textsigac); connect(textsigac, &TextSignalAction::activated, this, &MainWindow::example_source); } } MainWindow::~MainWindow() { settings->sync(); } void MainWindow::start() { this->show(); ndialog->show(); } void MainWindow::show_hide_coreview(bool show) { coreview_shown = show; if (!show) { if (corescene == nullptr) { } else { central_widget_tabs->removeTab(central_widget_tabs->indexOf(coreview.data())); corescene.reset(); if (coreview != nullptr) { coreview->setScene(corescene.data()); } } return; } if (machine == nullptr) { return; } if (corescene != nullptr) { return; } if (machine->config().pipelined()) { corescene.reset(new CoreViewScenePipelined(machine.data())); } else { corescene.reset(new CoreViewSceneSimple(machine.data())); } central_widget_tabs->insertTab(0, coreview.data(), coreview->windowTitle()); // Ensures correct zoom. coreview->setScene(corescene.data()); this->setCentralWidget(central_widget_tabs.data()); // Connect scene signals to actions connect(corescene.data(), &CoreViewScene::request_registers, this, &MainWindow::show_registers); connect( corescene.data(), &CoreViewScene::request_program_memory, this, &MainWindow::show_program); connect(corescene.data(), &CoreViewScene::request_data_memory, this, &MainWindow::show_memory); connect( corescene.data(), &CoreViewScene::request_jump_to_program_counter, program.data(), &ProgramDock::jump_to_pc); connect( corescene.data(), &CoreViewScene::request_cache_program, this, &MainWindow::show_cache_program); connect( corescene.data(), &CoreViewScene::request_cache_data, this, &MainWindow::show_cache_data); connect( corescene.data(), &CoreViewScene::request_peripherals, this, &MainWindow::show_peripherals); connect(corescene.data(), &CoreViewScene::request_terminal, this, &MainWindow::show_terminal); coreview->setScene(corescene.data()); } void MainWindow::create_core( const machine::MachineConfig &config, bool load_executable, bool keep_memory) { // Create machine auto *new_machine = new machine::Machine(config, true, load_executable); if (keep_memory && (machine != nullptr)) { new_machine->memory_rw()->reset(*machine->memory()); } // Remove old machine machine.reset(new_machine); // Create machine view auto focused_index = central_widget_tabs->currentIndex(); corescene.reset(); show_hide_coreview(coreview_shown); central_widget_tabs->setCurrentIndex(focused_index); set_speed(); // Update machine speed to current settings const static machine::ExceptionCause ecall_variats[] = { machine::EXCAUSE_ECALL_ANY, machine::EXCAUSE_ECALL_M, machine::EXCAUSE_ECALL_S, machine::EXCAUSE_ECALL_U }; if (config.osemu_enable()) { auto *osemu_handler = new osemu::OsSyscallExceptionHandler( config.osemu_known_syscall_stop(), config.osemu_unknown_syscall_stop(), config.osemu_fs_root()); osemu_handler->setParent(new_machine); connect( osemu_handler, &osemu::OsSyscallExceptionHandler::char_written, terminal.data(), QOverload::of(&TerminalDock::tx_byte)); connect( osemu_handler, &osemu::OsSyscallExceptionHandler::rx_byte_pool, terminal.data(), &TerminalDock::rx_byte_pool); for (auto ecall_variat : ecall_variats) { machine->register_exception_handler(ecall_variat, osemu_handler); machine->set_step_over_exception(ecall_variat, true); machine->set_stop_on_exception(ecall_variat, false); } } else { for (auto ecall_variat : ecall_variats) { machine->set_step_over_exception(ecall_variat, false); machine->set_stop_on_exception(ecall_variat, config.osemu_exception_stop()); } } // Connect machine signals and slots connect(ui->actionRun, &QAction::triggered, machine.data(), &machine::Machine::play); connect(ui->actionPause, &QAction::triggered, machine.data(), &machine::Machine::pause); connect(ui->actionStep, &QAction::triggered, machine.data(), &machine::Machine::step); connect(ui->actionRestart, &QAction::triggered, machine.data(), &machine::Machine::restart); connect(machine.data(), &machine::Machine::status_change, this, &MainWindow::machine_status); connect(machine.data(), &machine::Machine::program_exit, this, &MainWindow::machine_exit); connect(machine.data(), &machine::Machine::program_trap, this, &MainWindow::machine_trap); connect( machine.data(), &machine::Machine::report_core_frequency, this, &MainWindow::update_core_frequency); // Connect signal from break to machine pause connect( machine->core(), &machine::Core::stop_on_exception_reached, machine.data(), &machine::Machine::pause); // Setup docks registers->connectToMachine(machine.data()); program->setup(machine.data()); memory->setup(machine.data()); cache_program->setup(machine->cache_program()); cache_data->setup(machine->cache_data()); bool cache_after_cache = config.cache_data().enabled() || config.cache_program().enabled(); cache_level2->setup(machine->cache_level2(), cache_after_cache); tlb_program->setup(machine->get_tlb_program_rw()); tlb_data->setup(machine->get_tlb_data_rw()); // Branch predictor bp_btb->setup(machine->core()->get_predictor(), machine->core()); bp_bht->setup(machine->core()->get_predictor(), machine->core()); bp_info->setup(machine->core()->get_predictor(), machine->core()); terminal->setup(machine->serial_port()); peripherals->setup(machine->peripheral_spi_led()); lcd_display->setup(machine->peripheral_lcd_display()); csrdock->setup(machine.data()); connect( machine->core(), &machine::Core::step_done, program.data(), &ProgramDock::update_pipeline_addrs); // Set status to ready machine_status(machine::Machine::ST_READY); } bool MainWindow::configured() { return (machine != nullptr); } void MainWindow::new_machine() { ndialog->show(); } void MainWindow::machine_reload(bool force_memory_reset, bool force_elf_load) { if (machine == nullptr) { return new_machine(); } bool load_executable = force_elf_load || machine->executable_loaded(); machine::MachineConfig cnf(&machine->config()); // We have to make local copy as create_core // will delete the current machine try { create_core(cnf, load_executable, !load_executable && !force_memory_reset); } catch (const machine::SimulatorExceptionInput &e) { showAsyncCriticalBox( this, "Error while initializing new machine", e.msg(false), e.msg(true)); } } void MainWindow::print_action() { #ifdef WITH_PRINTING printer.setColorMode(QPrinter::Color); if (print_dialog.exec() == QDialog::Accepted) { // This vector pre-drawing step is required because Qt fallbacks to // bitmap and produces extremely large and slow to render files. // (https://forum.qt.io/topic/21330/printing-widgets-not-as-bitmap-but-in-a-vector-based-format/3) QPicture scene_as_vector; { QPainter painter(&scene_as_vector); corescene->render(&painter); painter.end(); } // Prepare printer for PDF printing with appropriate resize. QRectF scene_rect = corescene->sceneRect(); if (printer.outputFormat() == QPrinter::PdfFormat && (scene_rect.height() != 0)) { QPageLayout layout = printer.pageLayout(); layout.setOrientation(QPageLayout::Portrait); QPageSize pagesize = layout.pageSize(); QRectF paint_rect = layout.paintRect(QPageLayout::Point); QSize pointsize = pagesize.sizePoints(); qreal ratio = scene_rect.width() / scene_rect.height(); // Cut off the excess if (paint_rect.height() * ratio > paint_rect.width()) { pointsize.setHeight( pointsize.height() - paint_rect.height() + paint_rect.width() / ratio); } else { pointsize.setWidth( pointsize.width() - paint_rect.width() + paint_rect.height() * ratio); } pagesize = QPageSize(pointsize, "custom", QPageSize::ExactMatch); layout.setPageSize(pagesize, layout.margins()); printer.setPageLayout(layout); } QPainter painter(&printer); // scale to fit paint size QRectF paint_rect = printer.pageLayout().paintRect(QPageLayout::Millimeter); double xscale = paint_rect.width() / scene_as_vector.widthMM(); double yscale = paint_rect.height() / scene_as_vector.heightMM(); double scale = qMin(xscale, yscale); painter.scale(scale, scale); painter.drawPicture(0, 0, scene_as_vector); painter.end(); } #else showAsyncMessageBox( this, QMessageBox::Information, "Printing is not supported.", "The simulator was compiled without printing support.\n" "If you compiled the simulator yourself, make sure that the Qt " "print support library is present.\n" "Otherwise report this to the executable provider (probably your " "teacher)."); #endif // WITH_PRINTING } #define SHOW_HANDLER(NAME, DEFAULT_AREA, DEFAULT_VISIBLE) \ void MainWindow::show_##NAME() { \ show_dockwidget(&*NAME, DEFAULT_AREA, true, false); \ } \ void MainWindow::reset_state_##NAME() { \ show_dockwidget(&*NAME, DEFAULT_AREA, DEFAULT_VISIBLE, true); \ } SHOW_HANDLER(registers, Qt::TopDockWidgetArea, true) SHOW_HANDLER(program, Qt::LeftDockWidgetArea, true) SHOW_HANDLER(memory, Qt::RightDockWidgetArea, true) SHOW_HANDLER(cache_program, Qt::RightDockWidgetArea, false) SHOW_HANDLER(cache_data, Qt::RightDockWidgetArea, false) SHOW_HANDLER(cache_level2, Qt::RightDockWidgetArea, false) SHOW_HANDLER(tlb_program, Qt::RightDockWidgetArea, false) SHOW_HANDLER(tlb_data, Qt::RightDockWidgetArea, false) SHOW_HANDLER(bp_btb, Qt::RightDockWidgetArea, false) SHOW_HANDLER(bp_bht, Qt::RightDockWidgetArea, false) SHOW_HANDLER(bp_info, Qt::RightDockWidgetArea, false) SHOW_HANDLER(peripherals, Qt::RightDockWidgetArea, false) SHOW_HANDLER(terminal, Qt::RightDockWidgetArea, false) SHOW_HANDLER(lcd_display, Qt::RightDockWidgetArea, false) SHOW_HANDLER(csrdock, Qt::TopDockWidgetArea, false) SHOW_HANDLER(messages, Qt::BottomDockWidgetArea, false) #undef SHOW_HANDLER void MainWindow::reset_windows() { reset_state_registers(); reset_state_program(); reset_state_memory(); reset_state_cache_program(); reset_state_cache_data(); reset_state_cache_level2(); reset_state_bp_btb(); reset_state_bp_bht(); reset_state_bp_info(); reset_state_peripherals(); reset_state_terminal(); reset_state_lcd_display(); reset_state_csrdock(); reset_state_messages(); } void MainWindow::show_symbol_dialog() { if (machine == nullptr || machine->symbol_table() == nullptr) { return; } QStringList symbol_names = machine->symbol_table()->names(); auto *gotosyboldialog = new GoToSymbolDialog(this, symbol_names); connect( gotosyboldialog, &GoToSymbolDialog::program_focus_addr, program.data(), &ProgramDock::focus_addr_with_save); connect( gotosyboldialog, &GoToSymbolDialog::program_focus_addr, this, &MainWindow::show_program); connect( gotosyboldialog, &GoToSymbolDialog::memory_focus_addr, memory.data(), &MemoryDock::focus_addr); connect(gotosyboldialog, &GoToSymbolDialog::memory_focus_addr, this, &MainWindow::show_memory); connect( gotosyboldialog, &GoToSymbolDialog::obtain_value_for_name, machine->symbol_table(), &machine::SymbolTable::name_to_value); gotosyboldialog->setAttribute(Qt::WA_DeleteOnClose); gotosyboldialog->open(); } void MainWindow::about_program() { auto *aboutdialog = new AboutDialog(this); aboutdialog->show(); } void MainWindow::about_qt() { QMessageBox::aboutQt(this); } void MainWindow::set_speed() { if (machine == nullptr) { return; // just ignore } if (ui->ips1->isChecked()) { machine->set_speed(1000); } else if (ui->ips2->isChecked()) { machine->set_speed(500); } else if (ui->ips5->isChecked()) { machine->set_speed(200); } else if (ui->ips10->isChecked()) { machine->set_speed(100); } else if (ui->ips25->isChecked()) { machine->set_speed(40); } else if (ui->ipsMax->isChecked()) { machine->set_speed(0, 100); } else { machine->set_speed(0); } } void MainWindow::view_mnemonics_registers(bool enable) { machine::Instruction::set_symbolic_registers(enable); settings->setValue("viewMnemonicRegisters", enable); if (program == nullptr) { return; } program->request_update_all(); } void MainWindow::closeEvent(QCloseEvent *event) { settings->setValue("windowGeometry", saveGeometry()); settings->setValue("windowState", saveState()); settings->setValue("openSrcFiles", editor_tabs->get_open_file_list()); settings->sync(); QStringList list; if (!ignore_unsaved && editor_tabs->get_modified_tab_filenames(list, true)) { event->ignore(); auto *dialog = new SaveChangedDialog(list, this); if (!QMetaType::isRegistered(qMetaTypeId())) { qRegisterMetaType(); } connect( dialog, &SaveChangedDialog::user_decision, this, [this](bool cancel, const QStringList &tabs_to_save) { if (cancel) return; for (const auto &name : tabs_to_save) { auto tab_id = editor_tabs->find_tab_id_by_filename(name); if (tab_id.has_value()) editor_tabs->save_tab(tab_id.value()); } ignore_unsaved = true; close(); }, Qt::QueuedConnection); dialog->open(); } } void MainWindow::show_dockwidget( QDockWidget *dw, Qt::DockWidgetArea area, bool defaultVisible, bool resetState) { if (dw == nullptr) { return; } if (resetState) { if (dw->isFloating()) { dw->hide(); dw->setFloating(false); } addDockWidget(area, dw); if (defaultVisible) { dw->show(); } else { dw->hide(); } } else if (dw->isHidden()) { dw->show(); addDockWidget(area, dw); } else { dw->raise(); dw->setFocus(); } } void MainWindow::machine_status(enum machine::Machine::Status st) { QString status; switch (st) { case machine::Machine::ST_READY: ui->actionPause->setEnabled(false); ui->actionRun->setEnabled(true); ui->actionStep->setEnabled(true); status = "Ready"; break; case machine::Machine::ST_RUNNING: ui->actionPause->setEnabled(true); ui->actionRun->setEnabled(false); ui->actionStep->setEnabled(false); status = "Running"; break; case machine::Machine::ST_BUSY: // Busy is not interesting (in such case we should just be running return; case machine::Machine::ST_EXIT: // machine_exit is called, so we disable controls in that status = "Exited"; break; case machine::Machine::ST_TRAPPED: // machine_trap is called, so we disable controls in that status = "Trapped"; break; default: status = "Unknown"; break; } ui->statusBar->showMessage(status); } void MainWindow::machine_exit() { ui->actionPause->setEnabled(false); ui->actionRun->setEnabled(false); ui->actionStep->setEnabled(false); } void MainWindow::machine_trap(machine::SimulatorException &e) { machine_exit(); showAsyncCriticalBox(this, "Machine trapped", e.msg(false), e.msg(true)); } void MainWindow::close_source_by_name(QString &filename, bool ask) { editor_tabs->close_tab_by_name(filename, ask); } void MainWindow::example_source(const QString &source_file) { if (!editor_tabs->open_file(source_file, true)) { showAsyncCriticalBox( this, "Simulator Error", tr("Cannot open example file '%1' for reading.").arg(source_file)); } } void MainWindow::message_selected( messagetype::Type type, const QString &file, int line, int column, const QString &text, const QString &hint) { (void)type; (void)column; (void)text; (void)hint; if (file.isEmpty()) { return; } central_widget_tabs->setCurrentWidget(editor_tabs.data()); if (!editor_tabs->set_cursor_to(file, line, column)) { editor_tabs->open_file_if_not_open(file, false); if (!editor_tabs->set_cursor_to(file, line, column)) return; } // Highlight the line auto editor = editor_tabs->get_current_editor(); QTextEdit::ExtraSelection selection; selection.format.setBackground(QColor(Qt::red).lighter(160)); selection.format.setProperty(QTextFormat::FullWidthSelection, true); selection.cursor = editor->textCursor(); selection.cursor.clearSelection(); editor->setExtraSelections({ selection }); } void MainWindow::update_core_frequency(double frequency) { frequency_label->setText(QString("Core frequency: %1 kHz").arg(frequency / 1000.0, 0, 'f', 3)); } bool SimpleAsmWithEditorCheck::process_file(const QString &filename, QString *error_ptr) { EditorTab *tab = mainwindow->editor_tabs->find_tab_by_filename(filename); if (tab == nullptr) { return Super::process_file(filename, error_ptr); } SrcEditor *editor = tab->get_editor(); QTextDocument *doc = editor->document(); int ln = 1; for (QTextBlock block = doc->begin(); block.isValid(); block = block.next(), ln++) { QString line = block.text(); process_line(line, filename, ln); } return !error_occured; } bool SimpleAsmWithEditorCheck::process_pragma( QStringList &operands, const QString &filename, int line_number, QString *error_ptr) { (void)error_ptr; #if 0 static const QMap pragma_how_map = { {QString("registers"), static_cast(&MainWindow::registers)}, }; #endif if (operands.count() < 2 || (QString::compare(operands.at(0), "qtrvsim", Qt::CaseInsensitive) && QString::compare(operands.at(0), "qtmips", Qt::CaseInsensitive))) { return true; } QString op = operands.at(1).toLower(); if (op == "show") { if (operands.count() < 3) { return true; } QString show_method = "show_" + operands.at(2); QString show_method_sig = show_method + "()"; if (mainwindow->metaObject()->indexOfMethod(show_method_sig.toLatin1().data()) == -1) { emit report_message( messagetype::MSG_WARNING, filename, line_number, 0, "#pragma qtrvsim show - unknown object " + operands.at(2), ""); return true; } QMetaObject::invokeMethod(mainwindow, show_method.toLatin1().data()); return true; } if (op == "tab") { if ((operands.count() < 3) || error_occured) { return true; } if (!QString::compare(operands.at(2), "core", Qt::CaseInsensitive) && (mainwindow->editor_tabs != nullptr) && (mainwindow->coreview != nullptr)) { mainwindow->editor_tabs->setCurrentWidget(mainwindow->coreview.data()); } return true; } if (op == "focus") { bool ok; if (operands.count() < 4) { return true; } fixmatheval::FmeExpression expression; fixmatheval::FmeValue value; QString error; ok = expression.parse(operands.at(3), error); if (!ok) { emit report_message( messagetype::MSG_WARNING, filename, line_number, 0, "expression parse error " + error, ""); return true; } ok = expression.eval(value, symtab, error, address); if (!ok) { emit report_message( messagetype::MSG_WARNING, filename, line_number, 0, "expression evaluation error " + error, ""); return true; } if (!QString::compare(operands.at(2), "memory", Qt::CaseInsensitive) && (mainwindow->memory != nullptr)) { mainwindow->memory->focus_addr(machine::Address(value)); return true; } if (!QString::compare(operands.at(2), "program", Qt::CaseInsensitive) && (mainwindow->program != nullptr)) { mainwindow->program->focus_addr(machine::Address(value)); return true; } emit report_message( messagetype::MSG_WARNING, filename, line_number, 0, "unknown #pragma qtrvsim focus unknown object " + operands.at(2), ""); return true; } emit report_message( messagetype::MSG_WARNING, filename, line_number, 0, "unknown #pragma qtrvsim " + op, ""); return true; } void MainWindow::compile_source() { bool error_occured = false; if (machine != nullptr) { if (machine->config().reset_at_compile()) { machine_reload(true); } } if (machine == nullptr) { showAsyncCriticalBox(this, "Simulator Error", tr("No machine to store program.")); return; } SymbolTableDb symtab(machine->symbol_table_rw(true)); machine::FrontendMemory *mem = machine->memory_data_bus_rw(); if (mem == nullptr) { showAsyncCriticalBox( this, "Simulator Error", tr("No physical addresspace to store program.")); return; } machine->cache_sync(); machine->tlb_sync(); auto editor = editor_tabs->get_current_editor(); auto filename = editor->filename().isEmpty() ? "Unknown" : editor->filename(); auto content = editor->document(); emit clear_messages(); SimpleAsmWithEditorCheck sasm(this); connect(&sasm, &SimpleAsm::report_message, this, &MainWindow::report_message); sasm.setup(mem, &symtab, machine::Address(0x00000200), machine->core()->get_xlen()); int ln = 1; for (QTextBlock block = content->begin(); block.isValid(); block = block.next(), ln++) { QString line = block.text(); if (!sasm.process_line(line, filename, ln)) { error_occured = true; } } if (!sasm.finish()) { error_occured = true; } if (error_occured) { show_messages(); } } void MainWindow::build_execute() { QStringList list; if (editor_tabs->get_modified_tab_filenames(list, false)) { auto *dialog = new SaveChangedDialog(list, this); connect( dialog, &SaveChangedDialog::user_decision, this, &MainWindow::build_execute_with_save); dialog->open(); } else { build_execute_no_check(); } } void MainWindow::build_execute_with_save( bool cancel, QStringList tosavelist) { // NOLINT(performance-unnecessary-value-param) if (cancel) { return; } for (const auto &filename : tosavelist) { editor_tabs->find_tab_by_filename(filename)->get_editor()->saveFile(); } build_execute_no_check(); } void MainWindow::build_execute_no_check() { QString work_dir = ""; ExtProcess *proc; ExtProcess *procptr = build_process; if (procptr != nullptr) { procptr->close(); procptr->deleteLater(); } emit clear_messages(); show_messages(); proc = new ExtProcess(this); build_process = procptr; connect(proc, &ExtProcess::report_message, this, &MainWindow::report_message); connect( proc, QOverload::of(&QProcess::finished), this, [this](int exitCode, QProcess::ExitStatus exitStatus) { if ((exitStatus != QProcess::NormalExit) || (exitCode != 0)) { return; } if (machine != nullptr) { if (machine->config().reset_at_compile()) { machine_reload(true, true); } } }); auto current_srceditor = editor_tabs->get_current_editor(); if (current_srceditor != nullptr && !current_srceditor->filename().isEmpty()) { QFileInfo fi(current_srceditor->filename()); work_dir = fi.dir().path(); } if (work_dir.isEmpty() && (machine != nullptr)) { if (!machine->config().elf().isEmpty()) { QFileInfo fi(machine->config().elf()); work_dir = fi.dir().path(); } } if (!work_dir.isEmpty()) { proc->setWorkingDirectory(work_dir); proc->start("make", {}, QProcess::Unbuffered | QProcess::ReadOnly); } } ================================================ FILE: src/gui/mainwindow/mainwindow.h ================================================ #ifndef MAINWINDOW_H #define MAINWINDOW_H #ifdef WITH_PRINTING #include #include #endif #include "assembler/simpleasm.h" #include "dialogs/new/newdialog.h" #include "extprocess.h" #include "machine/machine.h" #include "machine/machineconfig.h" #include "scene.h" #include "ui_MainWindow.h" #include "widgets/hidingtabwidget.h" #include "windows/cache/cachedock.h" #include "windows/csr/csrdock.h" #include "windows/editor/editordock.h" #include "windows/editor/editortab.h" #include "windows/editor/srceditor.h" #include "windows/lcd/lcddisplaydock.h" #include "windows/memory/memorydock.h" #include "windows/messages/messagesdock.h" #include "windows/peripherals/peripheralsdock.h" #include "windows/predictor/predictor_bht_dock.h" #include "windows/predictor/predictor_btb_dock.h" #include "windows/predictor/predictor_info_dock.h" #include "windows/program/programdock.h" #include "windows/registers/registersdock.h" #include "windows/terminal/terminaldock.h" #include "windows/tlb/tlbdock.h" #include #include #include #include class MainWindow : public QMainWindow { Q_OBJECT friend class SimpleAsmWithEditorCheck; public: explicit MainWindow(OWNED QSettings *settings, QWidget *parent = nullptr); ~MainWindow() override; void start(); void create_core( const machine::MachineConfig &config, bool load_executable = true, bool keep_memory = false); bool configured(); signals: void report_message( messagetype::Type type, QString file, int line, int column, QString text, QString hint); void clear_messages(); public slots: // Actions signals void new_machine(); void machine_reload(bool force_memory_reset = false, bool force_elf_load = false); void print_action(); void close_source_by_name(QString &filename, bool ask = false); void example_source(const QString &source_file); void compile_source(); void build_execute(); void build_execute_no_check(); void build_execute_with_save(bool cancel, QStringList tosavelist); void reset_state_registers(); void reset_state_program(); void reset_state_memory(); void reset_state_cache_program(); void reset_state_cache_data(); void reset_state_cache_level2(); void reset_state_tlb_program(); void reset_state_tlb_data(); void reset_state_peripherals(); void reset_state_terminal(); void reset_state_lcd_display(); void reset_state_csrdock(); void reset_state_messages(); void show_registers(); void show_program(); void show_memory(); void show_cache_data(); void show_cache_program(); void show_cache_level2(); void show_tlb_program(); void show_tlb_data(); void show_peripherals(); void show_terminal(); void show_lcd_display(); void show_csrdock(); void show_hide_coreview(bool show); void show_messages(); void reset_windows(); void show_symbol_dialog(); // Branch predictor void show_bp_btb(); void show_bp_bht(); void show_bp_info(); void reset_state_bp_btb(); void reset_state_bp_bht(); void reset_state_bp_info(); // Actions - help void about_program(); void about_qt(); // Actions - execution speed void set_speed(); // Machine signals void machine_status(enum machine::Machine::Status st); void machine_exit(); void machine_trap(machine::SimulatorException &e); void view_mnemonics_registers(bool enable); void message_selected( messagetype::Type type, const QString &file, int line, int column, const QString &text, const QString &hint); // Update data void update_core_frequency(double frequency); protected: void closeEvent(QCloseEvent *cancel) override; private: Box ui {}; // Placed right on bottom status bar. Box frequency_label {}; Box ndialog {}; Box central_widget_tabs {}; Box editor_tabs {}; Box coreview {}; Box corescene; Box registers {}; Box program {}; Box memory {}; Box cache_program {}, cache_data {}, cache_level2 {}; Box tlb_program {}, tlb_data {}; // Branch predictor Box bp_btb {}; Box bp_bht {}; Box bp_info {}; Box peripherals {}; Box terminal {}; Box lcd_display {}; CsrDock *csrdock {}; MessagesDock *messages {}; bool coreview_shown = true; QActionGroup *speed_group {}; QSharedPointer settings; Box machine; // Current simulated machine void show_dockwidget( QDockWidget *w, Qt::DockWidgetArea area = Qt::RightDockWidgetArea, bool defaultVisible = false, bool resetState = false); QPointer build_process; bool ignore_unsaved = false; #ifdef WITH_PRINTING QPrinter printer { QPrinter::HighResolution }; QPrintDialog print_dialog { &printer, this }; #endif }; class SimpleAsmWithEditorCheck : public SimpleAsm { Q_OBJECT using Super = SimpleAsm; public: explicit SimpleAsmWithEditorCheck(MainWindow *a_mainwindow, QObject *parent = nullptr) : Super(parent) , mainwindow(a_mainwindow) {} bool process_file(const QString &filename, QString *error_ptr = nullptr) override; protected: bool process_pragma( QStringList &operands, const QString &filename = "", int line_number = 0, QString *error_ptr = nullptr) override; private: MainWindow *mainwindow; }; #endif // MAINWINDOW_H ================================================ FILE: src/gui/qhtml5file.h ================================================ /**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef QHTML5FILE_H #define QHTML5FILE_H #include #include #include QT_BEGIN_NAMESPACE namespace QHtml5File { void load( const QString &accept, std::function fileDataReady); void save(const QByteArray &contents, const QString &fileNameHint); } // namespace QHtml5File QT_END_NAMESPACE #endif ================================================ FILE: src/gui/qhtml5file_html5.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2018 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the QtCore module of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:LGPL$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** GNU Lesser General Public License Usage ** Alternatively, this file may be used under the terms of the GNU Lesser ** General Public License version 3 as published by the Free Software ** Foundation and appearing in the file LICENSE.LGPL3 included in the ** packaging of this file. Please review the following information to ** ensure the GNU Lesser General Public License version 3 requirements ** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. ** ** GNU General Public License Usage ** Alternatively, this file may be used under the terms of the GNU ** General Public License version 2.0 or (at your option) the GNU General ** Public license version 3 or any later version approved by the KDE Free ** Qt Foundation. The licenses are as published by the Free Software ** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 ** included in the packaging of this file. Please review the following ** information to ensure the GNU General Public License requirements will ** be met: https://www.gnu.org/licenses/gpl-2.0.html and ** https://www.gnu.org/licenses/gpl-3.0.html. ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "qhtml5file.h" #include #include #include // // This file implements file loading via the HTML file input element and file save via // browser download. // // Global user file data ready callback and C helper function. JavaScript will // call this function when the file data is ready; the helper then forwards // the call to the current handler function. This means there can be only one // file open in proress at a given time. std::function g_qtFileDataReadyCallback; extern "C" EMSCRIPTEN_KEEPALIVE void qt_callFileDataReady(char *content, size_t contentSize, const char *fileName) { if (g_qtFileDataReadyCallback == nullptr) { return; } g_qtFileDataReadyCallback(content, contentSize, fileName); g_qtFileDataReadyCallback = nullptr; } namespace { void loadFile(const char *accept, std::function fileDataReady) { if (::g_qtFileDataReadyCallback) { puts( "Warning: Concurrent loadFile() calls are not supported. " "Cancelling earlier call"); } // Call qt_callFileDataReady to make sure the emscripten linker does not // optimize it away, which may happen if the function is called from // JavaScript only. Set g_qtFileDataReadyCallback to null to make it a a // no-op. ::g_qtFileDataReadyCallback = nullptr; ::qt_callFileDataReady(nullptr, 0, nullptr); ::g_qtFileDataReadyCallback = fileDataReady; EM_ASM_( { const accept = UTF8ToString($0); // Crate file file input which whil display the native file dialog var fileElement = document.createElement("input"); document.body.appendChild(fileElement); fileElement.type = "file"; fileElement.style = "display:none"; fileElement.accept = accept; fileElement.onchange = function(event) { const files = event.target.files; // Read files for (var i = 0; i < files.length; i++) { const file = files[i]; var reader = new FileReader(); reader.onload = function() { const name = file.name; var contentArray = new Uint8Array(reader.result); const contentSize = reader.result.byteLength; // Copy the file file content to the C++ heap. // Note: this could be simplified by passing the content // as an "array" type to ccall and then let it copy to // C++ memory. However, this built-in solution does not // handle files larger than ~15M (Chrome). Instead, // allocate memory manually and pass a pointer to the // C++ side (which will free() it when done). // TODO: consider slice()ing the file to read it // picewise and then assembling it in a QByteArray on // the C++ side. const heapPointer = _malloc(contentSize); const heapBytes = new Uint8Array(Module.HEAPU8.buffer, heapPointer, contentSize); heapBytes.set(contentArray); // Null out the first data copy to enable GC reader = null; contentArray = null; // Call the C++ file data ready callback ccall( "qt_callFileDataReady", null, [ "number", "number", "string" ], [ heapPointer, contentSize, name ]); }; reader.readAsArrayBuffer(file); } // Clean up document document.body.removeChild(fileElement); }; // onchange callback // Trigger file dialog open fileElement.click(); }, accept); } void saveFile(const char *contentPointer, size_t contentLength, const char *fileNameHint) { EM_ASM_( { // Make the file contents and file name hint accessible to // Javascript: convert the char * to a JavaScript string and create // a subarray view into the C heap. const contentPointer = $0; const contentLength = $1; const fileNameHint = UTF8ToString($2); const fileContent = Module.HEAPU8.subarray(contentPointer, contentPointer + contentLength); // Create a hidden download link and click it programatically const fileblob = new Blob([fileContent], { type: "application/octet-stream" }); var link = document.createElement("a"); document.body.appendChild(link); link.download = fileNameHint; link.href = window.URL.createObjectURL(fileblob); link.style = "display:none"; link.click(); document.body.removeChild(link); }, contentPointer, contentLength, fileNameHint); } } // namespace /*! \brief Read local file via file dialog. Call this function to make the browser display an open-file dialog. This function returns immediately, and \a fileDataReady is called when the user has selected a file and the file contents has been read. \a The accept argument specifies which file types to accept, and must follow the html standard formatting, for example ".png, .jpg, .jpeg". This function is implemented on Qt for WebAssembly only. A nonfunctional cross- platform stub is provided so that code that uses it can compile on all platforms. */ void QHtml5File::load( const QString &accept, std::function fileDataReady) { loadFile(accept.toUtf8().constData(), [=](char *content, size_t size, const char *fileName) { // Copy file data into QByteArray and free buffer that was allocated // on the JavaScript side. We could have used // QByteArray::fromRawData() to avoid the copy here, but that would // make memory management awkward. QByteArray qtFileContent(content, size); free(content); // Call user-supplied data ready callback fileDataReady(qtFileContent, QString::fromUtf8(fileName)); }); } /*! \brief Write local file via browser download Call this function to make the browser start a file download. The file will contains the given \a content, with a suggested \a fileNameHint. This function is implemented on Qt for WebAssembly only. A nonfunctional cross- platform stub is provided so that code that uses it can compile on all platforms. */ void QHtml5File::save(const QByteArray &content, const QString &fileNameHint) { // Convert to C types and save saveFile(content.constData(), content.size(), fileNameHint.toUtf8().constData()); } ================================================ FILE: src/gui/resources/icons/icons.qrc ================================================ gui.png application-exit.png reload.png document-import.png finish.png forward.png next.png pause.png refresh.png play.png stop.png new.png open.png save.png closetab.png compfile-256.png build-256.png ================================================ FILE: src/gui/resources/samples/samples.qrc ================================================ simple-lw-sw-ia.S template.S template-os.S ================================================ FILE: src/gui/resources/samples/simple-lw-sw-ia.S ================================================ // Template file with simple memory example // QtRVSim simulator https://github.com/cvut/qtrvsim/ // // template-os.S - example file // // (C) 2021 by Pavel Pisa // e-mail: pisa@cmp.felk.cvut.cz // homepage: http://cmp.felk.cvut.cz/~pisa // work: http://www.pikron.com/ // license: public domain // Directives to make interesting windows visible #pragma qtrvsim show registers #pragma qtrvsim show memory .globl _start .option norelax .text _start: loop: // load the word from absolute address lw x2, 0x400(x0) // store the word to absolute address sw x2, 0x404(x0) // stop execution wait for debugger/user // ebreak // ensure that continuation does not // interpret random data beq x0, x0, loop nop nop ebreak .data .org 0x400 src_val: .word 0x12345678 dst_val: .word 0 // Specify location to show in memory window #pragma qtrvsim focus memory src_val ================================================ FILE: src/gui/resources/samples/template-os.S ================================================ // Template file with defines of system calls // QtRVSim simulator https://github.com/cvut/qtrvsim/ // // template-os.S - example file // // (C) 2021 by Pavel Pisa // e-mail: pisa@cmp.felk.cvut.cz // homepage: http://cmp.felk.cvut.cz/~pisa // work: http://www.pikron.com/ // license: public domain // Directives to make interesting windows visible #pragma qtrvsim show terminal #pragma qtrvsim show registers #pragma qtrvsim show csrdock #pragma qtrvsim show memory .globl _start .globl __start .option norelax // Linux kernel compatible system calls subset .equ __NR_exit, 93 // void exit(int status) .equ __NR_read, 63 // ssize_t read(int fd, void *buf, size_t count) .equ __NR_write, 64 // ssize_t write(int fd, const void *buf, size_t count) .equ __NR_close, 57 // int close(int fd) .equ __NR_openat, 56 // int openat(int fd, const char *pathname, int flags, mode_t mode) // use fd = -100 for normal open behaviour. Full openat not supported. .equ __NR_brk, 214 // void * brk(void *addr) .equ __NR_ftruncate64, 46 // int ftruncate64(int fd, off_t length) .equ __NR_readv, 65 // ssize_t readv(int fd, const struct iovec *iov, int iovcnt) .equ __NR_writev, 66 // ssize_t writev(int fd, const struct iovec *iov, int iovcnt) .text __start: _start: addi a7, zero, __NR_write // load syscall number addi a0, zero, 1 // load file descriptor la a1, text_1 // load text start address la a2, text_1_e // load text end address sub a2, a2, a1 // compute text length ecall // print the text addi a7, zero, __NR_exit // load syscall numver addi a0, zero, 0 // load status argument ecall // exit final: ebreak // request developer interaction jal zero, final .data .org 0x400 data_1: .word 1, 2, 3, 4 text_1: .ascii "Hello world.\n" // store ASCII text, no termination text_1_e: // The sample can be compiled by full-featured riscv64-unknown-elf GNU tool-chain // for RV32IMA use // riscv64-unknown-elf-gcc -c -march=rv64ima -mabi=lp64 template-os.S // riscv64-unknown-elf-gcc -march=rv64ima -mabi=lp64 -nostartfiles -nostdlib template-os.o // for RV64IMA use // riscv64-unknown-elf-gcc -c -march=rv32ima -mabi=ilp32 template-os.S // riscv64-unknown-elf-gcc -march=rv32ima -mabi=ilp32 -nostartfiles -nostdlib template-os.o // add "-o template-os" to change default "a.out" output file name ================================================ FILE: src/gui/resources/samples/template.S ================================================ // Template file with defines of peripheral registers // QtRVSim simulator https://github.com/cvut/qtrvsim/ // // template.S - example file // // (C) 2021-2024 by Pavel Pisa // e-mail: pisa@cmp.felk.cvut.cz // homepage: http://cmp.felk.cvut.cz/~pisa // work: http://www.pikron.com/ // license: public domain // Directives to make interesting windows visible #pragma qtrvsim show terminal #pragma qtrvsim show registers #pragma qtrvsim show memory .globl _start .globl __start .option norelax // Serial port/terminal registers // There is mirror of this region at address 0xffff0000 // to match QtSpim and Mars emulators .equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region .equ SERP_RX_ST_REG, 0xffffc000 // Receiver status register .equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG .equ SERP_RX_ST_REG_READY_m, 0x1 // Data byte is ready to be read .equ SERP_RX_ST_REG_IE_m, 0x2 // Enable Rx ready interrupt .equ SERP_RX_DATA_REG, 0xffffc004 // Received data byte in 8 LSB bits .equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG .equ SERP_TX_ST_REG, 0xffffc008 // Transmitter status register .equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG .equ SERP_TX_ST_REG_READY_m, 0x1 // Transmitter can accept next byte .equ SERP_TX_ST_REG_IE_m, 0x2 // Enable Tx ready interrupt .equ SERP_TX_DATA_REG, 0xffffc00c // Write word to send 8 LSB bits to terminal .equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG // Memory mapped peripheral for dial knobs input, // LED and RGB LEDs output designed to match // MZ_APO education Zynq based board developed // by Petr Porazil and Pavel Pisa at PiKRON.com company .equ SPILED_REG_BASE, 0xffffc100 // base of SPILED port region .equ SPILED_REG_LED_LINE, 0xffffc104 // 32 bit word mapped as output .equ SPILED_REG_LED_LINE_o, 0x0004 // Offset of the LED_LINE .equ SPILED_REG_LED_RGB1, 0xffffc110 // RGB LED 1 color components .equ SPILED_REG_LED_RGB1_o, 0x0010 // Offset of LED_RGB1 .equ SPILED_REG_LED_RGB2, 0xffffc114 // RGB LED 2 color components .equ SPILED_REG_LED_RGB2_o, 0x0014 // Offset of LED_RGB2 .equ SPILED_REG_KNOBS_8BIT, 0xffffc124 // Three 8 bit knob values .equ SPILED_REG_KNOBS_8BIT_o, 0x0024 // Offset of KNOBS_8BIT // The simple 16-bit per pixel (RGB565) frame-buffer // display size is 480 x 320 pixel // Pixel format RGB565 expect // bits 11 .. 15 red component // bits 5 .. 10 green component // bits 0 .. 4 blue component .equ LCD_FB_START, 0xffe00000 .equ LCD_FB_END, 0xffe4afff // RISC-V ACLINT MSWI and MTIMER memory mapped peripherals .equ ACLINT_MSWI, 0xfffd0000 // core 0 SW interrupt request .equ ACLINT_MTIMECMP, 0xfffd4000 // core 0 compare value .equ ACLINT_MTIME, 0xfffdbff8 // timer base 10 MHz // Mapping of interrupts // mcause mie / mip // irq number bit Source // 3 3 ACLINT MSWI // 7 7 MTIME reached value of MTIMECMP // 16 16 There is received character ready to be read // 17 17 Serial port ready to accept character to Tx // Start address after reset .org 0x00000200 .text __start: _start: loop: li a0, SERIAL_PORT_BASE // load base address of serial port la a1, text_1 // load address of text next_char: lb t1, 0(a1) // load one byte after another beq t1, zero, end_char // is this the terminal zero byte addi a1, a1, 1 // move pointer to next text byte tx_busy: lw t0, SERP_TX_ST_REG_o(a0) // read status of transmitter andi t0, t0, SERP_TX_ST_REG_READY_m // mask ready bit beq t0, zero, tx_busy // if not ready wait for ready condition sw t1, SERP_TX_DATA_REG_o(a0) // write byte to Tx data register jal zero, next_char // unconditional branch to process next byte end_char: ebreak // stop continuous execution, request developer interaction jal zero, end_char .org 0x400 .data data_1: .word 1, 2, 3, 4 // example how to fill data words text_1: .asciz "Hello world.\n" // store zero terminated ASCII text // if whole source compile is OK the switch to core tab #pragma qtrvsim tab core // The sample can be compiled by full-featured riscv64-unknown-elf GNU tool-chain // for RV32IMA use // riscv64-unknown-elf-gcc -c -march=rv64ima -mabi=lp64 template.S // riscv64-unknown-elf-gcc -march=rv64ima -mabi=lp64 -nostartfiles -nostdlib template.o // for RV64IMA use // riscv64-unknown-elf-gcc -c -march=rv32ima -mabi=ilp32 template.S // riscv64-unknown-elf-gcc -march=rv32ima -mabi=ilp32 -nostartfiles -nostdlib template.o // add "-o template" to change default "a.out" output file name ================================================ FILE: src/gui/statictable.cpp ================================================ #include "statictable.h" #include "machine/simulator_exception.h" #include StaticTableLayout::StaticTableLayout( QWidget *parent, int margin, int horizontal_big_spacing, int horizontal_small_spacing, int vertical_spacing) : QLayout(parent) { setContentsMargins(margin, margin, margin, margin); bhspace = horizontal_big_spacing; shspace = horizontal_small_spacing; vspace = vertical_spacing; setSizeConstraint(QLayout::SetMinAndMaxSize); cch_do_layout.size = QSize(0, 0); cch_do_layout.count = 0; cch_heightForWidth.w = 0; cch_heightForWidth.count = 0; cch_heightForWidth.width = 0; cch_minSize.count = 0; cch_minSize.size = QSize(0, 0); } StaticTableLayout::~StaticTableLayout() { for (auto &row : items) { for (auto &col : row) delete col; } } Qt::Orientations StaticTableLayout::expandingDirections() const { return Qt::Horizontal; } bool StaticTableLayout::hasHeightForWidth() const { return true; } int StaticTableLayout::heightForWidth(int w) const { if (cch_heightForWidth.w != w || cch_heightForWidth.count != items.count()) cch_heightForWidth.width = layout_height(w); cch_heightForWidth.w = w; cch_heightForWidth.count = items.count(); return cch_heightForWidth.width; } QSize StaticTableLayout::minimumSize() const { if (cch_minSize.count == items.size()) return cch_minSize.size; cch_minSize.count = items.size(); cch_minSize.size = QSize(); for (const auto &item : items) { QSize ss; for (auto layout_item : item) { ss = cch_minSize.size.expandedTo(layout_item->minimumSize() + QSize(shspace, 0)); } cch_minSize.size = cch_minSize.size.expandedTo(ss - QSize(shspace, 0)); } int left, top, right, bottom; getContentsMargins(&left, &top, &right, &bottom); cch_minSize.size += QSize(left + right, top + bottom); return cch_minSize.size; } void StaticTableLayout::setGeometry(const QRect &rect) { QLayout::setGeometry(rect); do_layout(rect); } QSize StaticTableLayout::sizeHint() const { return minimumSize(); } void StaticTableLayout::addItem(QLayoutItem *item __attribute__((unused))) { // Just implement it but it does nothing } QLayoutItem *StaticTableLayout::itemAt(int index __attribute__((unused))) const { return nullptr; // This is just dummy implementation to satisfy // reimplementation } QLayoutItem *StaticTableLayout::takeAt(int index __attribute__((unused))) { return nullptr; // This is just dummy implementation to satisfy // reimplementation } int StaticTableLayout::count() const { return items.size(); } void StaticTableLayout::addRow(const QList &w) { items.append(list2vec(w)); } void StaticTableLayout::insertRow(const QList &w, int i) { items.insert(i, list2vec(w)); } void StaticTableLayout::removeRow(int i) { for (auto &item : items[i]) { delete item->widget(); delete item; } items.remove(i); } void StaticTableLayout::clearRows() { for (auto &item : items) { for (auto &layout_item : item) { delete layout_item->widget(); delete layout_item; } } items.clear(); } void StaticTableLayout::itemRect(QRect &rect, QVector &separators, int i) { separators.clear(); int row = i / columns(); int col = i % columns(); int left, top, right, bottom; getContentsMargins(&left, &top, &right, &bottom); int x = left; for (int s = 0; s < col; s++) { for (int row_width : row_widths[s]) { x += row_width + shspace; } x += bhspace - shspace; } if (col > 0) { // otherwise we are on the left edge and there was no previous // column so no big space x -= bhspace / 2; } int y = top + (row * (row_height + vspace)); int width = 0; for (int row_width : row_widths[col]) { width += row_width + shspace; separators.append(width - shspace / 2); } if (col <= 0) { width -= bhspace / 2; } rect = QRect(x, y - vspace / 2, width - shspace + bhspace, row_height + vspace); separators.removeLast(); // drop the last separator as that one we don't want to // see } int StaticTableLayout::columns() { return row_widths.size(); } int StaticTableLayout::real_row_height() const { return row_height + vspace; } int StaticTableLayout::layout_count_approx(const QRect &rect) const { if (items.empty() || rect.width() < rect.height()) return 1; // Note: for some reason (probably optimisation) when qlabel is not // visible, it reports zero size. So we have to find at least one that is // visible !items[vis][0]->widget()->isVisible() int vis = 0; while (items[vis].empty() || items[vis][0]->widget()->sizeHint().width() == 0) { vis++; if (vis >= items.size()) return 1; // If none is visible, then just say that it has to be a single column } int w = 0; for (auto item : items[vis]) w += item->sizeHint().width() + shspace; w -= shspace; // subtract the latest spacing int width = rect.right() / w; // Note: this always rounds down so this // always founds maximal possible count return width <= 0 ? 1 : width; // We have to fit at least one column } int StaticTableLayout::layout_size(int &row_h, QList> &row_w, int count) const { row_h = 0; row_w.clear(); int col = 0; for (const auto &item : items) { if (row_w.size() <= col) row_w.append(QList()); for (int y = 0; y < item.size(); y++) { QSize s = item[y]->sizeHint(); row_h = qMax(row_h, s.height()); if (row_w[col].size() <= y) row_w[col].append(0); row_w[col][y] = qMax(row_w[col][y], s.width()); } if (++col >= count) col = 0; } SANITY_ASSERT(row_w.size() <= count, "We should end up with maximum of count columns"); int w = 0; for (auto &i : row_w) { for (int y : i) { w += y + shspace; } w += bhspace - shspace; // subtract latest small spacing and add big // spacing } w -= bhspace; // subtract latest big spacing return w; } void StaticTableLayout::layout_parms(QRect &rect, int &row_h, QList> &row_w, int &count) const { int left, top, right, bottom; getContentsMargins(&left, &top, &right, &bottom); rect = rect.adjusted(left, top, -right, -bottom); // Firt let's do orientation count only on the first line count = layout_count_approx(rect); while (layout_size(row_h, row_w, count) > rect.right() && count > 1) { // Not using orientation count go down if we can't fit (if we can then // just be happy with what we have) count--; } } void StaticTableLayout::do_layout(const QRect &rect) { if (cch_do_layout.size == rect.size() && cch_do_layout.count == items.size()) // No effective change so don't do layout return; cch_do_layout.size = rect.size(); cch_do_layout.count = items.size(); int row_h; QList> row_w; int count; QRect reff(rect); layout_parms(reff, row_h, row_w, count); int col = 0; int x = reff.x(), y = reff.y(); for (auto &item : items) { for (int ii = 0; ii < item.size(); ii++) { item[ii]->setGeometry(QRect(x, y, row_w[col][ii], row_h)); x += row_w[col][ii] + shspace; } x += bhspace - shspace; if (++col >= count) { col = 0; x = reff.x(); y += row_h + vspace; } } row_height = row_h; row_widths = row_w; } int StaticTableLayout::layout_height(int width) const { QRect reff(0, 0, width, 0); int row_h; QList> row_w; int count; layout_parms(reff, row_h, row_w, count); return (row_h + vspace) * ((items.size() + count - 1) / count); } QVector StaticTableLayout::list2vec(const QList &w) { QVector v; for (auto &i : w) { addChildWidget(i); v.append(new QWidgetItem(i)); } return v; } StaticTable::StaticTable(QWidget *parent) : QWidget(parent), layout(this) { setSizePolicy(QSizePolicy::MinimumExpanding, QSizePolicy::Fixed); } int StaticTable::count() { return layout.count(); } void StaticTable::addRow(const QList &w) { layout.addRow(w); } void StaticTable::insertRow(const QList &w, int i) { layout.insertRow(w, i); } void StaticTable::removeRow(int i) { layout.removeRow(i); } void StaticTable::clearRows() { layout.clearRows(); } int StaticTable::columns() { return qMax(layout.columns(), 1); } int StaticTable::row_size() { return layout.real_row_height(); } void StaticTable::paintEvent(QPaintEvent *) { if (layout.columns() <= 0) // Don't paint unless we have at least one column return; QPainter p(this); p.setPen(QPen(QColor(200, 200, 200))); QRect rect; QVector separators; for (int i = 0; i < layout.count(); i++) { int row = i / layout.columns(); int col = i % layout.columns(); if ((col % 2) == (row % 2)) p.setBrush(QBrush(QColor(255, 255, 255))); else p.setBrush(QBrush(QColor(235, 235, 235))); layout.itemRect(rect, separators, i); int x = rect.left(); // just to store x if (col <= 0) // this is left most row rect.setLeft(-2); if (col >= (layout.columns() - 1)) rect.setRight(width()); if (row <= 0) rect.setTop(-2); p.drawRect(rect); for (int separator : separators) { int sep_x = x + separator; p.drawLine(sep_x, rect.top(), sep_x, rect.bottom()); } } } ================================================ FILE: src/gui/statictable.h ================================================ #ifndef STATICTABLE_H #define STATICTABLE_H #include #include #include #include /* * This implements new layout and widget in the same time. * The Basic idea is that we need some table view that can also fill in horizontal space. This * widget paints simple table underneath the widgets and lays them out in to them. It shows more * than one column when there is enough horizontal space. */ class StaticTableLayout : public QLayout { public: explicit StaticTableLayout( QWidget *parent, int margin = 4, int horizontal_big_spacing = 4, int horizontal_small_spacing = 8, int vertical_spacing = 4); ~StaticTableLayout() override; Qt::Orientations expandingDirections() const override; bool hasHeightForWidth() const override; int heightForWidth(int) const override; QSize minimumSize() const override; void setGeometry(const QRect &rect) override; QSize sizeHint() const override; void addItem(QLayoutItem *item) override; QLayoutItem *itemAt(int index) const override; QLayoutItem *takeAt(int index) override; int count() const override; // This returns number of item blocks void addRow(const QList &); // This adds a row of widgets void insertRow(const QList &, int i); // Insert row to given position while shifting // all others up void removeRow(int i); // Remove row void clearRows(); // Clear all rows from table void itemRect(QRect &rect, QVector &separators, int i); // This returns a single item // rectangle (if expand_margin, and // it's on edge also count in // margin) int columns(); int real_row_height() const; protected: int shspace, bhspace, vspace; QVector> items; int row_height {}; QList> row_widths; int layout_count_approx(const QRect &rect) const; int layout_size(int &row_h, QList> &row_w, int count) const; void layout_parms(QRect &rect, int &row_h, QList> &row_w, int &count) const; void do_layout(const QRect &rect); int layout_height(int width) const; QVector list2vec(const QList &); struct { QSize size; int count {}; } cch_do_layout; mutable struct { int w, count; int width; } cch_heightForWidth {}; mutable struct { int count {}; QSize size; } cch_minSize; }; class StaticTable : public QWidget { public: explicit StaticTable(QWidget *parent = nullptr); int count(); void addRow(const QList &); void insertRow(const QList &, int i); void removeRow(int i); void clearRows(); int columns(); int row_size(); // return real row size (height) including spacing protected: void paintEvent(QPaintEvent *) override; StaticTableLayout layout; }; #endif // STATICTABLE_H ================================================ FILE: src/gui/textsignalaction.cpp ================================================ #include "textsignalaction.h" #include #include TextSignalAction::TextSignalAction(QObject *parent) : Super(parent) { connect(this, &TextSignalAction::triggered, this, &TextSignalAction::process_triggered); } TextSignalAction::TextSignalAction(const QString &text, QObject *parent) : Super(text, parent) , signal_text(text) { connect(this, &TextSignalAction::triggered, this, &TextSignalAction::process_triggered); } TextSignalAction::TextSignalAction(const QString &text, QString signal_text, QObject *parent) : Super(text, parent) , signal_text(std::move(signal_text)) { connect(this, &TextSignalAction::triggered, this, &TextSignalAction::process_triggered); } TextSignalAction::TextSignalAction(const QIcon &icon, const QString &text, QObject *parent) : Super(icon, text, parent) , signal_text(text) { connect(this, &TextSignalAction::triggered, this, &TextSignalAction::process_triggered); } TextSignalAction::TextSignalAction( const QIcon &icon, const QString &text, QString signal_text, QObject *parent) : Super(icon, text, parent) , signal_text(std::move(signal_text)) { connect(this, &TextSignalAction::triggered, this, &TextSignalAction::process_triggered); } void TextSignalAction::process_triggered(bool checked) { (void)checked; emit activated(signal_text); } ================================================ FILE: src/gui/textsignalaction.h ================================================ #ifndef TEXTSIGNALACTION_H #define TEXTSIGNALACTION_H #include #include class TextSignalAction : public QAction { Q_OBJECT using Super = QAction; public: explicit TextSignalAction(QObject *parent = nullptr); explicit TextSignalAction(const QString &text, QObject *parent = nullptr); TextSignalAction(const QString &text, QString signal_text, QObject *parent = nullptr); TextSignalAction(const QIcon &icon, const QString &text, QObject *parent = nullptr); TextSignalAction( const QIcon &icon, const QString &text, QString signal_text, QObject *parent = nullptr); signals: void activated(QString signal_text); protected slots: void process_triggered(bool checked); protected: QString signal_text; }; #endif // TEXTSIGNALACTION_H ================================================ FILE: src/gui/ui/hexlineedit.cpp ================================================ #include "hexlineedit.h" HexLineEdit::HexLineEdit(QWidget *parent, int digits, int base, const QString &prefix) : Super(parent) { this->base = base; this->digits = digits; this->prefix = prefix; last_set = 0; QChar dmask; QString t = ""; QString mask = ""; for (int i = 0; i < prefix.count(); i++) { mask += "\\" + QString(prefix.at(i)); } switch (base) { case 10: mask += "D"; dmask = 'd'; break; case 2: mask += "B"; dmask = 'b'; break; case 16: case 0: default: mask += "H"; dmask = 'h'; break; } if (digits > 1) { t.fill(dmask, digits - 1); } mask += t; setInputMask(mask); connect(this, &QLineEdit::editingFinished, this, &HexLineEdit::on_edit_finished); set_value(0); } void HexLineEdit::set_value(uint32_t value) { QString s, t = ""; last_set = value; s = QString::number(value, base); if (s.count() < digits) { t.fill('0', digits - s.count()); } setText(prefix + t + s); } void HexLineEdit::on_edit_finished() { bool ok; uint32_t val; val = text().toULong(&ok, 16); if (!ok) { set_value(last_set); return; } last_set = val; emit value_edit_finished(val); } ================================================ FILE: src/gui/ui/hexlineedit.h ================================================ #ifndef HEXLINEEDIT_H #define HEXLINEEDIT_H #include #include class HexLineEdit : public QLineEdit { Q_OBJECT using Super = QLineEdit; public: explicit HexLineEdit( QWidget *parent = nullptr, int digits = 8, int base = 0, const QString &prefix = "0x"); public slots: void set_value(uint32_t value); signals: void value_edit_finished(uint32_t value); private slots: void on_edit_finished(); private: int base; int digits; QString prefix; uint32_t last_set; }; #endif // HEXLINEEDIT_H ================================================ FILE: src/gui/ui/pow2spinbox.cpp ================================================ #include "pow2spinbox.h" Pow2SpinBox::Pow2SpinBox(QWidget *parent) : QSpinBox(parent) { setRange(1, 1024); setValue(1); } QValidator::State Pow2SpinBox::validate(QString &input, int &pos) const { Q_UNUSED(pos); if (input.isEmpty()) return QValidator::Intermediate; bool ok = false; qint64 v = input.toLongLong(&ok); if (!ok || v <= 0) return QValidator::Invalid; if ((v & (v - 1)) == 0) return QValidator::Acceptable; return QValidator::Intermediate; } int Pow2SpinBox::valueFromText(const QString &text) const { return text.toInt(); } QString Pow2SpinBox::textFromValue(int value) const { return QString::number(value); } void Pow2SpinBox::stepBy(int steps) { int v = value(); if (v < 1) v = 1; auto isPow2 = [](int x) { return x > 0 && (x & (x - 1)) == 0; }; auto nextPow2 = [](int x) -> int { if (x <= 1) return 1; int p = 1; while (p < x && (p << 1) > 0) { p <<= 1; } return p; }; auto prevPow2 = [](int x) -> int { if (x <= 1) return 1; int p = 1; while ((p << 1) <= x) { p <<= 1; } if (p > x) { p >>= 1; } return p; }; if (steps > 0) { if (!isPow2(v)) { v = nextPow2(v); } else { for (int i = 0; i < steps; ++i) { if (v > (maximum() >> 1)) { v = maximum(); break; } v <<= 1; } } } else { if (!isPow2(v)) { v = prevPow2(v); } else { for (int i = 0; i < -steps; ++i) { if (v <= 1) { v = 1; break; } v >>= 1; } } } setValue(qBound(minimum(), v, maximum())); } ================================================ FILE: src/gui/ui/pow2spinbox.h ================================================ #ifndef POW2SPINBOX_H #define POW2SPINBOX_H #include #include class Pow2SpinBox : public QSpinBox { Q_OBJECT public: explicit Pow2SpinBox(QWidget *parent = nullptr); protected: QValidator::State validate(QString &input, int &pos) const override; int valueFromText(const QString &text) const override; QString textFromValue(int value) const override; void stepBy(int steps) override; }; #endif // POW2SPINBOX_H ================================================ FILE: src/gui/widgets/hidingtabwidget.cpp ================================================ #include "hidingtabwidget.h" void HidingTabWidget::tabInserted(int index) { QTabWidget::tabInserted(index); if (count() == 1) { show(); requestAddRemoveTab(this, true); } tabCountChanged(); } void HidingTabWidget::tabRemoved(int index) { QTabWidget::tabRemoved(index); if (count() == 0) { hide(); requestAddRemoveTab(this, false); } tabCountChanged(); } void HidingTabWidget::addRemoveTabRequested(QWidget *tab, bool exists) { if (!exists) { removeTab(indexOf(tab)); } else { addTab(tab, tab->windowTitle()); } } ================================================ FILE: src/gui/widgets/hidingtabwidget.h ================================================ #ifndef HIDINGTABWIDGET_H #define HIDINGTABWIDGET_H #include #include /** A widget that hides itself when it has no tabs. */ class HidingTabWidget : public QTabWidget { Q_OBJECT using Super = QTabWidget; public: explicit HidingTabWidget(QWidget *parent = nullptr) : Super(parent) {}; void tabInserted(int index) override; void tabRemoved(int index) override; signals: void requestAddRemoveTab(QWidget *tab, bool visible); public slots: void addRemoveTabRequested(QWidget *tab, bool exists); protected: virtual void tabCountChanged() {}; }; #endif // HIDINGTABWIDGET_H ================================================ FILE: src/gui/windows/cache/cachedock.cpp ================================================ #include "cachedock.h" CacheDock::CacheDock(QWidget *parent, const QString &type) : QDockWidget(parent) { top_widget = new QWidget(this); setWidget(top_widget); layout_box = new QVBoxLayout(top_widget); top_form = new QWidget(top_widget); top_form->setVisible(false); layout_box->addWidget(top_form); layout_top_form = new QFormLayout(top_form); l_hit = new QLabel("0", top_form); l_hit->setTextFormat(Qt::PlainText); layout_top_form->addRow("Hit:", l_hit); l_miss = new QLabel("0", top_form); l_miss->setTextFormat(Qt::PlainText); layout_top_form->addRow("Miss:", l_miss); l_m_reads = new QLabel("0", top_form); l_m_reads->setTextFormat(Qt::PlainText); layout_top_form->addRow("Memory reads:", l_m_reads); l_m_writes = new QLabel("0", top_form); l_m_writes->setTextFormat(Qt::PlainText); layout_top_form->addRow("Memory writes:", l_m_writes); l_stalled = new QLabel("0", top_form); l_stalled->setTextFormat(Qt::PlainText); layout_top_form->addRow("Memory stall cycles:", l_stalled); l_hit_rate = new QLabel("0.000%", top_form); l_hit_rate->setTextFormat(Qt::PlainText); layout_top_form->addRow("Hit rate:", l_hit_rate); l_speed = new QLabel("100%", top_form); l_speed->setTextFormat(Qt::PlainText); layout_top_form->addRow("Improved speed:", l_speed); graphicsview = new GraphicsView(top_widget); graphicsview->setVisible(false); layout_box->addWidget(graphicsview); cachescene = nullptr; no_cache = new QLabel("No " + type + " Cache configured", top_widget); layout_box->addWidget(no_cache); setObjectName(type + "Cache"); setWindowTitle(type + " Cache"); } void CacheDock::setup(const machine::Cache *cache, bool cache_after_cache) { memory_reads = 0; memory_writes = 0; hit = 0; miss = 0; stalled = 0; speed_improv = 0.0; hit_rate = 0.0; l_hit->setText("0"); l_miss->setText("0"); l_stalled->setText("0"); l_m_reads->setText("0"); l_m_writes->setText("0"); l_hit_rate->setText("0.000%"); l_speed->setText("100%"); l_speed->setHidden(cache_after_cache); if (cache != nullptr) { connect(cache, &machine::Cache::hit_update, this, &CacheDock::hit_update); connect(cache, &machine::Cache::miss_update, this, &CacheDock::miss_update); connect(cache, &machine::Cache::memory_reads_update, this, &CacheDock::memory_reads_update); connect( cache, &machine::Cache::memory_writes_update, this, &CacheDock::memory_writes_update); connect(cache, &machine::Cache::statistics_update, this, &CacheDock::statistics_update); } top_form->setVisible(cache != nullptr); no_cache->setVisible(cache == nullptr || !cache->get_config().enabled()); delete cachescene; cachescene = new CacheViewScene(cache); graphicsview->setScene(cachescene); graphicsview->setVisible(cache != nullptr && cache->get_config().enabled()); } void CacheDock::paintEvent(QPaintEvent *event) { l_stalled->setText(QString::number(stalled)); l_hit_rate->setText(QString::number(hit_rate, 'f', 3) + QString("%")); l_speed->setText(QString::number(speed_improv, 'f', 0) + QString("%")); l_hit->setText(QString::number(hit)); l_miss->setText(QString::number(miss)); l_m_reads->setText(QString::number(memory_reads)); l_m_writes->setText(QString::number(memory_writes)); QDockWidget::paintEvent(event); } void CacheDock::hit_update(unsigned val) { hit = val; } void CacheDock::miss_update(unsigned val) { miss = val; } void CacheDock::memory_reads_update(unsigned val) { memory_reads = val; } void CacheDock::memory_writes_update(unsigned val) { memory_writes = val; } void CacheDock::statistics_update(unsigned stalled_cycles, double speed_improv, double hit_rate) { this->stalled = stalled_cycles; this->hit = hit_rate; this->speed_improv = speed_improv; } ================================================ FILE: src/gui/windows/cache/cachedock.h ================================================ #ifndef CACHEDOCK_H #define CACHEDOCK_H #include "cacheview.h" #include "graphicsview.h" #include "machine/machine.h" #include #include #include class CacheDock : public QDockWidget { Q_OBJECT public: CacheDock(QWidget *parent, const QString &type); void setup(const machine::Cache *cache, bool cache_after_cache = false); void paintEvent(QPaintEvent *event) override; private slots: void hit_update(unsigned); void miss_update(unsigned); void memory_reads_update(unsigned val); void memory_writes_update(unsigned val); void statistics_update(unsigned stalled_cycles, double speed_improv, double hit_rate); private: QVBoxLayout *layout_box; QWidget *top_widget, *top_form; QFormLayout *layout_top_form; QLabel *l_hit, *l_miss, *l_stalled, *l_speed, *l_hit_rate; QLabel *no_cache; QLabel *l_m_reads, *l_m_writes; GraphicsView *graphicsview; CacheViewScene *cachescene; // Statistics unsigned memory_reads = 0; unsigned memory_writes = 0; unsigned hit = 0; unsigned miss = 0; unsigned stalled = 0; double speed_improv = 0.0; double hit_rate = 0.0; }; #endif // CACHEDOCK_H ================================================ FILE: src/gui/windows/cache/cacheview.cpp ================================================ #include "cacheview.h" #include "fontsize.h" #include #include ////////////////////// #define ROW_HEIGHT 14 #define VD_WIDTH 10 #define DATA_WIDTH 72 #define PENW 1 #define LETTERW 7 ////////////////////// #include "common/endian.h" #include "machine/memory/cache/cache.h" #include using namespace std; static inline unsigned int bitsToRepresent(quint32 range_max_val) { return 32 - qCountLeadingZeroBits(range_max_val); } CacheAddressBlock::CacheAddressBlock(const machine::Cache *cache, unsigned width) { rows = cache->get_config().set_count(); columns = cache->get_config().block_size(); s_row = cache->get_config().set_count() > 1 ? bitsToRepresent(cache->get_config().set_count() - 1) : 0; this->width = width; s_col = cache->get_config().block_size() > 1 ? bitsToRepresent(cache->get_config().block_size() - 1) : 0; s_tag = 30 - s_row - s_col; // 32 bits - 2 unused and then every bit used // for different index this->width = width; tag = 0; row = 0; col = 0; connect(cache, &machine::Cache::cache_update, this, &CacheAddressBlock::cache_update); } QRectF CacheAddressBlock::boundingRect() const { return { 0, 0, static_cast(width), 40 }; } void CacheAddressBlock::paint( QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) { (void)option; (void)widget; QFont fnt; fnt.setPointSize(7); painter->setFont(fnt); unsigned wpos = 5; // Part used for tag (we expect that tag is always used) unsigned wid = s_tag == 0 ? 0 : (((s_tag - 1) / 4) + 1); unsigned tag_center = wpos + wid * LETTERW / 2 + 1; QRectF rect(wpos, 16, wid * LETTERW + 2, ROW_HEIGHT); painter->drawRect(rect); painter->drawText(rect, Qt::AlignCenter, QString("%1").arg(tag, wid, 16, QChar('0'))); wpos += wid * LETTERW + 2; // Part used for the set unsigned row_center = wpos; if (s_row > 0) { wid = s_row == 0 ? 0 : (((s_row - 1) / 4) + 1); row_center += wid * LETTERW / 2 + 1; rect = QRectF(wpos, 16, wid * LETTERW + 2, ROW_HEIGHT); painter->drawRect(rect); painter->drawText(rect, Qt::AlignCenter, QString("%1").arg(row, wid, 16, QChar('0'))); wpos += wid * LETTERW + 2; } // Part used for block unsigned col_center = wpos; if (s_col > 0) { wid = s_col == 0 ? 0 : (((s_col - 1) / 4) + 1); col_center += wid * LETTERW / 2 + 1; rect = QRectF(wpos, 16, wid * LETTERW + 2, ROW_HEIGHT); painter->drawRect(rect); painter->drawText(rect, Qt::AlignCenter, QString("%1").arg(col, wid, 16, QChar('0'))); wpos += wid * LETTERW + 2; } // Part used for two lowers bits painter->setBrush(QBrush(QColor(Qt::gray))); painter->drawRect(wpos, 16, LETTERW + 2, ROW_HEIGHT); painter->setBrush(QBrush(QColor(Qt::black))); wpos += LETTERW + 2; // Pain address label painter->drawText(QRectF(5, 0, wpos - 5, 14), Qt::AlignCenter, "Address"); uint32_t addr = (((tag * rows) + row) * columns + col) * 4; painter->drawText( QRectF(50, 0, wpos + 40, 14), Qt::AlignCenter, "0x" + QString("%1").arg(addr, 8, 16, QChar('0'))); QPen p; p.setWidth(2); painter->setPen(p); // Tag line painter->drawLine(-8, 40, -8, 33); painter->drawLine(-8, 33, tag_center, 33); painter->drawLine(tag_center, 33, tag_center, 30); // set line if (s_row > 0) { painter->drawLine(-4, 40, row_center, 40); painter->drawLine(row_center, 40, row_center, 30); } // block line if (s_col > 0) { painter->drawLine(width - 16, 40, col_center, 40); painter->drawLine(col_center, 40, col_center, 30); } } void CacheAddressBlock::cache_update( unsigned associat, unsigned set, unsigned col, bool valid, bool dirty, uint32_t tag, const uint32_t *data, bool write) { (void)associat; (void)valid; (void)dirty; (void)data; (void)write; this->tag = tag; this->row = set; this->col = col; update(); } CacheViewBlock::CacheViewBlock(const machine::Cache *cache, unsigned block, bool last) : QGraphicsObject(nullptr) , simulated_machine_endian(cache->simulated_machine_endian) { islast = last; this->block = block; rows = cache->get_config().set_count(); columns = cache->get_config().block_size(); curr_row = 0; last_set = 0; last_col = 0; last_highlighted = false; QFont font; font.setPixelSize(FontSize::SIZE7); validity = new QGraphicsSimpleTextItem *[rows]; if (cache->get_config().write_policy() == machine::CacheConfig::WP_BACK) { dirty = new QGraphicsSimpleTextItem *[rows]; } else { dirty = nullptr; } tag = new QGraphicsSimpleTextItem *[rows]; data = new QGraphicsSimpleTextItem **[rows]; int row_y = 1; for (unsigned i = 0; i < rows; i++) { int row_x = 2; validity[i] = new QGraphicsSimpleTextItem("0", this); validity[i]->setPos(row_x, row_y); validity[i]->setFont(font); row_x += VD_WIDTH; if (dirty) { dirty[i] = new QGraphicsSimpleTextItem(this); dirty[i]->setPos(row_x, row_y); dirty[i]->setFont(font); row_x += VD_WIDTH; } tag[i] = new QGraphicsSimpleTextItem(this); tag[i]->setPos(row_x, row_y); tag[i]->setFont(font); row_x += DATA_WIDTH; data[i] = new QGraphicsSimpleTextItem *[columns]; for (unsigned y = 0; y < columns; y++) { data[i][y] = new QGraphicsSimpleTextItem(this); data[i][y]->setPos(row_x, row_y); data[i][y]->setFont(font); row_x += DATA_WIDTH; } row_y += ROW_HEIGHT; } unsigned wd = 1; auto *l_validity = new QGraphicsSimpleTextItem("V", this); l_validity->setFont(font); QRectF box = l_validity->boundingRect(); l_validity->setPos(wd + (VD_WIDTH - box.width()) / 2, -1 - box.height()); wd += VD_WIDTH; if (cache->get_config().write_policy() == machine::CacheConfig::WP_BACK) { auto *l_dirty = new QGraphicsSimpleTextItem("D", this); l_dirty->setFont(font); box = l_dirty->boundingRect(); l_dirty->setPos(wd + (VD_WIDTH - box.width()) / 2, -1 - box.height()); wd += VD_WIDTH; } auto *l_tag = new QGraphicsSimpleTextItem("Tag", this); l_tag->setFont(font); box = l_tag->boundingRect(); l_tag->setPos(wd + (DATA_WIDTH - box.width()) / 2, -1 - box.height()); wd += DATA_WIDTH; auto *l_data = new QGraphicsSimpleTextItem("Data", this); l_data->setFont(font); box = l_data->boundingRect(); l_data->setPos(wd + (columns * DATA_WIDTH - box.width()) / 2, -1 - box.height()); connect(cache, &machine::Cache::cache_update, this, &CacheViewBlock::cache_update); } CacheViewBlock::~CacheViewBlock() { delete[] validity; delete[] dirty; delete[] tag; for (unsigned y = 0; y < rows; y++) { delete[] data[y]; } delete[] data; } QRectF CacheViewBlock::boundingRect() const { return QRectF( -PENW / 2 - 11, -PENW / 2 - 16, VD_WIDTH + (dirty ? VD_WIDTH : 0) + DATA_WIDTH * (columns + 1) + PENW + 12 + (columns > 1 ? 7 : 0), ROW_HEIGHT * rows + PENW + 50); } void CacheViewBlock::paint( QPainter *painter, const QStyleOptionGraphicsItem *option __attribute__((unused)), QWidget *widget __attribute__((unused))) { // Draw horizontal lines for (unsigned i = 0; i <= rows; i++) { painter->drawLine( 0, i * ROW_HEIGHT, VD_WIDTH + (dirty ? VD_WIDTH : 0) + DATA_WIDTH * (columns + 1), i * ROW_HEIGHT); } // Draw vertical lines painter->drawLine(0, 0, 0, rows * ROW_HEIGHT); int c_width = VD_WIDTH; painter->drawLine(c_width, 0, c_width, rows * ROW_HEIGHT); if (dirty) { c_width += VD_WIDTH; painter->drawLine(c_width, 0, c_width, rows * ROW_HEIGHT); } c_width += DATA_WIDTH; painter->drawLine(c_width, 0, c_width, rows * ROW_HEIGHT); for (unsigned i = 0; i < columns; i++) { c_width += DATA_WIDTH; painter->drawLine(c_width, 0, c_width, rows * ROW_HEIGHT); } QPen p_wide, p; p.setWidth(1); p_wide.setWidth(2); painter->setPen(p); // Tag compare unsigned allright = (dirty ? 2 : 1) * VD_WIDTH + DATA_WIDTH * (columns + 1); unsigned bottom = ROW_HEIGHT * rows; unsigned tag_center = (dirty ? 2 : 1) * VD_WIDTH + DATA_WIDTH / 2; painter->drawEllipse(QPointF(tag_center, bottom + 15), 5, 5); painter->drawText(QRectF(tag_center - 5, bottom + 9.5, 10, 10), Qt::AlignCenter, "="); painter->setPen(p_wide); painter->drawLine(tag_center, bottom, tag_center, bottom + 10); painter->setPen(p); // And painter->drawLine(tag_center + 10, bottom + 25, tag_center + 10, bottom + 35); painter->drawLine(tag_center + 10, bottom + 25, tag_center + 15, bottom + 25); painter->drawLine(tag_center + 10, bottom + 35, tag_center + 15, bottom + 35); painter->drawArc(tag_center + 10, bottom + 25, 10, 10, 270 * 16, 180 * 16); // Connection from and to right painter->drawLine(tag_center + 20, bottom + 30, allright, bottom + 30); // Connection from valid to and painter->drawLine(VD_WIDTH / 2, bottom, VD_WIDTH / 2, bottom + 32); painter->drawLine(VD_WIDTH / 2, bottom + 32, tag_center + 10, bottom + 32); // Connection from tag comparison to and painter->drawLine(tag_center, bottom + 20, tag_center, bottom + 28); painter->drawLine(tag_center, bottom + 28, tag_center + 10, bottom + 28); unsigned data_start = (dirty ? 2 : 1) * VD_WIDTH + DATA_WIDTH; if (columns > 1) { // Output mutex const QPointF poly[] = { QPointF(data_start, bottom + 10), QPointF(data_start + columns * DATA_WIDTH, bottom + 10), QPointF(data_start + columns * DATA_WIDTH - 10, bottom + 20), QPointF(data_start + 10, bottom + 20) }; painter->drawPolygon(poly, sizeof(poly) / sizeof(QPointF)); unsigned data_center = data_start + DATA_WIDTH * columns / 2; painter->setPen(p_wide); painter->drawLine(data_center, bottom + 20, data_center, bottom + 25); painter->drawLine(data_center, bottom + 25, allright, bottom + 25); for (unsigned i = 0; i < columns; i++) { unsigned xpos = data_start + i * DATA_WIDTH + DATA_WIDTH / 2; painter->drawLine(xpos, bottom, xpos, bottom + 10); } // Mutex source painter->drawLine(allright + 5, -16, allright + 5, bottom + 15); painter->drawLine( allright + 5, bottom + 15, data_start + columns * DATA_WIDTH - 4, bottom + 15); if (!islast) painter->drawLine(allright + 5, bottom + 15, allright + 5, bottom + 40); } else { // Wire with data to right painter->setPen(p_wide); painter->drawLine( data_start + DATA_WIDTH / 2, bottom, data_start + DATA_WIDTH / 2, bottom + 25); painter->drawLine(data_start + DATA_WIDTH / 2, bottom + 25, allright, bottom + 25); } // Connection with tag painter->setPen(p_wide); painter->drawLine(-9, -16, -9, bottom + 15); painter->drawLine(-9, bottom + 15, tag_center - 5, bottom + 15); if (!islast) { painter->drawLine(-9, bottom + 15, -9, bottom + 40); } // Connection with row if (rows > 1) { unsigned selected = ROW_HEIGHT * curr_row + ROW_HEIGHT / 2; painter->drawLine(-5, -16, -5, islast ? selected : bottom + 40); painter->drawLine(-5, selected, 0, selected); } } void CacheViewBlock::cache_update( unsigned associat, unsigned set, unsigned col, bool valid, bool dirty, uint32_t tag, const uint32_t *data, bool write) { (void)col; if (associat != block) { if (last_highlighted) { this->data[last_set][last_col]->setBrush(QBrush(QColor(0, 0, 0))); } last_highlighted = false; return; // Ignore blocks that are not used } validity[set]->setText(valid ? "1" : "0"); if (this->dirty) { this->dirty[set]->setText(valid ? (dirty ? "1" : "0") : ""); } // TODO calculate correct size of tag this->tag[set]->setText( valid ? QString("0x") + QString("%1").arg(tag, 8, 16, QChar('0')) : QString("")); for (unsigned i = 0; i < columns; i++) { this->data[set][i]->setText( valid ? QString("0x") + QString("%1").arg( byteswap_if(data[i], simulated_machine_endian != NATIVE_ENDIAN), 8, 16, QChar('0')) : QString("")); // TODO Use cache API } if (last_highlighted) { this->data[last_set][last_col]->setBrush(QBrush(QColor(0, 0, 0))); } if (write) { this->data[set][col]->setBrush(QBrush(QColor(240, 0, 0))); } else { this->data[set][col]->setBrush(QBrush(QColor(0, 0, 240))); } last_highlighted = true; curr_row = set; last_set = set; last_col = col; update(); } CacheViewScene::CacheViewScene(const machine::Cache *cache) { associativity = cache->get_config().associativity(); block = new CacheViewBlock *[associativity]; int offset = 0; for (unsigned i = 0; i < associativity; i++) { block[i] = new CacheViewBlock(cache, i, i >= (associativity - 1)); addItem(block[i]); block[i]->setPos(1, offset); offset += block[i]->boundingRect().height(); } ablock = new CacheAddressBlock(cache, block[0]->boundingRect().width()); addItem(ablock); ablock->setPos(0, -ablock->boundingRect().height() - 16); } CacheViewScene::~CacheViewScene() { delete[] block; } ================================================ FILE: src/gui/windows/cache/cacheview.h ================================================ #ifndef CACHEVIEW_H #define CACHEVIEW_H #include "common/endian.h" #include "graphicsview.h" #include "machine/machine.h" #include #include #include class CacheAddressBlock : public QGraphicsObject { Q_OBJECT public: CacheAddressBlock(const machine::Cache *cache, unsigned width); [[nodiscard]] QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; private slots: void cache_update( unsigned associat, unsigned set, unsigned col, bool valid, bool dirty, uint32_t tag, const uint32_t *data, bool write); private: unsigned rows, columns; unsigned tag, row, col; unsigned s_tag, s_row, s_col; unsigned width; }; class CacheViewBlock : public QGraphicsObject { Q_OBJECT public: CacheViewBlock(const machine::Cache *cache, unsigned block, bool last); ~CacheViewBlock() override; [[nodiscard]] QRectF boundingRect() const override; void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; private slots: virtual void cache_update( unsigned associat, unsigned set, unsigned col, bool valid, bool dirty, uint32_t tag, const uint32_t *data, bool write); private: const Endian simulated_machine_endian; bool islast; unsigned block; unsigned rows, columns; QGraphicsSimpleTextItem **validity, **dirty, **tag, ***data; unsigned curr_row; bool last_highlighted; unsigned last_set; unsigned last_col; }; class CacheViewScene : public QGraphicsScene { Q_OBJECT public: explicit CacheViewScene(const machine::Cache *cache); ~CacheViewScene() override; private: unsigned associativity; CacheViewBlock **block; CacheAddressBlock *ablock; }; #endif // CACHEVIEW_H ================================================ FILE: src/gui/windows/coreview/components/cache.cpp ================================================ #include "cache.h" Cache::Cache( const machine::Cache *cache, svgscene::SimpleTextItem *hit_text, svgscene::SimpleTextItem *mis_text) : QObject() , hit_text(hit_text) , mis_text(mis_text) { connect(cache, &machine::Cache::hit_update, this, &Cache::hit_update); connect(cache, &machine::Cache::miss_update, this, &Cache::miss_update); } void Cache::hit_update(unsigned value) { hit_text->setText(QString::number(value)); } void Cache::miss_update(unsigned int value) { mis_text->setText(QString::number(value)); } ================================================ FILE: src/gui/windows/coreview/components/cache.h ================================================ #ifndef QTRVSIM_CACHE_H #define QTRVSIM_CACHE_H #include #include #include #include class Cache : public QObject { public: Cache( const machine::Cache *cache, svgscene::SimpleTextItem *hit_text, svgscene::SimpleTextItem *mis_text); protected slots: void hit_update(unsigned value); void miss_update(unsigned value); protected: BORROWED svgscene::SimpleTextItem *hit_text; BORROWED svgscene::SimpleTextItem *mis_text; }; #endif // QTRVSIM_CACHE_H ================================================ FILE: src/gui/windows/coreview/components/value_handlers.cpp ================================================ #include "value_handlers.h" #include using svgscene::SimpleTextItem; const QString BoolValue::COMPONENT_NAME = QStringLiteral("bool-value"); const QString PCValue::COMPONENT_NAME = QStringLiteral("pc-value"); const QString RegValue::COMPONENT_NAME = QStringLiteral("reg-value"); const QString RegIdValue::COMPONENT_NAME = QStringLiteral("reg-id-value"); const QString DebugValue::COMPONENT_NAME = QStringLiteral("debug-value"); const QString MultiTextValue::COMPONENT_NAME = QStringLiteral("multi-text-value"); const QString InstructionValue::COMPONENT_NAME = QStringLiteral("instruction-value"); BoolValue::BoolValue(SimpleTextItem *const element, const bool &data) : element(element) , data(data) {} void BoolValue::update() { element->setText(data ? QStringLiteral("1") : QStringLiteral("0")); } PCValue::PCValue(SimpleTextItem *element, const machine::Address &data) : element(element) , data(data) {} PCValue::PCValue(const PCValue &other) : QObject(other.parent()) , element(other.element) , data(other.data) {} void PCValue::clicked() { emit jump_to_pc(data); } void PCValue::update() { element->setText(QString("0x%1").arg(data.get_raw(), 8, 16, QChar('0'))); } RegValue::RegValue(SimpleTextItem *element, const machine::RegisterValue &data) : element(element) , data(data) {} void RegValue::update() { element->setText(QString("%1").arg(data.as_u32(), 8, 16, QChar('0'))); } RegIdValue::RegIdValue(svgscene::SimpleTextItem *element, const machine::RegisterId &data) : element(element) , data(data) {} void RegIdValue::update() { element->setText(QString("%1").arg(int(data), 2, 10, QChar('0'))); } DebugValue::DebugValue(SimpleTextItem *element, const unsigned int &data) : element(element) , data(data) {} void DebugValue::update() { element->setText(QString("%1").arg(data, 0, 10, QChar(' '))); } MultiTextValue::MultiTextValue(SimpleTextItem *const element, Data data) : element(element) , current_text_index(data.first) , text_table(data.second) , originalBrush(element->brush()) {} void MultiTextValue::update() { if (current_text_index != 0) { // Highlight non-default value. element->setBrush(Qt::red); } else { element->setBrush(originalBrush); } element->setText(text_table.at(current_text_index)); } InstructionValue::InstructionValue(SimpleTextItem *const element, Data data) : element(element) , instruction_data(data.first) , address_data(data.second) {} void InstructionValue::update() { element->setText(instruction_data.to_str(address_data)); } ================================================ FILE: src/gui/windows/coreview/components/value_handlers.h ================================================ /** * Components defined in here update the GUI placeholders with up to date * values that is read from provided source. * * Components accept different types and produce different formatting. * * @file */ #ifndef QTRVSIM_VALUE_HANDLERS_H #define QTRVSIM_VALUE_HANDLERS_H #include #include #include #include #include #include #include #include #include #include class BoolValue { public: BoolValue(svgscene::SimpleTextItem *element, const bool &data); void update(); static const QString COMPONENT_NAME; private: BORROWED svgscene::SimpleTextItem *const element; const bool &data; }; class PCValue : public QObject { Q_OBJECT public slots: void clicked(); signals: void jump_to_pc(machine::Address pc_value); public: PCValue(svgscene::SimpleTextItem *element, const machine::Address &data); PCValue(const PCValue &); void update(); static const QString COMPONENT_NAME; private: BORROWED svgscene::SimpleTextItem *const element; const machine::Address &data; }; class RegValue { public: RegValue(svgscene::SimpleTextItem *element, const machine::RegisterValue &data); void update(); static const QString COMPONENT_NAME; private: BORROWED svgscene::SimpleTextItem *const element; const machine::RegisterValue &data; }; class RegIdValue { public: RegIdValue(svgscene::SimpleTextItem *element, const machine::RegisterId &data); void update(); static const QString COMPONENT_NAME; private: BORROWED svgscene::SimpleTextItem *const element; const machine::RegisterId &data; }; class DebugValue { public: DebugValue(svgscene::SimpleTextItem *element, const unsigned int &data); void update(); static const QString COMPONENT_NAME; private: BORROWED svgscene::SimpleTextItem *const element; const unsigned &data; }; class MultiTextValue { using Source = const std::unordered_map &; using Data = std::pair; public: MultiTextValue(svgscene::SimpleTextItem *element, Data data); void update(); static const QString COMPONENT_NAME; private: BORROWED svgscene::SimpleTextItem *const element; const unsigned ¤t_text_index; Source &text_table; QBrush originalBrush; }; class InstructionValue { using Data = std::pair; public: InstructionValue(svgscene::SimpleTextItem *element, Data data); void update(); static const QString COMPONENT_NAME; private: BORROWED svgscene::SimpleTextItem *const element; const machine::Instruction &instruction_data; const machine::Address &address_data; }; template class Multiplexer { public: Multiplexer( std::vector connections, const SOURCE &active_connection) : connections(std::move(connections)) , active_connection(active_connection) , current_active_connection(0) { // Hide all but first for (size_t i = 1; i < this->connections.size(); ++i) { this->connections.at(i)->hide(); } } void update() { if (current_active_connection != active_connection) { connections.at(static_cast(current_active_connection))->hide(); connections.at(static_cast(active_connection))->show(); current_active_connection = active_connection; } } private: const std::vector connections; const SOURCE &active_connection; SOURCE current_active_connection; }; #endif // QTRVSIM_VALUE_HANDLERS_H ================================================ FILE: src/gui/windows/coreview/data.h ================================================ /** * This file contains maps with data for core visualization. * - Tables of strings. * - Maps of string hook used in SVG files onto lenses into the core state * struct. (see `common/type_utils/lens`) * * @file */ #ifndef QTRVSIM_DATA_H #define QTRVSIM_DATA_H #include "common/type_utils/lens.h" #include using std::pair; using std::unordered_map; using std::vector; class CoreViewScene; using machine::Address; using machine::CoreState; using machine::Instruction; using machine::RegisterId; using machine::RegisterValue; static const std::unordered_map EXCEPTION_NAME_TABLE = { { machine::EXCAUSE_NONE, QStringLiteral("NONE") }, { machine::EXCAUSE_INSN_FAULT, QStringLiteral("I_FAULT") }, { machine::EXCAUSE_INSN_ILLEGAL, QStringLiteral("I_ILLEGAL") }, { machine::EXCAUSE_BREAK, QStringLiteral("BREAK") }, { machine::EXCAUSE_LOAD_MISALIGNED, QStringLiteral("L_MALIGN") }, { machine::EXCAUSE_LOAD_FAULT, QStringLiteral("L_FAULT") }, { machine::EXCAUSE_STORE_MISALIGNED, QStringLiteral("S_MALIGN") }, { machine::EXCAUSE_STORE_FAULT, QStringLiteral("S_FAULT") }, { machine::EXCAUSE_ECALL_U, QStringLiteral("ECALL_U") }, { machine::EXCAUSE_ECALL_S, QStringLiteral("ECALL_S") }, { machine::EXCAUSE_RESERVED_10, QStringLiteral("RES_10") }, { machine::EXCAUSE_ECALL_M, QStringLiteral("ECALL_M") }, { machine::EXCAUSE_INSN_PAGE_FAULT, QStringLiteral("I_PGFAULT") }, { machine::EXCAUSE_LOAD_PAGE_FAULT, QStringLiteral("L_PGFAULT") }, { machine::EXCAUSE_RESERVED_14, QStringLiteral("RES_14") }, { machine::EXCAUSE_STORE_PAGE_FAULT, QStringLiteral("S_PGFAULT") }, // Simulator specific exception cause codes, alliases { machine::EXCAUSE_HWBREAK, QStringLiteral("HWBREAK") }, { machine::EXCAUSE_ECALL_ANY, QStringLiteral("ECALL") }, { machine::EXCAUSE_INT, QStringLiteral("INT") }, }; static const std::unordered_map STALL_TEXT_TABLE = { { 0, QStringLiteral("NORMAL") }, { 1, QStringLiteral("STALL") }, { 2, QStringLiteral("FORWARD") }, }; static const std::unordered_map PRIVILEGE_TEXT_TABLE = { { static_cast(machine::CSR::PrivilegeLevel::UNPRIVILEGED), QStringLiteral("UNPRIV") }, { static_cast(machine::CSR::PrivilegeLevel::SUPERVISOR), QStringLiteral("SUPERV") }, { static_cast(machine::CSR::PrivilegeLevel::HYPERVISOR), QStringLiteral("HYPERV") }, { static_cast(machine::CSR::PrivilegeLevel::MACHINE), QStringLiteral("MACHINE") }, }; /** * Link targets available for use in the SVG. * * EXAMPLE: * ```svg * * Registers * * ``` */ static const unordered_map HYPERLINK_TARGETS { { "#focus_pc", &CoreViewScene::request_jump_to_program_counter_wrapper }, { "#registers", &CoreViewScene::request_registers }, { "#cache_data", &CoreViewScene::request_cache_data }, { "#cache_program", &CoreViewScene::request_cache_program }, { "#data_memory", &CoreViewScene::request_data_memory }, { "#peripherals", &CoreViewScene::request_peripherals }, { "#program_memory", &CoreViewScene::request_program_memory }, { "#terminal", &CoreViewScene::request_terminal }, }; using MultiTextData = pair &>; /** * Maps SVG usable value names to lenses (lazy references) to fields, where thy * can be * retrieved. */ const struct { const unordered_map> BOOL { { QStringLiteral("decode-RegWrite"), LENS(CoreState, pipeline.decode.result.regwrite) }, { QStringLiteral("decode-MemToReg"), LENS(CoreState, pipeline.decode.result.memread) }, { QStringLiteral("decode-MemWrite"), LENS(CoreState, pipeline.decode.result.memwrite) }, { QStringLiteral("decode-MemRead"), LENS(CoreState, pipeline.decode.result.memread) }, { QStringLiteral("decode-BranchBxx"), LENS(CoreState, pipeline.decode.result.branch_bxx) }, { QStringLiteral("decode-BranchJal"), LENS(CoreState, pipeline.decode.result.branch_jal) }, { QStringLiteral("decode-BranchJalr"), LENS(CoreState, pipeline.decode.result.branch_jalr) }, { QStringLiteral("decode-BranchVal"), LENS(CoreState, pipeline.decode.result.branch_val) }, { QStringLiteral("decode-AluMul"), LENS(CoreState, pipeline.decode.internal.alu_mul) }, { QStringLiteral("decode-AluSrc"), LENS(CoreState, pipeline.decode.result.alusrc) }, { QStringLiteral("decode-AuiPC"), LENS(CoreState, pipeline.decode.result.alu_pc) }, { QStringLiteral("exec-RegWrite"), LENS(CoreState, pipeline.execute.result.regwrite) }, { QStringLiteral("exec-MemToReg"), LENS(CoreState, pipeline.execute.result.memread) }, { QStringLiteral("exec-MemWrite"), LENS(CoreState, pipeline.execute.result.memwrite) }, { QStringLiteral("exec-MemRead"), LENS(CoreState, pipeline.execute.result.memread) }, { QStringLiteral("exec-BranchBxx"), LENS(CoreState, pipeline.execute.internal.branch_bxx) }, { QStringLiteral("exec-BranchJal"), LENS(CoreState, pipeline.execute.result.branch_jal) }, { QStringLiteral("exec-BranchJalr"), LENS(CoreState, pipeline.execute.result.branch_jalr) }, { QStringLiteral("exec-BranchVal"), LENS(CoreState, pipeline.execute.result.branch_val) }, { QStringLiteral("exec-AluMul"), LENS(CoreState, pipeline.execute.internal.alu_mul) }, { QStringLiteral("exec-AluSrc"), LENS(CoreState, pipeline.execute.internal.alu_src) }, { QStringLiteral("exec-AuiPC"), LENS(CoreState, pipeline.execute.internal.alu_pc) }, { QStringLiteral("exec-AluZero"), LENS(CoreState, pipeline.execute.result.alu_zero) }, { QStringLiteral("mem-RegWrite"), LENS(CoreState, pipeline.memory.result.regwrite) }, { QStringLiteral("mem-MemToReg"), LENS(CoreState, pipeline.memory.result.memtoreg) }, { QStringLiteral("mem-MemWrite"), LENS(CoreState, pipeline.memory.internal.memwrite) }, { QStringLiteral("mem-MemRead"), LENS(CoreState, pipeline.memory.internal.memread) }, { QStringLiteral("mem-BranchOutcome"), LENS(CoreState, pipeline.memory.internal.branch_outcome) }, { QStringLiteral("mem-BranchJalx"), LENS(CoreState, pipeline.memory.internal.branch_jalx) }, { QStringLiteral("mem-BranchJalr"), LENS(CoreState, pipeline.memory.internal.branch_jalr) }, { QStringLiteral("wb-RegWrite"), LENS(CoreState, pipeline.writeback.internal.regwrite) }, { QStringLiteral("wb-MemToReg"), LENS(CoreState, pipeline.writeback.internal.memtoreg) }, }; const unordered_map> REG { { QStringLiteral("alu-res"), LENS(CoreState, pipeline.execute.result.alu_val) }, { QStringLiteral("alu-src1"), LENS(CoreState, pipeline.execute.internal.alu_src1) }, { QStringLiteral("alu-src2"), LENS(CoreState, pipeline.execute.internal.alu_src2) }, { QStringLiteral("decode-imm"), LENS(CoreState, pipeline.decode.result.immediate_val) }, { QStringLiteral("exec-imm"), LENS(CoreState, pipeline.execute.result.immediate_val) }, { QStringLiteral("decode-inst-bus"), LENS(CoreState, pipeline.decode.internal.inst_bus) }, { QStringLiteral("mem-write-val"), LENS(CoreState, pipeline.memory.internal.mem_write_val) }, { QStringLiteral("mem-write-addr"), LENS(CoreState, pipeline.memory.internal.mem_addr) }, { QStringLiteral("mem-read-val"), LENS(CoreState, pipeline.memory.internal.mem_read_val) }, { QStringLiteral("decode-rs1"), LENS(CoreState, pipeline.decode.result.val_rs) }, { QStringLiteral("decode-rs2"), LENS(CoreState, pipeline.decode.result.val_rt) }, { QStringLiteral("exec-rs1"), LENS(CoreState, pipeline.execute.internal.rs) }, { QStringLiteral("exec-rs2"), LENS(CoreState, pipeline.execute.internal.rt) }, { QStringLiteral("wb"), LENS(CoreState, pipeline.writeback.internal.value) }, }; const unordered_map> REG_ID { { QStringLiteral("decode-rd"), LENS(CoreState, pipeline.decode.result.num_rd) }, { QStringLiteral("exec-rd"), LENS(CoreState, pipeline.execute.result.num_rd) }, { QStringLiteral("mem-rd"), LENS(CoreState, pipeline.memory.result.num_rd) }, { QStringLiteral("wb-rd"), LENS(CoreState, pipeline.writeback.internal.num_rd) }, { QStringLiteral("rs1"), LENS(CoreState, pipeline.decode.result.num_rs) }, { QStringLiteral("rs2"), LENS(CoreState, pipeline.decode.result.num_rt) }, }; const unordered_map> DEBUG_VAL { { QStringLiteral("CycleCount"), LENS(CoreState, cycle_count) }, { QStringLiteral("StallCount"), LENS(CoreState, stall_count) }, { QStringLiteral("decode-AluControl"), LENS(CoreState, pipeline.decode.internal.alu_op_num) }, { QStringLiteral("exec-AluControl"), LENS(CoreState, pipeline.execute.internal.alu_op_num) }, { QStringLiteral("exec-ForwardA"), LENS(CoreState, pipeline.execute.internal.forward_from_rs1_num) }, { QStringLiteral("exec-ForwardB"), LENS(CoreState, pipeline.execute.internal.forward_from_rs2_num) }, }; const unordered_map> PC { { QStringLiteral("fetch-pc"), LENS(CoreState, pipeline.fetch.result.inst_addr) }, }; #define MULTITEXT_LENS(INDEX, TABLE) \ [](const CoreState &base) -> MultiTextData { return { base.INDEX, TABLE }; } const unordered_map< QStringView, LensPair>> MULTI_TEXT { { QStringLiteral("fetch-exception"), MULTITEXT_LENS(pipeline.fetch.internal.excause_num, EXCEPTION_NAME_TABLE) }, { QStringLiteral("decode-exception"), MULTITEXT_LENS(pipeline.decode.internal.excause_num, EXCEPTION_NAME_TABLE) }, { QStringLiteral("execute-exception"), MULTITEXT_LENS(pipeline.execute.internal.excause_num, EXCEPTION_NAME_TABLE) }, { QStringLiteral("memory-exception"), MULTITEXT_LENS(pipeline.memory.internal.excause_num, EXCEPTION_NAME_TABLE) }, { QStringLiteral("hazard"), MULTITEXT_LENS(pipeline.execute.internal.stall_status, STALL_TEXT_TABLE) }, { QStringLiteral("Privilege"), MULTITEXT_LENS(current_privilege_u, PRIVILEGE_TEXT_TABLE) }, }; const unordered_map> INSTRUCTION { { QStringLiteral("fetch"), LENS_PAIR(CoreState, pipeline.fetch.result.inst, pipeline.fetch.result.inst_addr) }, { QStringLiteral("decode"), LENS_PAIR(CoreState, pipeline.decode.result.inst, pipeline.decode.result.inst_addr) }, { QStringLiteral("exec"), LENS_PAIR(CoreState, pipeline.execute.result.inst, pipeline.execute.result.inst_addr) }, { QStringLiteral("mem"), LENS_PAIR(CoreState, pipeline.memory.result.inst, pipeline.memory.result.inst_addr) }, { QStringLiteral("wb"), { LENS_PAIR( CoreState, pipeline.writeback.internal.inst, pipeline.writeback.internal.inst_addr) } }, }; } VALUE_SOURCE_NAME_MAPS; #endif // QTRVSIM_DATA_H ================================================ FILE: src/gui/windows/coreview/scene.cpp ================================================ #include "scene.h" #include "common/logging.h" #include "data.h" #include "machine/core.h" #include #include #include #include #include using std::unordered_map; using std::vector; using svgscene::HyperlinkItem; using svgscene::SimpleTextItem; using svgscene::SvgDocument; using svgscene::SvgDomTree; LOG_CATEGORY("gui.coreview"); CoreViewScene::CoreViewScene(machine::Machine *machine, const QString &core_svg_scheme_name) : SvgGraphicsScene() , program_counter_value((VALUE_SOURCE_NAME_MAPS.PC.at(QStringLiteral("fetch-pc")))( machine->core()->get_state())) { SvgDocument document = svgscene::parseFromFileName(this, QString(":/core/%1.svg").arg(core_svg_scheme_name)); for (auto hyperlink_tree : document.getRoot().findAll()) { this->install_hyperlink(hyperlink_tree.getElement()); } /* * TODO: * Components not implemented: * - colored frames on special values */ const machine::CoreState &core_state = machine->core()->get_state(); // Find all components in the DOM tree and install controllers for them. for (auto component : document.getRoot().findAll("data-component")) { QStringView component_name = component.getAttrValueOr("data-component"); if (component_name.isEmpty()) { continue; } // This switch is performance optimization. // Single char lookup will usually give only one match, outperforming // a hashtable, which has to check the key in the end as well hut // hashing is more complex than single char lookup. switch (component_name.at(0).toLatin1()) { case 'b': { if (component_name == BoolValue::COMPONENT_NAME) { install_value( values.bool_values, VALUE_SOURCE_NAME_MAPS.BOOL, component, core_state); } break; } case 'd': { if (component_name == DebugValue::COMPONENT_NAME) { install_value( values.debug_values, VALUE_SOURCE_NAME_MAPS.DEBUG_VAL, component, core_state); } else if (component_name == QStringLiteral("data-cache")) { if (machine->config().cache_data().enabled()) { auto texts = component.findAll(); // Diagrams.net dow not allow me, to put there some marks. // :( auto miss = texts.takeLast().getElement(); auto hit = texts.takeLast().getElement(); data_cache.reset(new Cache(machine->cache_data(), hit, miss)); } else { component.getElement()->hide(); } } break; } case 'i': { if (component_name == InstructionValue::COMPONENT_NAME) { install_value( values.instruction_values, VALUE_SOURCE_NAME_MAPS.INSTRUCTION, component, core_state); } break; } case 'm': { if (component_name == MultiTextValue::COMPONENT_NAME) { install_value( values.multi_text_values, VALUE_SOURCE_NAME_MAPS.MULTI_TEXT, component, core_state); } else if (component_name == QStringLiteral("mux2")) { const QString &source_name = component.getAttrValueOr("data-source"); // Draw.io does not allow tagging the paths, to I use this style identification // hack. auto conn_trees = component.findAll("stroke-linecap", "round"); if (conn_trees.size() != 2) { WARN( "Mux2 does not have 2 connections found %zi (source: \"%s\").", conn_trees.size(), qPrintable(source_name)); break; } std::vector connections; connections.reserve(conn_trees.size()); std::transform( conn_trees.begin(), conn_trees.end(), std::back_inserter(connections), [](SvgDomTree &e) { return e.getElement(); }); try { const bool &source = VALUE_SOURCE_NAME_MAPS.BOOL.at(source_name)(core_state); values.mux2_values.emplace_back(std::move(connections), source); } catch (std::out_of_range &e) { WARN( "Source for mux2 value not found (source: \"%s\").", qPrintable(source_name)); } } else if (component_name == QStringLiteral("mux3")) { const QString &source_name = component.getAttrValueOr("data-source"); // Draw.io does not allow tagging the paths, to I use this style identification // hack. auto conn_trees = component.findAll("stroke-linecap", "round"); if (conn_trees.size() != 3) { WARN( "Mux3 does not have 3 connections found %lld (source: \"%s\").", conn_trees.size(), qPrintable(source_name)); break; } std::vector connections; connections.reserve(conn_trees.size()); std::transform( conn_trees.begin(), conn_trees.end(), std::back_inserter(connections), [](SvgDomTree &e) { return e.getElement(); }); try { const unsigned &source = VALUE_SOURCE_NAME_MAPS.DEBUG_VAL.at(source_name)(core_state); values.mux3_values.emplace_back(std::move(connections), source); } catch (std::out_of_range &e) { WARN( "Source for mux3 value not found (source: \"%s\").", qPrintable(source_name)); } } break; } case 'p': { if (component_name == PCValue::COMPONENT_NAME) { install_value(values.pc_values, VALUE_SOURCE_NAME_MAPS.PC, component, core_state); } else if (component_name == QStringLiteral("program-cache")) { if (machine->config().cache_program().enabled()) { auto texts = component.findAll(); // Diagrams.net does not allow me, to put there some // marks. :( auto miss = texts.takeLast().getElement(); auto hit = texts.takeLast().getElement(); program_cache.reset(new Cache(machine->cache_program(), hit, miss)); } else { component.getElement()->hide(); } } break; } case 'r': { if (component_name == RegValue::COMPONENT_NAME) { install_value(values.reg_values, VALUE_SOURCE_NAME_MAPS.REG, component, core_state); } else if (component_name == RegIdValue::COMPONENT_NAME) { install_value( values.reg_id_values, VALUE_SOURCE_NAME_MAPS.REG_ID, component, core_state); } break; } } } if (machine->config().hazard_unit() == machine::MachineConfig::HU_NONE) { // Hazard unit conditional hide for (auto elem_tree : document.getRoot().findAll("data-tags", "hazardunit")) { elem_tree.getElement()->hide(); } } update_values(); // Set to initial value - most often zero. // Update coreview with each core step. connect(machine->core(), &machine::Core::step_done, this, &CoreViewScene::update_values); } CoreViewScene::~CoreViewScene() = default; void CoreViewScene::install_hyperlink(svgscene::HyperlinkItem *element) const { if (element->getTargetName() == "#") { DEBUG("Skipping NOP hyperlink."); return; } try { connect( element, &svgscene::HyperlinkItem::triggered, this, HYPERLINK_TARGETS.at(element->getTargetName())); DEBUG("Registered hyperlink to target %s", qPrintable(element->getTargetName())); } catch (std::out_of_range &) { WARN( "Registering hyperlink without valid target (href: \"%s\").", qPrintable(element->getTargetName())); } } template void CoreViewScene::install_value( vector &handler_list, const unordered_map &value_source_name_map, SvgDomTree component, const CoreState &core_state) { SimpleTextItem *text_element = component.template find().getElement(); const QString &source_name = component.getAttrValueOr("data-source"); try { handler_list.emplace_back(text_element, value_source_name_map.at(source_name)(core_state)); DEBUG( "Installing value %s with source %s.", qPrintable(T_handler::COMPONENT_NAME), qPrintable(source_name)); } catch (std::out_of_range &) { WARN( "Source for %s value not found (source: \"%s\").", typeid(T).name(), qPrintable(source_name)); } } template void CoreViewScene::update_value_list(std::vector &value_list) { DEBUG("Calling full update of %s...", typeid(T).name()); for (auto &value_handler : value_list) { value_handler.update(); } } void CoreViewScene::update_values() { update_value_list(values.bool_values); update_value_list(values.debug_values); update_value_list(values.reg_values); update_value_list(values.reg_id_values); update_value_list(values.pc_values); update_value_list(values.multi_text_values); update_value_list(values.instruction_values); update_value_list(values.mux2_values); update_value_list(values.mux3_values); } void CoreViewScene::request_jump_to_program_counter_wrapper() { emit request_jump_to_program_counter(program_counter_value); } CoreViewSceneSimple::CoreViewSceneSimple(machine::Machine *machine) : CoreViewScene(machine, "simple") {} CoreViewScenePipelined::CoreViewScenePipelined(machine::Machine *machine) : CoreViewScene( machine, (machine->config().hazard_unit() == machine::MachineConfig::HU_STALL_FORWARD) ? "forwarding" : "pipeline") {} ================================================ FILE: src/gui/windows/coreview/scene.h ================================================ #ifndef QTRVSIM_SCENE_H #define QTRVSIM_SCENE_H #include "./components/cache.h" #include "./components/value_handlers.h" #include "common/polyfills/qstring_hash.h" #include "graphicsview.h" #include #include #include #include #include #include #include #include class CoreViewScene : public svgscene::SvgGraphicsScene { Q_OBJECT public: CoreViewScene(machine::Machine *machine, const QString &core_svg_scheme_name); ~CoreViewScene() override; /** Hyperlink handler which automatically uses current PC value via this object */ void request_jump_to_program_counter_wrapper(); signals: /* Hyperlink handlers propagated to the main window. */ void request_registers(); void request_data_memory(); void request_program_memory(); void request_jump_to_program_counter(machine::Address addr); void request_cache_program(); void request_cache_data(); void request_peripherals(); void request_terminal(); public slots: /** * Update all installed dynamic values. * * @see install_value */ void update_values(); protected: /** * Lookup link target and connect element one of `request_` slots. * @param element the clickable element */ void install_hyperlink(svgscene::HyperlinkItem *element) const; /** * Create update handler for dynamic value in core view. * * @tparam T_handler one of classes in * `coreview/components/numeric_value.h` * @tparam T type of value to update from * @param handler_list list, where the created handler will be * stored * @param value_source_name_map maps of load_sections_indexes names used in svg to * references in the state struct * @param component text element that will be updated * @param source_name name of data source, see * value_source_name_map */ template void install_value( std::vector &handler_list, const std::unordered_map &value_source_name_map, svgscene::SvgDomTree component, const machine::CoreState &core_state); /** * Update all value components of given type. * * @tparam T type of component * @param value_list list of components * @see install_value */ template void update_value_list(std::vector &value_list); protected: /** * Lists of dynamic values from svg that should updated each cycle. */ struct { std::vector bool_values; std::vector reg_values; std::vector reg_id_values; std::vector debug_values; std::vector pc_values; std::vector multi_text_values; std::vector instruction_values; std::vector> mux2_values; std::vector> mux3_values; } values; Box program_cache; Box data_cache; /** Reference to current PC value to be used to focus PC in program memory on lick */ const machine::Address &program_counter_value; }; class CoreViewSceneSimple : public CoreViewScene { public: explicit CoreViewSceneSimple(machine::Machine *machine); }; class CoreViewScenePipelined : public CoreViewScene { public: explicit CoreViewScenePipelined(machine::Machine *machine); }; #endif ================================================ FILE: src/gui/windows/coreview/schemas/schemas.qrc ================================================ simple.svg pipeline.svg forwarding.svg ================================================ FILE: src/gui/windows/csr/csrdock.cpp ================================================ #include "csrdock.h" #include "csr/controlstate.h" CsrDock::CsrDock(QWidget *parent) : QDockWidget(parent) , xlen(machine::Xlen::_32) , csr_handle(nullptr) { scrollarea = new QScrollArea(this); scrollarea->setWidgetResizable(true); widg = new StaticTable(scrollarea); for (size_t i = 0; i < machine::CSR::REGISTERS.size(); i++) { auto &desc = machine::CSR::REGISTERS.at(i); csr_view[i] = new QLabel(sizeHintText(), widg); csr_view[i]->setTextFormat(Qt::PlainText); csr_view[i]->setFixedSize(csr_view[i]->sizeHint()); csr_view[i]->setText(""); csr_view[i]->setTextInteractionFlags(Qt::TextSelectableByMouse); auto desc_label = new QLabel(QString(desc.name), widg); desc_label->setToolTip( (QString("%0 (0x%1)").arg(desc.description).arg(desc.address.data, 0, 16))); widg->addRow({ desc_label, csr_view[i] }); csr_highlighted[i] = false; } scrollarea->setWidget(widg); setWidget(scrollarea); setObjectName("Control and Status Registers"); setWindowTitle("Control and Status Registers"); pal_normal = QPalette(csr_view[1]->palette()); pal_updated = QPalette(csr_view[1]->palette()); pal_read = QPalette(csr_view[1]->palette()); pal_normal.setColor(QPalette::WindowText, QColor(0, 0, 0)); pal_updated.setColor(QPalette::WindowText, QColor(240, 0, 0)); pal_read.setColor(QPalette::WindowText, QColor(0, 0, 240)); csr_highlighted_any = false; } void CsrDock::setup(machine::Machine *machine) { if (machine == nullptr) { // Reset data for (auto &i : csr_view) { i->setText(""); } return; } // if xlen changes adjust space to show full value if (xlen != machine->config().get_simulated_xlen()) { xlen = machine->config().get_simulated_xlen(); auto *dumy_data_label = new QLabel(sizeHintText(), widg); for (size_t i = 0; i < machine::CSR::REGISTERS.size(); i++) { csr_view[i]->setFixedSize(dumy_data_label->sizeHint()); } delete dumy_data_label; } csr_handle = machine->control_state(); reload(); connect(csr_handle, &machine::CSR::ControlState::write_signal, this, &CsrDock::csr_changed); connect(csr_handle, &machine::CSR::ControlState::read_signal, this, &CsrDock::csr_read); connect(machine, &machine::Machine::tick, this, &CsrDock::clear_highlights); } const char *CsrDock::sizeHintText() { if (xlen == machine::Xlen::_64) return "0x0000000000000000"; else return "0x00000000"; } void CsrDock::reload() { if (csr_handle == nullptr) { return; } clear_highlights(); for (size_t i = 0; i < machine::CSR::REGISTERS.size(); i++) { labelVal(csr_view[i], csr_handle->read_internal(i).as_xlen(xlen)); } } void CsrDock::csr_changed(size_t internal_reg_id, machine::RegisterValue val) { if (isHidden()) { return; } // FIXME assert takes literal SANITY_ASSERT( (uint)internal_reg_id < machine::CSR::REGISTERS.size(), QString("CsrDock received signal with invalid CSR register: ") + QString::number((uint)internal_reg_id)); labelVal(csr_view[(uint)internal_reg_id], val.as_xlen(xlen)); csr_view[internal_reg_id]->setPalette(pal_updated); csr_highlighted[internal_reg_id] = true; csr_highlighted_any = true; } void CsrDock::csr_read(size_t internal_reg_id, machine::RegisterValue val) { (void)val; if (isHidden()) { return; } // FIXME assert takes literal SANITY_ASSERT( (uint)internal_reg_id < machine::CSR::REGISTERS.size(), QString("CsrDock received signal with invalid CSR register: ") + QString::number((uint)internal_reg_id)); if (!csr_highlighted[internal_reg_id]) { csr_view[internal_reg_id]->setPalette(pal_read); } csr_highlighted[internal_reg_id] = true; csr_highlighted_any = true; } void CsrDock::clear_highlights() { if (!csr_highlighted_any) { return; } for (size_t i = 0; i < machine::CSR::REGISTERS.size(); i++) { if (csr_highlighted[i]) { csr_view[i]->setPalette(pal_normal); csr_highlighted[i] = false; } } csr_highlighted_any = false; } void CsrDock::showEvent(QShowEvent *event) { // Slots are inactive when this widget is hidden reload(); QDockWidget::showEvent(event); } void CsrDock::labelVal(QLabel *label, uint64_t value) { QString t = QString("0x") + QString::number(value, 16); label->setText(t); } ================================================ FILE: src/gui/windows/csr/csrdock.h ================================================ #ifndef CSRDOCK_H #define CSRDOCK_H #include "machine/csr/controlstate.h" #include "machine/machine.h" #include "statictable.h" #include #include #include #include #include #include class CsrDock : public QDockWidget { Q_OBJECT public: explicit CsrDock(QWidget *parent); void setup(machine::Machine *machine); void reload(); private slots: void csr_changed(std::size_t internal_reg_id, machine::RegisterValue val); void csr_read(std::size_t internal_reg_id, machine::RegisterValue val); void clear_highlights(); private: void showEvent(QShowEvent *event) override; private: machine::Xlen xlen; // We keep this handle for batch updates when this widget was hidden. const machine::CSR::ControlState *csr_handle {}; const char *sizeHintText(); QT_OWNED StaticTable *widg; QT_OWNED QScrollArea *scrollarea; std::array csr_view {}; bool csr_highlighted[machine::CSR::REGISTERS.size()] {}; bool csr_highlighted_any; QPalette pal_normal; QPalette pal_updated; QPalette pal_read; static void labelVal(QLabel *label, uint64_t val); }; #endif // CSRDOCK_H ================================================ FILE: src/gui/windows/editor/editordock.cpp ================================================ #include "editordock.h" #include "common/logging.h" #include "dialogs/savechanged/savechangeddialog.h" #include "editortab.h" #include "helper/async_modal.h" #include #include #include #include #include #include LOG_CATEGORY("gui.editordock"); #ifdef __EMSCRIPTEN__ #include "qhtml5file.h" #endif int compare_filenames(const QString &filename1, const QString &filename2) { QFileInfo fi1(filename1); QFileInfo fi2(filename2); QString canon1 = fi1.canonicalFilePath(); QString canon2 = fi2.canonicalFilePath(); if (!canon1.isEmpty() && (canon1 == canon2)) { return 2; } if (filename1 == filename2) { return 1; } return 0; } EditorDock::EditorDock(QSharedPointer settings, QTabWidget *parent_tabs, QWidget *parent) : Super(parent) , settings(std::move(settings)) { { auto bar = tabBar(); bar->setMovable(true); QFont font = bar->font(); font.setPointSize(10); font.setBold(false); bar->setFont(font); } setObjectName("EditorDock"); setTabsClosable(true); connect(this, &EditorDock::tabCloseRequested, this, [this](int index) { close_tab(index); }); connect( this, &EditorDock::currentChanged, parent_tabs, [this, parent_tabs](int index) { // Update parent title if (count() == 0 || index < 0) return; auto *editor = get_tab(index)->get_editor(); QString title = QString("&Editor (%1)").arg(editor->title()); parent_tabs->setTabText(parent_tabs->indexOf(this), title); // IMPORTANT: This repeated call solved a very annoying QT resize bug. Do not remove it! parent_tabs->setTabText(parent_tabs->indexOf(this), title); parent_tabs->setCurrentIndex(parent_tabs->indexOf(this)); }, Qt::QueuedConnection); } EditorTab *EditorDock::get_tab(int index) const { return dynamic_cast(widget(index)); } EditorTab *EditorDock::open_file(const QString &filename, bool save_as_required) { auto tab = new EditorTab(line_numbers_visible, this); if (tab->get_editor()->loadFile(filename)) { addTab(tab, tab->title()); setCurrentWidget(tab); if (save_as_required) tab->get_editor()->setSaveAsRequired(save_as_required); return tab; } else { delete tab; return nullptr; } } EditorTab *EditorDock::open_file_if_not_open(const QString &filename, bool save_as_required) { auto tab = find_tab_by_filename(filename); if (tab == nullptr) { return open_file(filename, save_as_required); } else { setCurrentWidget(tab); return tab; } } EditorTab *EditorDock::create_empty_tab() { auto tab = new EditorTab(line_numbers_visible, this); while (true) { auto filename = QString("Unknown %1").arg(unknown_editor_counter++); if (!find_tab_id_by_filename(filename).has_value()) { tab->get_editor()->setFileName(filename); tab->get_editor()->setSaveAsRequired(true); break; } } addTab(tab, tab->title()); setCurrentWidget(tab); return tab; } std::optional EditorDock::find_tab_id_by_filename(const QString &filename) const { int best_match = 0; int best_match_index = -1; for (int i = 0; i < this->count(); i++) { auto *editor = get_tab(i)->get_editor(); int match = compare_filenames(filename, editor->filename()); if (match == 2) { return i; } if (match > best_match) { best_match = match; best_match_index = i; } } if (best_match_index >= 0) { return best_match_index; } return std::nullopt; } EditorTab *EditorDock::find_tab_by_filename(const QString &filename) const { auto index = find_tab_id_by_filename(filename); if (index.has_value()) { return get_tab(index.value()); } else { return nullptr; } } SrcEditor *EditorDock::get_current_editor() const { if (count() == 0) return nullptr; return get_tab(currentIndex())->get_editor(); } QStringList EditorDock::get_open_file_list() const { QStringList open_src_files; for (int i = 0; i < this->count(); i++) { auto *editor = get_tab(i)->get_editor(); if (editor->filename().isEmpty()) { continue; } open_src_files.append(editor->filename()); } return open_src_files; } bool EditorDock::get_modified_tab_filenames(QStringList &output, bool report_unnamed) const { output.clear(); for (int i = 0; i < this->count(); i++) { auto editor = get_tab(i)->get_editor(); if (editor->filename().isEmpty() && !report_unnamed) { continue; } if (!editor->isModified()) { continue; } output.append(editor->filename()); } return !output.empty(); } void EditorDock::set_show_line_numbers(bool visible) { line_numbers_visible = visible; settings->setValue("editorShowLineNumbers", visible); for (int i = 0; i < this->count(); i++) { get_tab(i)->set_show_line_number(visible); } } void EditorDock::tabCountChanged() { Super::tabCountChanged(); emit editor_available_changed(count() > 0); } void EditorDock::open_file_dialog() { #ifndef __EMSCRIPTEN__ QString file_name = QFileDialog::getOpenFileName( this, tr("Open File"), "", "Source Files (*.asm *.S *.s *.c Makefile)"); if (file_name.isEmpty()) { return; } auto tab_id = find_tab_id_by_filename(file_name); if (tab_id.has_value()) { setCurrentIndex(tab_id.value()); return; } if (!open_file(file_name)) { showAsyncCriticalBox( this, "Simulator Error", tr("Cannot open file '%1' for reading.").arg(file_name)); } #else QHtml5File::load("*", [&](const QByteArray &content, const QString &filename) { auto tab = create_empty_tab(); tab->get_editor()->loadByteArray(content, filename); setTabText(indexOf(tab), tab->get_editor()->title()); }); #endif } void EditorDock::save_tab(int index) { auto editor = get_tab(index)->get_editor(); if (editor->saveAsRequired()) { return save_tab_as(index); } #ifndef __EMSCRIPTEN__ if (!editor->saveFile()) { showAsyncCriticalBox( this, "Simulator Error", tr("Cannot save file '%1'.").arg(editor->filename())); } #else QHtml5File::save(editor->document()->toPlainText().toUtf8(), editor->filename()); editor->setModified(false); #endif } void EditorDock::save_current_tab() { if (count() == 0) return; save_tab(currentIndex()); } void EditorDock::save_tab_as(int index) { #ifndef __EMSCRIPTEN__ QFileDialog fileDialog(this, tr("Save as...")); fileDialog.setAcceptMode(QFileDialog::AcceptSave); fileDialog.setDefaultSuffix("s"); if (fileDialog.exec() != QDialog::Accepted) { return; } const QString fn = fileDialog.selectedFiles().first(); auto tab = get_tab(index); if (!tab->get_editor()->saveFile(fn)) { showAsyncCriticalBox(this, "Simulator Error", tr("Cannot save file '%1'.").arg(fn)); return; } setTabText(index, tab->get_editor()->title()); emit currentChanged(index); #else QString filename = get_tab(index)->get_editor()->filename(); if (filename.isEmpty()) filename = "unknown.s"; auto *dialog = new QInputDialog(this); dialog->setWindowTitle("Select file name"); dialog->setLabelText("File name:"); dialog->setTextValue(filename); dialog->setMinimumSize(QSize(200, 100)); dialog->setAttribute(Qt::WA_DeleteOnClose); connect( dialog, &QInputDialog::textValueSelected, this, [this, index](const QString &filename) { save_tab_to(index, filename); }, Qt::QueuedConnection); dialog->open(); #endif } void EditorDock::save_current_tab_as() { if (count() == 0) return; save_tab_as(currentIndex()); } void EditorDock::save_tab_to(int index, const QString &filename) { if (filename.isEmpty()) { WARN("Cannot save file '%s'.", filename.toStdString().c_str()); return; } auto editor = get_tab(index)->get_editor(); if (filename.isEmpty() || (editor == nullptr)) { return; } editor->setFileName(filename); if (!editor->filename().isEmpty()) { save_current_tab(); } } void EditorDock::save_current_tab_to(const QString &filename) { if (count() == 0) return; save_tab_to(currentIndex(), filename); } void EditorDock::close_tab(int index) { auto editor = get_tab(index)->get_editor(); if (!editor->isModified()) { close_tab_unchecked(index); } else { confirm_close_tab_dialog(index); } } void EditorDock::close_current_tab() { if (count() == 0) return; close_tab(currentIndex()); } void EditorDock::close_tab_by_name(QString &filename, bool ask) { auto *tab = find_tab_by_filename(filename); if (tab == nullptr) { WARN("Cannot find tab for file '%s'. Unable to close it.", filename.toStdString().c_str()); return; } if (!ask) { close_tab(indexOf(tab)); } else { confirm_close_tab_dialog(indexOf(tab)); } } void EditorDock::close_tab_unchecked(int index) { auto *tab = get_tab(index); removeTab(index); delete tab; } void EditorDock::confirm_close_tab_dialog(int index) { auto *msgbox = new QMessageBox(this); msgbox->setWindowTitle("Close unsaved source"); msgbox->setText("Close unsaved source."); msgbox->setInformativeText("Do you want to save your changes?"); msgbox->setStandardButtons(QMessageBox::Save | QMessageBox::Discard | QMessageBox::Cancel); msgbox->setDefaultButton(QMessageBox::Save); msgbox->setMinimumSize(QSize(200, 150)); msgbox->setAttribute(Qt::WA_DeleteOnClose); connect( msgbox, &QDialog::finished, this, [this, index](int result) { if (result == QMessageBox::Save) { save_tab(index); close_tab_unchecked(index); } else if (result == QMessageBox::Discard) { close_tab_unchecked(index); } }, Qt::QueuedConnection); msgbox->open(); } bool EditorDock::set_cursor_to(const QString &filename, int line, int column) { auto tab = (filename == "Unknown") ? get_tab(currentIndex()) : find_tab_by_filename(filename); if (tab == nullptr) { WARN( "Cannot find tab for file '%s'. Unable to set cursor.", filename.toStdString().c_str()); return false; } setCurrentWidget(tab); tab->get_editor()->setCursorTo(line, column); return true; } ================================================ FILE: src/gui/windows/editor/editordock.h ================================================ #ifndef EDITORDOCK_H #define EDITORDOCK_H #include "common/memory_ownership.h" #include "editortab.h" #include "widgets/hidingtabwidget.h" #include #include class EditorDock : public HidingTabWidget { Q_OBJECT using Super = HidingTabWidget; public: /** * @param parent_tabs used to update parent tab based on current state */ explicit EditorDock( QSharedPointer settings, QTabWidget *parent_tabs, QWidget *parent = nullptr); BORROWED [[nodiscard]] EditorTab *get_tab(int index) const; BORROWED EditorTab *create_empty_tab(); BORROWED EditorTab *open_file(const QString &filename, bool save_as_required = false); BORROWED EditorTab * open_file_if_not_open(const QString &filename, bool save_as_required = false); BORROWED [[nodiscard]] std::optional find_tab_id_by_filename(const QString &filename) const; BORROWED [[nodiscard]] EditorTab *find_tab_by_filename(const QString &filename) const; BORROWED [[nodiscard]] SrcEditor *get_current_editor() const; [[nodiscard]] QStringList get_open_file_list() const; bool get_modified_tab_filenames(QStringList &output, bool report_unnamed = false) const; bool set_cursor_to(const QString &filename, int line, int column); protected: void tabCountChanged() override; signals: void editor_available_changed(bool available); public slots: void set_show_line_numbers(bool visible); void open_file_dialog(); void save_tab(int index); void save_current_tab(); void save_tab_as(int index); void save_current_tab_as(); void save_tab_to(int index, const QString &filename); void save_current_tab_to(const QString &filename); void close_tab(int index); void close_current_tab(); void close_tab_by_name(QString &filename, bool ask = false); private: void close_tab_unchecked(int index); void confirm_close_tab_dialog(int index); private: QSharedPointer settings; bool line_numbers_visible = true; size_t unknown_editor_counter = 1; }; #endif // EDITORDOCK_H ================================================ FILE: src/gui/windows/editor/editortab.cpp ================================================ #include "editortab.h" #include "srceditor.h" #include #include EditorTab::EditorTab(bool show_line_numbers, QWidget *parent) : Super(parent) , layout(new QVBoxLayout(this)) , editor(new SrcEditor(this)) , status_bar_layout(new QHBoxLayout()) , status_bar_path(new QLabel("Unknown", this)) , status_bar_location(new QLabel("0:0", this)) { layout->setSpacing(0); layout->setContentsMargins(0, 0, 0, 0); layout->addWidget(reinterpret_cast(editor)); status_bar_layout->setSpacing(0); status_bar_layout->setContentsMargins(5, 0, 5, 0); status_bar_layout->addWidget(status_bar_path); status_bar_layout->addWidget(status_bar_location); layout->addLayout(status_bar_layout); status_bar_path->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Preferred); status_bar_location->setSizePolicy(QSizePolicy::Fixed, QSizePolicy::Preferred); connect(editor, &SrcEditor::cursorPositionChanged, this, [this]() { auto cursor = editor->textCursor(); status_bar_location->setText( QString("%1:%2").arg(cursor.blockNumber() + 1).arg(cursor.columnNumber() + 1)); }); connect(editor, &SrcEditor::file_name_change, this, [this]() { elide_file_name(); }); set_show_line_number(show_line_numbers); } QString EditorTab::title() { return editor->title(); } void EditorTab::set_show_line_number(bool visible) { editor->setShowLineNumbers(visible); } void EditorTab::resizeEvent(QResizeEvent *event) { QWidget::resizeEvent(event); elide_file_name(); } void EditorTab::elide_file_name() { auto filename = editor->filename().isEmpty() ? "Unknown" : editor->filename(); QFontMetrics metrics(status_bar_path->font()); int width = status_bar_layout->geometry().width() - status_bar_location->geometry().width() - 10; QString clippedText = metrics.elidedText(filename, Qt::ElideMiddle, width); status_bar_path->setText(clippedText); } ================================================ FILE: src/gui/windows/editor/editortab.h ================================================ #ifndef EDITORTAB_H #define EDITORTAB_H #include "common/memory_ownership.h" #include "srceditor.h" #include #include #include class EditorTab : public QWidget { Q_OBJECT using Super = QWidget; public: explicit EditorTab(bool show_line_numbers, QWidget *parent = nullptr); [[nodiscard]] SrcEditor *get_editor() const { return editor; } QString title(); public slots: void set_show_line_number(bool visible); protected: void resizeEvent(QResizeEvent *event) override; void elide_file_name(); private: QT_OWNED QVBoxLayout *layout; QT_OWNED SrcEditor *editor; QT_OWNED QHBoxLayout *status_bar_layout; QT_OWNED QLabel *status_bar_path; QT_OWNED QLabel *status_bar_location; }; #endif // EDITORTAB_H ================================================ FILE: src/gui/windows/editor/highlighterasm.cpp ================================================ /* Based on Qt example released under BSD license */ #include "highlighterasm.h" #include "QStringList" #include "machine/csr/controlstate.h" #include "machine/instruction.h" HighlighterAsm::HighlighterAsm(QTextDocument *parent) : QSyntaxHighlighter(parent) { HighlightingRule rule; QTextCharFormat keywordFormat, registerFormat, singleLineCommentFormat, multiLineCommentFormat, quotationFormat; keywordFormat.setForeground(Qt::darkBlue); keywordFormat.setFontWeight(QFont::Bold); registerFormat.setFontWeight(QFont::Bold); registerFormat.setForeground(Qt::darkMagenta); singleLineCommentFormat.setForeground(Qt::darkGray); multiLineCommentFormat.setForeground(Qt::darkGray); quotationFormat.setForeground(Qt::darkGreen); { const QStringList keywordPatterns = { QStringLiteral("\\.org"), QStringLiteral("\\.word"), QStringLiteral("\\.text"), QStringLiteral("\\.data"), QStringLiteral("\\.bss"), QStringLiteral("\\.option"), QStringLiteral("\\.globl"), QStringLiteral("\\.set"), QStringLiteral("\\.equ"), QStringLiteral("\\.end"), QStringLiteral("\\.ent"), QStringLiteral("\\.ascii"), QStringLiteral("\\.asciz"), QStringLiteral("\\.byte"), QStringLiteral("\\.skip"), QStringLiteral("\\.space") }; rule.pattern = QRegularExpression("(" + keywordPatterns.join('|') + ")\\b"); rule.format = keywordFormat; highlightingRules.append(rule); } { QStringList inst_list; machine::Instruction::append_recognized_instructions(inst_list); inst_list.append("nop"); rule.pattern = QRegularExpression( QString("\\b(" + inst_list.join('|') + ")\\b"), QRegularExpression::CaseInsensitiveOption); rule.format = keywordFormat; highlightingRules.append(rule); } { QStringList reg_list; machine::Instruction::append_recognized_registers(reg_list); rule.pattern = QRegularExpression("\\b(" + reg_list.join('|') + "|x[0-9]+)\\b"); rule.format = registerFormat; highlightingRules.append(rule); } { QTextCharFormat namedValueFormat; namedValueFormat.setFontWeight(QFont::Bold); namedValueFormat.setForeground(Qt::darkMagenta); QString pattern = "\\b("; for (const auto ® : machine::CSR::REGISTERS) { pattern.append(reg.name).append('|'); } pattern = pattern.left(pattern.size() - 1); pattern.append(")\\b"); rule.pattern = QRegularExpression(pattern); rule.format = namedValueFormat; highlightingRules.append(rule); } rule.pattern = QRegularExpression(QStringLiteral("(;[^\n]*)|(#[^\n]*)|(//[^\n]*)")); rule.format = singleLineCommentFormat; highlightingRules.append(rule); rule.pattern = QRegularExpression(QStringLiteral("\".*\"")); rule.format = quotationFormat; highlightingRules.append(rule); for (auto &rule : highlightingRules) { rule.pattern.optimize(); } } void HighlighterAsm::highlightBlock(const QString &text) { for (const HighlightingRule &rule : qAsConst(highlightingRules)) { auto matchIterator = rule.pattern.globalMatch(text); while (matchIterator.hasNext()) { QRegularExpressionMatch match = matchIterator.next(); setFormat(match.capturedStart(), match.capturedLength(), rule.format); } } setCurrentBlockState(0); #if 0 int startIndex = 0; if (previousBlockState() != 1) startIndex = text.indexOf(commentStartExpression); while (startIndex >= 0) { QRegularExpressionMatch match = commentEndExpression.match(text, startIndex); int endIndex = match.capturedStart(); int commentLength = 0; if (endIndex == -1) { setCurrentBlockState(1); commentLength = text.length() - startIndex; } else { commentLength = endIndex - startIndex + match.capturedLength(); } setFormat(startIndex, commentLength, multiLineCommentFormat); startIndex = text.indexOf(commentStartExpression, startIndex + commentLength); } #endif } ================================================ FILE: src/gui/windows/editor/highlighterasm.h ================================================ /* Based on Qt example released under BSD license */ #ifndef HIGHLIGHTERASM_H #define HIGHLIGHTERASM_H #include #include #include QT_BEGIN_NAMESPACE class QTextDocument; QT_END_NAMESPACE class HighlighterAsm : public QSyntaxHighlighter { Q_OBJECT public: explicit HighlighterAsm(QTextDocument *parent = nullptr); protected: void highlightBlock(const QString &text) override; private: struct HighlightingRule { QRegularExpression pattern; QTextCharFormat format; }; QVector highlightingRules; }; #endif // HIGHLIGHTERASM_H ================================================ FILE: src/gui/windows/editor/highlighterc.cpp ================================================ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #include "highlighterc.h" //! [0] HighlighterC::HighlighterC(QTextDocument *parent) : QSyntaxHighlighter(parent) { HighlightingRule rule; keywordFormat.setForeground(Qt::darkBlue); keywordFormat.setFontWeight(QFont::Bold); const QString keywordPatterns[] = { QStringLiteral("\\bchar\\b"), QStringLiteral("\\bclass\\b"), QStringLiteral("\\bconst\\b"), QStringLiteral("\\bdouble\\b"), QStringLiteral("\\benum\\b"), QStringLiteral("\\bexplicit\\b"), QStringLiteral("\\bfriend\\b"), QStringLiteral("\\binline\\b"), QStringLiteral("\\bint\\b"), QStringLiteral("\\blong\\b"), QStringLiteral("\\bnamespace\\b"), QStringLiteral("\\boperator\\b"), QStringLiteral("\\bprivate\\b"), QStringLiteral("\\bprotected\\b"), QStringLiteral("\\bpublic\\b"), QStringLiteral("\\bshort\\b"), QStringLiteral("\\bsignals\\b"), QStringLiteral("\\bsigned\\b"), QStringLiteral("\\bslots\\b"), QStringLiteral("\\bstatic\\b"), QStringLiteral("\\bstruct\\b"), QStringLiteral("\\btemplate\\b"), QStringLiteral("\\btypedef\\b"), QStringLiteral("\\btypename\\b"), QStringLiteral("\\bunion\\b"), QStringLiteral("\\bunsigned\\b"), QStringLiteral("\\bvirtual\\b"), QStringLiteral("\\bvoid\\b"), QStringLiteral("\\bvolatile\\b"), QStringLiteral("\\bbool\\b") }; for (const QString &pattern : keywordPatterns) { rule.pattern = QRegularExpression(pattern); rule.format = keywordFormat; highlightingRules.append(rule); //! [0] //! [1] } //! [1] //! [2] classFormat.setFontWeight(QFont::Bold); classFormat.setForeground(Qt::darkMagenta); rule.pattern = QRegularExpression(QStringLiteral("\\bQ[A-Za-z]+\\b")); rule.format = classFormat; highlightingRules.append(rule); //! [2] //! [3] singleLineCommentFormat.setForeground(Qt::red); rule.pattern = QRegularExpression(QStringLiteral("//[^\n]*")); rule.format = singleLineCommentFormat; highlightingRules.append(rule); multiLineCommentFormat.setForeground(Qt::red); //! [3] //! [4] quotationFormat.setForeground(Qt::darkGreen); rule.pattern = QRegularExpression(QStringLiteral("\".*\"")); rule.format = quotationFormat; highlightingRules.append(rule); //! [4] //! [5] functionFormat.setFontItalic(true); functionFormat.setForeground(Qt::blue); rule.pattern = QRegularExpression(QStringLiteral("\\b[A-Za-z0-9_]+(?=\\()")); rule.format = functionFormat; highlightingRules.append(rule); //! [5] //! [6] commentStartExpression = QRegularExpression(QStringLiteral("/\\*")); commentEndExpression = QRegularExpression(QStringLiteral("\\*/")); } //! [6] //! [7] void HighlighterC::highlightBlock(const QString &text) { #if QT_VERSION < QT_VERSION_CHECK(5, 7, 0) foreach (const HighlightingRule &rule, highlightingRules) #else for (const HighlightingRule &rule : qAsConst(highlightingRules)) #endif { QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text); while (matchIterator.hasNext()) { QRegularExpressionMatch match = matchIterator.next(); setFormat(match.capturedStart(), match.capturedLength(), rule.format); } } //! [7] //! [8] setCurrentBlockState(0); //! [8] //! [9] int startIndex = 0; if (previousBlockState() != 1) { startIndex = text.indexOf(commentStartExpression); } //! [9] //! [10] while (startIndex >= 0) { //! [10] //! [11] QRegularExpressionMatch match = commentEndExpression.match(text, startIndex); int endIndex = match.capturedStart(); int commentLength = 0; if (endIndex == -1) { setCurrentBlockState(1); commentLength = text.length() - startIndex; } else { commentLength = endIndex - startIndex + match.capturedLength(); } setFormat(startIndex, commentLength, multiLineCommentFormat); startIndex = text.indexOf(commentStartExpression, startIndex + commentLength); } } //! [11] ================================================ FILE: src/gui/windows/editor/highlighterc.h ================================================ /**************************************************************************** ** ** Copyright (C) 2016 The Qt Company Ltd. ** Contact: https://www.qt.io/licensing/ ** ** This file is part of the examples of the Qt Toolkit. ** ** $QT_BEGIN_LICENSE:BSD$ ** Commercial License Usage ** Licensees holding valid commercial Qt licenses may use this file in ** accordance with the commercial license agreement provided with the ** Software or, alternatively, in accordance with the terms contained in ** a written agreement between you and The Qt Company. For licensing terms ** and conditions see https://www.qt.io/terms-conditions. For further ** information use the contact form at https://www.qt.io/contact-us. ** ** BSD License Usage ** Alternatively, you may use this file under the terms of the BSD license ** as follows: ** ** "Redistribution and use in source and binary forms, with or without ** modification, are permitted provided that the following conditions are ** met: ** * Redistributions of source code must retain the above copyright ** notice, this list of conditions and the following disclaimer. ** * Redistributions in binary form must reproduce the above copyright ** notice, this list of conditions and the following disclaimer in ** the documentation and/or other materials provided with the ** distribution. ** * Neither the name of The Qt Company Ltd nor the names of its ** contributors may be used to endorse or promote products derived ** from this software without specific prior written permission. ** ** ** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT ** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR ** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT ** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, ** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT ** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, ** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY ** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT ** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE ** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE." ** ** $QT_END_LICENSE$ ** ****************************************************************************/ #ifndef HIGHLIGHTERC_H #define HIGHLIGHTERC_H #include #include #include QT_BEGIN_NAMESPACE class QTextDocument; QT_END_NAMESPACE //! [0] class HighlighterC : public QSyntaxHighlighter { Q_OBJECT public: explicit HighlighterC(QTextDocument *parent = nullptr); protected: void highlightBlock(const QString &text) override; private: struct HighlightingRule { QRegularExpression pattern; QTextCharFormat format; }; QVector highlightingRules; QRegularExpression commentStartExpression; QRegularExpression commentEndExpression; QTextCharFormat keywordFormat; QTextCharFormat classFormat; QTextCharFormat singleLineCommentFormat; QTextCharFormat multiLineCommentFormat; QTextCharFormat quotationFormat; QTextCharFormat functionFormat; }; //! [0] #endif // HIGHLIGHTERC_H ================================================ FILE: src/gui/windows/editor/linenumberarea.cpp ================================================ #include "linenumberarea.h" #include "srceditor.h" #include #include #include constexpr int RIGHT_MARGIN = 5; constexpr int RIGHT_PADDING = 5; constexpr int LEFT_PADDING = 5; LineNumberArea::LineNumberArea(SrcEditor *editor_) : QWidget(editor_), editor(editor_) {} QSize LineNumberArea::sizeHint() const { if (!line_numbers_visible) { return { 0, 0 }; } int digits = std::log10(std::max(1, editor->blockCount())) + 2; int space = editor->fontMetrics().horizontalAdvance(QLatin1Char('9')) * digits + LEFT_PADDING + RIGHT_PADDING + RIGHT_MARGIN; return { space, 0 }; } void LineNumberArea::paintEvent(QPaintEvent *event) { QPainter painter(this); painter.fillRect(event->rect(), palette().base()); painter.drawLine( event->rect().right() - RIGHT_MARGIN, 0, event->rect().right() - RIGHT_MARGIN, event->rect().bottom()); QTextBlock block = editor->firstVisibleBlock(); int blockNumber = block.blockNumber(); int top = qRound(editor->blockBoundingGeometry(block).translated(editor->contentOffset()).top()); int bottom = top + qRound(editor->blockBoundingRect(block).height()); while (block.isValid() && top <= event->rect().bottom()) { if (block.isVisible() && bottom >= event->rect().top()) { QString number = QString::number(blockNumber + 1); painter.setPen(palette().windowText().color()); painter.drawText( 0, top, this->sizeHint().width() - RIGHT_PADDING - RIGHT_MARGIN, editor->fontMetrics().height(), Qt::AlignRight, number); } block = block.next(); top = bottom; bottom = top + qRound(editor->blockBoundingRect(block).height()); ++blockNumber; } } void LineNumberArea::set(bool visible) { QWidget::setVisible(visible); line_numbers_visible = visible; } ================================================ FILE: src/gui/windows/editor/linenumberarea.h ================================================ #ifndef LINENUMBERAREA_H #define LINENUMBERAREA_H #include "common/memory_ownership.h" #include class SrcEditor; class LineNumberArea : public QWidget { public: explicit LineNumberArea(BORROWED SrcEditor *editor_); void set(bool visible); [[nodiscard]] QSize sizeHint() const override; protected: void paintEvent(QPaintEvent *event) override; private: BORROWED SrcEditor *editor; bool line_numbers_visible = false; }; #endif // LINENUMBERAREA_H ================================================ FILE: src/gui/windows/editor/srceditor.cpp ================================================ #include "srceditor.h" #include "common/logging.h" #include "editordock.h" #include "editortab.h" #include "linenumberarea.h" #include "windows/editor/highlighterasm.h" #include "windows/editor/highlighterc.h" #include #include #include #include #include #include #include #include #include #include LOG_CATEGORY("gui.src_editor"); SrcEditor::SrcEditor(QWidget *parent) : Super(parent), line_number_area(new LineNumberArea(this)) { QFont font1; saveAsRequiredFl = true; font1.setFamily("Courier"); font1.setFixedPitch(true); font1.setPointSize(10); setFont(font1); tname = "Unknown"; highlighter.reset(new HighlighterAsm(document())); QPalette p = palette(); p.setColor(QPalette::Base, Qt::white); p.setColor(QPalette::Text, Qt::black); p.setColor(QPalette::WindowText, Qt::darkGray); setPalette(p); // Set tab width to 4 spaces setTabStopDistance(fontMetrics().horizontalAdvance(' ') * TAB_WIDTH); connect(this, &SrcEditor::blockCountChanged, this, &SrcEditor::updateMargins); connect(this, &SrcEditor::updateRequest, this, &SrcEditor::updateLineNumberArea); // Clear error highlight on typing connect(this, &SrcEditor::textChanged, [this]() { setExtraSelections({}); }); updateMargins(0); } QString SrcEditor::filename() const { return fname; } QString SrcEditor::title() { return tname; } void SrcEditor::setFileName(const QString &filename) { QFileInfo fi(filename); saveAsRequiredFl = filename.isEmpty() || filename.startsWith(":/"); fname = filename; tname = fi.fileName(); if ((fi.suffix() == "c") || (fi.suffix() == "C") || (fi.suffix() == "cpp") || ((fi.suffix() == "c++"))) { highlighter.reset(new HighlighterC(document())); } else { highlighter.reset(new HighlighterAsm(document())); } emit file_name_change(); } bool SrcEditor::loadFile(const QString &filename) { QFile file(filename); if (file.open(QFile::ReadOnly | QFile::Text)) { setPlainText(file.readAll()); setFileName(filename); return true; } else { return false; } } bool SrcEditor::loadByteArray(const QByteArray &content, const QString &filename) { setPlainText(QString::fromUtf8(content.data(), content.size())); if (!filename.isEmpty()) { setFileName(filename); } return true; } bool SrcEditor::saveFile(QString filename) { if (filename.isEmpty()) { filename = this->filename(); } if (filename.isEmpty()) { return false; } QTextDocumentWriter writer(filename); writer.setFormat("plaintext"); bool success = writer.write(document()); setFileName(filename); if (success) { document()->setModified(false); } return success; } void SrcEditor::setCursorToLine(int ln) { QTextCursor cursor(document()->findBlockByNumber(ln - 1)); setTextCursor(cursor); } void SrcEditor::setCursorTo(int ln, int col) { QTextCursor cursor(document()->findBlockByNumber(ln - 1)); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::MoveAnchor, col - 1); setTextCursor(cursor); setFocus(); } bool SrcEditor::isModified() const { return document()->isModified(); } void SrcEditor::setModified(bool val) { document()->setModified(val); } void SrcEditor::setSaveAsRequired(bool val) { saveAsRequiredFl = val; } bool SrcEditor::saveAsRequired() const { return saveAsRequiredFl; } void SrcEditor::keyPressEvent(QKeyEvent *event) { QTextCursor cursor = textCursor(); if (cursor.hasSelection()) { switch (event->key()) { case Qt::Key_Tab: { indent_selection(cursor); return; } case Qt::Key_Backtab: { unindent_selection(cursor); return; } case Qt::Key_Slash: if (event->modifiers() & Qt::ControlModifier) { toggle_selection_comment(cursor, is_selection_comment()); return; } break; default: break; } } switch (event->key()) { case Qt::Key_Return: { // Keep indentation QString txt = cursor.block().text(); QString indent; for (auto ch : txt) { if (ch.isSpace()) { indent.append(ch); } else { break; } } cursor.insertText("\n"); cursor.insertText(indent); setTextCursor(cursor); return; } case Qt::Key_Slash: { if (event->modifiers() & Qt::ControlModifier) { // Toggle comment if (cursor.block().text().startsWith("//")) { cursor.movePosition(QTextCursor::StartOfLine); cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 2); cursor.removeSelectedText(); } else { cursor.movePosition(QTextCursor::StartOfLine); cursor.insertText("//"); } return; } break; } } QPlainTextEdit::keyPressEvent(event); } void SrcEditor::indent_selection(QTextCursor &cursor) { auto end = cursor.selectionEnd(); cursor.beginEditBlock(); cursor.setPosition(cursor.selectionStart()); cursor.movePosition(QTextCursor::StartOfLine); while (cursor.position() < end) { cursor.insertText("\t"); if (!cursor.movePosition(QTextCursor::Down)) break; } cursor.endEditBlock(); } void SrcEditor::unindent_selection(QTextCursor &cursor) { cursor.beginEditBlock(); auto end_line = document()->findBlock(cursor.selectionEnd()).blockNumber(); cursor.setPosition(cursor.selectionStart()); cursor.movePosition(QTextCursor::StartOfLine); while (cursor.blockNumber() <= end_line) { auto txt = cursor.block().text(); if (txt.isEmpty()) { // Empty line, skip } else if (txt.startsWith("\t")) { cursor.deleteChar(); } else if (txt.startsWith(" ")) { // Delete at most TAB_WIDTH spaces unsigned to_delete = std::min(txt.size(), TAB_WIDTH); while (to_delete > 0 && cursor.block().text().startsWith(" ")) { cursor.deleteChar(); to_delete--; } } if (!cursor.movePosition(QTextCursor::Down)) break; } cursor.endEditBlock(); } bool SrcEditor::is_selection_comment() { QTextCursor cursor = textCursor(); bool all_commented = true; auto end_line = document()->findBlock(cursor.selectionEnd()).blockNumber(); while (cursor.blockNumber() <= end_line) { auto txt = cursor.block().text(); if (!txt.startsWith("//")) { all_commented = false; break; } if (!cursor.movePosition(QTextCursor::Down)) break; } return all_commented; } void SrcEditor::toggle_selection_comment(QTextCursor &cursor, bool is_comment) { auto end_line = document()->findBlock(cursor.selectionEnd()).blockNumber(); cursor.beginEditBlock(); cursor.setPosition(cursor.selectionStart()); cursor.movePosition(QTextCursor::StartOfLine); while (cursor.blockNumber() <= end_line) { if (is_comment) { cursor.deleteChar(); cursor.deleteChar(); } else { cursor.insertText("//"); } if (!cursor.movePosition(QTextCursor::Down)) break; } cursor.endEditBlock(); } void SrcEditor::updateMargins(int /* newBlockCount */) { setViewportMargins(line_number_area->sizeHint().width(), 0, 0, 0); } void SrcEditor::updateLineNumberArea(const QRect &rect, int dy) { if (dy) { line_number_area->scroll(0, dy); } else { line_number_area->update(0, rect.y(), line_number_area->width(), rect.height()); } if (rect.contains(viewport()->rect())) updateMargins(0); } void SrcEditor::resizeEvent(QResizeEvent *event) { QPlainTextEdit::resizeEvent(event); QRect cr = contentsRect(); line_number_area->setGeometry( QRect(cr.left(), cr.top(), line_number_area->sizeHint().width(), cr.height())); } void SrcEditor::setShowLineNumbers(bool show) { line_number_area->set(show); updateMargins(0); } void SrcEditor::insertFromMimeData(const QMimeData *source) { if (source->hasText()) { insertPlainText(source->text()); } } bool SrcEditor::canInsertFromMimeData(const QMimeData *source) const { return source->hasText(); } ================================================ FILE: src/gui/windows/editor/srceditor.h ================================================ #ifndef SRCEDITOR_H #define SRCEDITOR_H #include "common/memory_ownership.h" #include "linenumberarea.h" #include "machine/machine.h" #include #include #include #include #include #include class SrcEditor : public QPlainTextEdit { Q_OBJECT using Super = QPlainTextEdit; public: explicit SrcEditor(QWidget *parent); [[nodiscard]] QString filename() const; QString title(); bool loadFile(const QString &filename); bool saveFile(QString filename = ""); bool loadByteArray(const QByteArray &content, const QString &filename = ""); void setCursorToLine(int ln); void setCursorTo(int ln, int col); void setFileName(const QString &filename); [[nodiscard]] bool isModified() const; void setModified(bool val); void setSaveAsRequired(bool val); [[nodiscard]] bool saveAsRequired() const; protected: void keyPressEvent(QKeyEvent *event) override; void resizeEvent(QResizeEvent *event) override; void insertFromMimeData(const QMimeData *source) override; bool canInsertFromMimeData(const QMimeData *source) const override; signals: void file_name_change(); public slots: void setShowLineNumbers(bool visible); private slots: void updateMargins(int newBlockCount); void updateLineNumberArea(const QRect &rect, int dy); private: ::Box highlighter {}; LineNumberArea *line_number_area; bool line_numbers_visible = true; QString fname; QString tname; bool saveAsRequiredFl {}; /** Width of a tab character in spaces. */ static constexpr unsigned TAB_WIDTH = 4; /** Indents selected lines by one tab. */ void indent_selection(QTextCursor &cursor); /** Unindents selected lines by one tab or 4 spaces from the beginning of each line. * If only some lines contain less prefix whitespace, remove as much as possible to mimic * VS Code behavior. */ void unindent_selection(QTextCursor &cursor); /** Returns true if all lines in the selection are commented out. */ bool is_selection_comment(); /** Comments out all lines in the selection. */ void toggle_selection_comment(QTextCursor &cursor, bool is_comment); friend class LineNumberArea; }; #endif // SRCEDITOR_H ================================================ FILE: src/gui/windows/lcd/lcddisplaydock.cpp ================================================ #include "lcddisplaydock.h" #include "lcddisplayview.h" #include #include LcdDisplayDock::LcdDisplayDock(QWidget *parent, QSettings *settings) : Super(parent) { (void)settings; lcd_display_widget.reset(new LcdDisplayView(this)); auto *fill_widget = new QWidget(this); layout = new QBoxLayout(QBoxLayout::LeftToRight, fill_widget); // add spacer, then your widget, then spacer layout->addItem(new QSpacerItem(0, 0)); layout->addWidget(lcd_display_widget.data()); layout->addItem(new QSpacerItem(0, 0)); fill_widget->setLayout(layout); setWidget(fill_widget); setObjectName("LCD Display"); setWindowTitle("LCD Display"); } void LcdDisplayDock::setup(machine::LcdDisplay *lcd_display) { lcd_display_widget->setup(lcd_display); update_layout(width(), height()); } void LcdDisplayDock::update_layout(int w, int h) { // Keeping the aspect ratio based on // https://stackoverflow.com/questions/30005540/keeping-the-aspect-ratio-of-a-sub-classed-qwidget-during-resize float thisAspectRatio = (float)w / h; int widgetStretch, outerStretch; float arWidth = lcd_display_widget->fb_width(); // aspect ratio width float arHeight = lcd_display_widget->fb_height(); // aspect ratio height if ((arWidth == 0) || (arHeight == 0)) { outerStretch = 0; widgetStretch = 1; } else if (thisAspectRatio > (arWidth / arHeight)) { // too wide layout->setDirection(QBoxLayout::LeftToRight); widgetStretch = height() * (arWidth / arHeight); // i.e., my width outerStretch = (width() - widgetStretch) / 2 + 0.5; } else { // too tall layout->setDirection(QBoxLayout::TopToBottom); widgetStretch = width() * (arHeight / arWidth); // i.e., my height outerStretch = (height() - widgetStretch) / 2 + 0.5; } layout->setStretch(0, outerStretch); layout->setStretch(1, widgetStretch); layout->setStretch(2, outerStretch); } void LcdDisplayDock::resizeEvent(QResizeEvent *event) { // Keeping the aspect ratio based on // https://stackoverflow.com/questions/30005540/keeping-the-aspect-ratio-of-a-sub-classed-qwidget-during-resize update_layout(event->size().width(), event->size().height()); Super::resizeEvent(event); } ================================================ FILE: src/gui/windows/lcd/lcddisplaydock.h ================================================ #ifndef LCDDISPLAYDOCK_H #define LCDDISPLAYDOCK_H #include "lcddisplayview.h" #include "machine/machine.h" #include #include class LcdDisplayDock : public QDockWidget { Q_OBJECT using Super = QDockWidget; public: LcdDisplayDock(QWidget *parent, QSettings *settings); void resizeEvent(QResizeEvent *event) override; void setup(machine::LcdDisplay *lcd_display); // public slots: private: void update_layout(int w, int h); QT_OWNED QBoxLayout *layout; Box lcd_display_widget; }; #endif // LCDDISPLAYDOCK_H ================================================ FILE: src/gui/windows/lcd/lcddisplayview.cpp ================================================ #include "lcddisplayview.h" #include #include #include LcdDisplayView::LcdDisplayView(QWidget *parent) : Super(parent) { setMinimumSize(100, 100); scale_x = 1.0; scale_y = 1.0; } void LcdDisplayView::setup(machine::LcdDisplay *lcd_display) { if (lcd_display == nullptr) { return; } connect(lcd_display, &machine::LcdDisplay::pixel_update, this, &LcdDisplayView::pixel_update); fb_pixels.reset( new QImage(lcd_display->get_width(), lcd_display->get_height(), QImage::Format_RGB32)); fb_pixels->fill(qRgb(0, 0, 0)); update_scale(); update(); } void LcdDisplayView::pixel_update(size_t x, size_t y, uint r, uint g, uint b) { int x1, y1, x2, y2; if (fb_pixels != nullptr) { fb_pixels->setPixel(x, y, qRgb(r, g, b)); x1 = x * scale_x - 2; if (x1 < 0) { x1 = 0; } x2 = x * scale_x + 2; if (x2 > width()) { x2 = width(); } y1 = y * scale_y - 2; if (y1 < 0) { y1 = 0; } y2 = y * scale_y + 2; if (y2 > height()) { y2 = height(); } update(x1, y1, x2 - x1, y2 - y1); } } void LcdDisplayView::update_scale() { if (fb_pixels != nullptr) { if ((fb_pixels->width() != 0) && (fb_pixels->height() != 0)) { scale_x = (float)width() / fb_pixels->width(); scale_y = (float)height() / fb_pixels->height(); return; } } scale_x = 1.0; scale_y = 1.0; } void LcdDisplayView::paintEvent(QPaintEvent *event) { if (fb_pixels == nullptr) { return Super::paintEvent(event); } if (fb_pixels->width() == 0) { return Super::paintEvent(event); } QPainter painter(this); painter.drawImage(rect(), *fb_pixels); #if 0 painter.setPen(QPen(QColor(255, 255, 0))); painter.drawLine(event->rect().topLeft(),event->rect().topRight()); painter.drawLine(event->rect().topLeft(),event->rect().bottomLeft()); painter.drawLine(event->rect().topLeft(),event->rect().bottomRight()); #endif } void LcdDisplayView::resizeEvent(QResizeEvent *event) { Super::resizeEvent(event); update_scale(); } uint LcdDisplayView::fb_width() { if (fb_pixels == nullptr) { return 0; } return fb_pixels->width(); } uint LcdDisplayView::fb_height() { if (fb_pixels == nullptr) { return 0; } return fb_pixels->height(); } ================================================ FILE: src/gui/windows/lcd/lcddisplayview.h ================================================ #ifndef LCDDISPLAYVIEW_H #define LCDDISPLAYVIEW_H #include "common/memory_ownership.h" #include "machine/memory/backend/lcddisplay.h" #include #include class LcdDisplayView : public QWidget { Q_OBJECT using Super = QWidget; public: explicit LcdDisplayView(QWidget *parent = nullptr); void setup(machine::LcdDisplay *lcd_display); uint fb_width(); uint fb_height(); public slots: void pixel_update(std::size_t x, std::size_t y, uint r, uint g, uint b); protected: void paintEvent(QPaintEvent *event) override; void resizeEvent(QResizeEvent *event) override; private: void update_scale(); float scale_x; float scale_y; Box fb_pixels; }; #endif // LCDDISPLAYVIEW_H ================================================ FILE: src/gui/windows/memory/memorydock.cpp ================================================ #include "memorydock.h" #include "memorymodel.h" #include "memorytableview.h" #include "ui/hexlineedit.h" #include #include #include #include MemoryDock::MemoryDock(QWidget *parent, QSettings *settings) : Super(parent) { setObjectName("Memory"); setWindowTitle("Memory"); auto *content = new QWidget(); auto *cell_size = new QComboBox(); cell_size->addItem("Byte", MemoryModel::CELLSIZE_BYTE); cell_size->addItem("Half-word", MemoryModel::CELLSIZE_HWORD); cell_size->addItem("Word", MemoryModel::CELLSIZE_WORD); cell_size->setCurrentIndex(MemoryModel::CELLSIZE_WORD); auto *cached_access = new QComboBox(); cached_access->addItem("Direct", 0); cached_access->addItem("Cached", 1); cached_access->addItem("As CPU (VMA)", 2); auto *memory_content = new MemoryTableView(nullptr, settings); // memory_content->setSizePolicy(); auto *memory_model = new MemoryModel(this); memory_content->setModel(memory_model); memory_content->verticalHeader()->hide(); // memory_content->setHorizontalHeader(memory_model->); auto *go_edit = new HexLineEdit(nullptr, 8, 16, "0x"); auto *layout_top = new QHBoxLayout; layout_top->addWidget(cell_size); layout_top->addWidget(cached_access); auto *layout = new QVBoxLayout; layout->addLayout(layout_top); layout->addWidget(memory_content); layout->addWidget(go_edit); content->setLayout(layout); setWidget(content); connect(this, &MemoryDock::machine_setup, memory_model, &MemoryModel::setup); connect(this, &MemoryDock::machine_setup, memory_model, &MemoryModel::setup); connect( cell_size, QOverload::of(&QComboBox::currentIndexChanged), memory_content, &MemoryTableView::set_cell_size); connect( cached_access, QOverload::of(&QComboBox::currentIndexChanged), memory_model, &MemoryModel::cached_access); connect( go_edit, &HexLineEdit::value_edit_finished, memory_content, [memory_content](uint32_t value) { memory_content->go_to_address(machine::Address(value)); }); connect( memory_content, &MemoryTableView::address_changed, go_edit, [go_edit](machine::Address addr) { go_edit->set_value(addr.get_raw()); }); connect(this, &MemoryDock::focus_addr, memory_content, &MemoryTableView::focus_address); connect( memory_model, &MemoryModel::setup_done, memory_content, &MemoryTableView::recompute_columns); } void MemoryDock::setup(machine::Machine *machine) { emit machine_setup(machine); } ================================================ FILE: src/gui/windows/memory/memorydock.h ================================================ #ifndef MEMORYDOCK_H #define MEMORYDOCK_H #include "machine/machine.h" #include "machine/memory/address.h" #include #include class MemoryDock : public QDockWidget { Q_OBJECT using Super = QDockWidget; public: MemoryDock(QWidget *parent, QSettings *settings); void setup(machine::Machine *machine); signals: void machine_setup(machine::Machine *machine); void focus_addr(machine::Address); private: machine::Machine *machinePtr; }; #endif // MEMORYDOCK_H ================================================ FILE: src/gui/windows/memory/memorymodel.cpp ================================================ #include "memorymodel.h" #include using ae = machine::AccessEffects; // For enum values, the type is obvious from context. MemoryModel::MemoryModel(QObject *parent) : Super(parent), data_font("Monospace") { cell_size = CELLSIZE_WORD; cells_per_row = 1; index0_offset = machine::Address::null(); data_font.setStyleHint(QFont::TypeWriter); machine = nullptr; memory_change_counter = 0; cache_data_change_counter = 0; mem_access_kind = MEM_ACC_AS_CPU; } const machine::FrontendMemory *MemoryModel::mem_access() const { if (machine == nullptr) { return nullptr; } if (machine->memory_data_bus() != nullptr) { return machine->memory_data_bus(); } // Direct access to memory is not allowed, data bus must be used. At least a // trivial one. If this occurred, there is a misconfigured machine. throw std::logic_error("No memory available on machine. This is a bug, please report it."); } machine::FrontendMemory *MemoryModel::mem_access_rw() const { if (machine == nullptr) { return nullptr; } if (machine->memory_data_bus_rw() != nullptr) { return machine->memory_data_bus_rw(); } // Direct access to memory is not allowed, data bus must be used. At least a // trivial one. If this occurred, there is a misconfigured machine. throw std::logic_error("No memory available on machine. This is u bug, please report it."); } int MemoryModel::rowCount(const QModelIndex & /*parent*/) const { return 750; } int MemoryModel::columnCount(const QModelIndex & /*parent*/) const { return cells_per_row + 1; } QVariant MemoryModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { if (section == 0) { return tr("Address"); } else { uint32_t addr = (section - 1) * cellSizeBytes(); QString ret = "+" + QString::number(addr, 10); return ret; } } } return Super::headerData(section, orientation, role); } QVariant MemoryModel::data(const QModelIndex &index, int role) const { if (role == Qt::DisplayRole || role == Qt::EditRole) { QString s, t; machine::Address address; uint32_t data; const machine::FrontendMemory *mem = nullptr; if (!get_row_address(address, index.row())) { return QString(""); } if (index.column() == 0) { t = QString::number(address.get_raw(), 16); s.fill('0', 8 - t.count()); return { QString("0x") + s + t }; } if (machine == nullptr) { return QString(""); } bool vm_enabled = machine->config().get_vm_enabled(); if (!vm_enabled) { mem = mem_access(); if ((mem_access_kind > MEM_ACC_AS_CPU) && (machine->cache_data() != nullptr)) { mem = machine->cache_data(); } } else { if (mem_access_kind == MEM_ACC_PHYS_ADDR) { mem = machine->get_tlb_data(); } else { mem = mem_access_phys(); } } if (mem == nullptr) { return QString(""); } address += cellSizeBytes() * (index.column() - 1); if (address < index0_offset) { return QString(""); } switch (cell_size) { case CELLSIZE_BYTE: data = mem->read_u8(address, ae::INTERNAL); break; case CELLSIZE_HWORD: data = mem->read_u16(address, ae::INTERNAL); break; default: case CELLSIZE_WORD: data = mem->read_u32(address, ae::INTERNAL); break; } t = QString::number(data, 16); s.fill('0', cellSizeBytes() * 2 - t.count()); t = s + t; #if 0 machine::LocationStatus loc_stat = machine::LOCSTAT_NONE; if (machine->cache_data() != nullptr) { loc_stat = machine->cache_data()->location_status(address); if (loc_stat & machine::LOCSTAT_DIRTY) t += " D"; else if (loc_stat & machine::LOCSTAT_CACHED) t += " C"; } #endif return t; } if (role == Qt::BackgroundRole) { machine::Address address; if (!get_row_address(address, index.row()) || machine == nullptr || index.column() == 0) { return {}; } address += cellSizeBytes() * (index.column() - 1); if (machine->cache_data() != nullptr) { machine::LocationStatus loc_stat; loc_stat = machine->cache_data()->location_status(address); if (loc_stat & machine::LOCSTAT_DIRTY) { QBrush bgd(Qt::yellow); return bgd; } else if (loc_stat & machine::LOCSTAT_CACHED) { QBrush bgd(Qt::lightGray); return bgd; } } return {}; } if (role == Qt::FontRole) { return data_font; } return {}; } void MemoryModel::setup(machine::Machine *machine) { this->machine = machine; if (machine != nullptr) { connect(machine, &machine::Machine::post_tick, this, &MemoryModel::check_for_updates); } if (mem_access() != nullptr) { connect( mem_access(), &machine::FrontendMemory::external_change_notify, this, &MemoryModel::check_for_updates); } emit update_all(); emit setup_done(); } void MemoryModel::setCellsPerRow(unsigned int cells) { beginResetModel(); cells_per_row = cells; endResetModel(); } void MemoryModel::set_cell_size(int index) { beginResetModel(); cell_size = (enum MemoryCellSize)index; index0_offset -= index0_offset.get_raw() % cellSizeBytes(); endResetModel(); emit cell_size_changed(); } void MemoryModel::update_all() { const machine::FrontendMemory *mem; mem = mem_access(); if (mem != nullptr) { memory_change_counter = mem->get_change_counter(); if (machine->cache_data() != nullptr) { cache_data_change_counter = machine->cache_data()->get_change_counter(); } } emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } void MemoryModel::check_for_updates() { bool need_update = false; const machine::FrontendMemory *mem; mem = mem_access(); if (mem == nullptr) { return; } if (memory_change_counter != mem->get_change_counter()) { need_update = true; } if (machine->cache_data() != nullptr) { if (cache_data_change_counter != machine->cache_data()->get_change_counter()) { need_update = true; } } if (!need_update) { return; } update_all(); } bool MemoryModel::adjustRowAndOffset(int &row, machine::Address address) { row = rowCount() / 2; address -= address.get_raw() % cellSizeBytes(); uint32_t row_bytes = cells_per_row * cellSizeBytes(); uint32_t diff = row * row_bytes; if (machine::Address(diff) > address) { row = address.get_raw() / row_bytes; if (row == 0) { index0_offset = machine::Address::null(); } else { index0_offset = address - row * row_bytes; } } else { index0_offset = address - diff; } return get_row_for_address(row, address); } void MemoryModel::cached_access(int cached) { mem_access_kind = cached; update_all(); } Qt::ItemFlags MemoryModel::flags(const QModelIndex &index) const { if (index.column() == 0) { return QAbstractTableModel::flags(index); } else { return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; } } bool MemoryModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole) { bool ok; machine::Address address; machine::FrontendMemory *mem; uint32_t data = value.toString().toULong(&ok, 16); if (!ok) { return false; } if (!get_row_address(address, index.row())) { return false; } if (index.column() == 0 || machine == nullptr) { return false; } if (machine->config().get_vm_enabled()) { if (mem_access_kind == MEM_ACC_PHYS_ADDR) { mem = machine->get_tlb_data_rw(); } else { mem = mem_access_phys_rw(); } } else { mem = mem_access_rw(); if (mem_access_kind > MEM_ACC_AS_CPU && machine->cache_data_rw()) { mem = machine->cache_data_rw(); } } if (mem == nullptr) { return false; } if ((mem_access_kind > MEM_ACC_AS_CPU) && (machine->cache_data_rw() != nullptr)) { mem = machine->cache_data_rw(); } address += cellSizeBytes() * (index.column() - 1); switch (cell_size) { case CELLSIZE_BYTE: mem->write_u8(address, data, ae::INTERNAL); break; case CELLSIZE_HWORD: mem->write_u16(address, data, ae::INTERNAL); break; default: case CELLSIZE_WORD: mem->write_u32(address, data, ae::INTERNAL); break; } } return true; } const machine::FrontendMemory *MemoryModel::mem_access_phys() const { if (!machine) return nullptr; if (mem_access_kind > MEM_ACC_AS_CPU && machine->cache_data()) { return machine->cache_data(); } else { return machine->memory_data_bus(); } } machine::FrontendMemory *MemoryModel::mem_access_phys_rw() const { if (!machine) return nullptr; if (mem_access_kind > MEM_ACC_AS_CPU && machine->cache_data_rw()) { return machine->cache_data_rw(); } else { return machine->memory_data_bus_rw(); } } ================================================ FILE: src/gui/windows/memory/memorymodel.h ================================================ #ifndef MEMORYMODEL_H #define MEMORYMODEL_H #include "machine/machine.h" #include #include class MemoryModel : public QAbstractTableModel { Q_OBJECT using Super = QAbstractTableModel; public: enum MemoryCellSize { CELLSIZE_BYTE, CELLSIZE_HWORD, CELLSIZE_WORD, }; enum MemoryAccessAtLevel { MEM_ACC_AS_CPU = 0, MEM_ACC_VIRT_ADDR = 1, MEM_ACC_PHYS_ADDR = 2, MEM_ACC_PHYS_ADDR_SKIP_CACHES = 3, MEM_ACC_AS_MACHINE = 4, }; explicit MemoryModel(QObject *parent); [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool adjustRowAndOffset(int &row, machine::Address address); void update_all(); void setCellsPerRow(unsigned int cells); [[nodiscard]] inline unsigned int cellsPerRow() const { return cells_per_row; } [[nodiscard]] inline const QFont *getFont() const { return &data_font; } [[nodiscard]] inline machine::Address getIndex0Offset() const { return index0_offset; } [[nodiscard]] inline unsigned int cellSizeBytes() const { switch (cell_size) { case CELLSIZE_BYTE: return 1; case CELLSIZE_HWORD: return 2; case CELLSIZE_WORD: return 4; } return 0; } inline bool get_row_address(machine::Address &address, int row) const { address = index0_offset + (row * cells_per_row * cellSizeBytes()); return address >= index0_offset; } inline bool get_row_for_address(int &row, machine::Address address) const { if (address < index0_offset) { row = -1; return false; } row = (address - index0_offset) / (cells_per_row * cellSizeBytes()); if ((address - index0_offset > 0x80000000) || row > rowCount()) { row = rowCount(); return false; } return true; } public slots: void setup(machine::Machine *machine); void set_cell_size(int index); void check_for_updates(); void cached_access(int cached); signals: void cell_size_changed(); void setup_done(); private: [[nodiscard]] const machine::FrontendMemory *mem_access() const; [[nodiscard]] machine::FrontendMemory *mem_access_rw() const; [[nodiscard]] const machine::FrontendMemory *mem_access_phys() const; [[nodiscard]] machine::FrontendMemory *mem_access_phys_rw() const; enum MemoryCellSize cell_size; unsigned int cells_per_row; machine::Address index0_offset; QFont data_font; machine::Machine *machine; uint32_t memory_change_counter; uint32_t cache_data_change_counter; int mem_access_kind; }; #endif // MEMORYMODEL_H ================================================ FILE: src/gui/windows/memory/memorytableview.cpp ================================================ #include "memorytableview.h" #include "common/polyfills/qt5/qfontmetrics.h" #include "hinttabledelegate.h" #include "memorymodel.h" #include #include #include #include #include #include #include MemoryTableView::MemoryTableView(QWidget *parent, QSettings *settings) : Super(parent) { setItemDelegate(new HintTableDelegate(this)); connect( verticalScrollBar(), &QAbstractSlider::valueChanged, this, &MemoryTableView::adjust_scroll_pos_check); connect( this, &MemoryTableView::adjust_scroll_pos_queue, this, &MemoryTableView::adjust_scroll_pos_process, Qt::QueuedConnection); this->settings = settings; initial_address = machine::Address(settings->value("DataViewAddr0", 0).toULongLong()); adjust_scroll_pos_in_progress = false; setTextElideMode(Qt::ElideNone); } void MemoryTableView::addr0_save_change(machine::Address val) { settings->setValue("DataViewAddr0", qint64(val.get_raw())); } void MemoryTableView::adjustColumnCount() { auto *m = dynamic_cast(model()); if (m == nullptr) { return; } auto *delegate = dynamic_cast(itemDelegate()); if (delegate == nullptr) { return; } if (horizontalHeader()->count() >= 2) { QModelIndex idx; QFontMetrics fm(*m->getFont()); idx = m->index(0, 0); QStyleOptionViewItem viewOpts; initViewItemOption(&viewOpts); // int width0_dh = itemDelegate(idx)->sizeHint(viewOptions(), // idx).get_width() + 2; int width0_dh = delegate->sizeHintForText(viewOpts, idx, "0x00000000").width() + 2; horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); horizontalHeader()->resizeSection(0, width0_dh); idx = m->index(0, 1); QString t = ""; t.fill(QChar('0'), m->cellSizeBytes() * 2); int width1_dh = delegate->sizeHintForText(viewOpts, idx, t).width() + 2; if (width1_dh < QFontMetrics_horizontalAdvance(fm, "+99")) { width1_dh = QFontMetrics_horizontalAdvance(fm, "+99"); } horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); horizontalHeader()->resizeSection(1, width1_dh); int w = verticalHeader()->width() + 4; unsigned int cells; int width0 = columnWidth(0); int width1 = columnWidth(1); w = width() - w - width0; if (w < width1 + 4) { cells = 1; } else { cells = w / (width1 + 4); } if (cells != m->cellsPerRow()) { m->setCellsPerRow(cells); } for (unsigned int i = 1; i < m->cellsPerRow() + 1; i++) { horizontalHeader()->setSectionResizeMode(i, QHeaderView::Fixed); horizontalHeader()->resizeSection(i, width1); } if (!initial_address.is_null()) { go_to_address(initial_address); initial_address = machine::Address::null(); } } } void MemoryTableView::recompute_columns() { adjustColumnCount(); } void MemoryTableView::set_cell_size(int index) { machine::Address address; int row; bool keep_row0 = false; auto *m = dynamic_cast(model()); if (m != nullptr) { keep_row0 = m->get_row_address(address, rowAt(0)); m->set_cell_size(index); } adjustColumnCount(); if (keep_row0) { m->adjustRowAndOffset(row, address); scrollTo(m->index(row, 0), QAbstractItemView::PositionAtTop); } } void MemoryTableView::adjust_scroll_pos_check() { if (!adjust_scroll_pos_in_progress) { adjust_scroll_pos_in_progress = true; emit adjust_scroll_pos_queue(); } } void MemoryTableView::adjust_scroll_pos_process() { adjust_scroll_pos_in_progress = false; machine::Address address; auto *m = dynamic_cast(model()); if (m == nullptr) { return; } QModelIndex prev_index = currentIndex(); auto row_bytes = machine::Address(m->cellSizeBytes() * m->cellsPerRow()); machine::Address index0_offset = m->getIndex0Offset(); do { int row = rowAt(0); int prev_row = row; if (row < m->rowCount() / 8) { if ((row == 0) && (index0_offset < row_bytes) && (!index0_offset.is_null())) { m->adjustRowAndOffset(row, machine::Address::null()); } else if (index0_offset > row_bytes) { m->get_row_address(address, row); m->adjustRowAndOffset(row, address); } else { break; } } else if (row > m->rowCount() - m->rowCount() / 8) { m->get_row_address(address, row); m->adjustRowAndOffset(row, address); } else { break; } scrollTo(m->index(row, 0), QAbstractItemView::PositionAtTop); setCurrentIndex(m->index(prev_index.row() + row - prev_row, prev_index.column())); emit m->update_all(); } while (false); m->get_row_address(address, rowAt(0)); addr0_save_change(address); emit address_changed(address); } void MemoryTableView::resizeEvent(QResizeEvent *event) { auto *m = dynamic_cast(model()); machine::Address address; bool keep_row0 = false; if (m != nullptr) { if (initial_address.is_null()) { keep_row0 = m->get_row_address(address, rowAt(0)); } else { address = initial_address; } } Super::resizeEvent(event); adjustColumnCount(); if (keep_row0) { initial_address = machine::Address::null(); go_to_address(address); } } void MemoryTableView::go_to_address(machine::Address address) { auto *m = dynamic_cast(model()); int row; if (m == nullptr) { return; } m->adjustRowAndOffset(row, address); scrollTo(m->index(row, 0), QAbstractItemView::PositionAtTop); setCurrentIndex(m->index(row, 1)); addr0_save_change(address); emit m->update_all(); } void MemoryTableView::focus_address(machine::Address address) { int row; auto *m = dynamic_cast(model()); if (m == nullptr) { return; } if (!m->get_row_for_address(row, address)) { go_to_address(address); } if (!m->get_row_for_address(row, address)) { return; } setCurrentIndex(m->index(row, 1)); } void MemoryTableView::keyPressEvent(QKeyEvent *event) { if (event->matches(QKeySequence::Copy)) { QString text; QItemSelectionRange range = selectionModel()->selection().first(); for (auto i = range.top(); i <= range.bottom(); ++i) { QStringList rowContents; for (auto j = range.left(); j <= range.right(); ++j) { rowContents << model()->index(i, j).data().toString(); } text += rowContents.join("\t"); text += "\n"; } QApplication::clipboard()->setText(text); } else { Super::keyPressEvent(event); } } ================================================ FILE: src/gui/windows/memory/memorytableview.h ================================================ #ifndef MEMORYTABLEVIEW_H #define MEMORYTABLEVIEW_H #include "common/polyfills/qt5/qtableview.h" #include "machine/memory/address.h" #include #include #include class MemoryTableView : public Poly_QTableView { Q_OBJECT using Super = Poly_QTableView; public: MemoryTableView(QWidget *parent, QSettings *settings); void resizeEvent(QResizeEvent *event) override; signals: void address_changed(machine::Address address); void adjust_scroll_pos_queue(); public slots: void set_cell_size(int index); void go_to_address(machine::Address address); void focus_address(machine::Address address); void recompute_columns(); protected: void keyPressEvent(QKeyEvent *event) override; private slots: void adjust_scroll_pos_check(); void adjust_scroll_pos_process(); private: void addr0_save_change(machine::Address val); void adjustColumnCount(); QSettings *settings; machine::Address initial_address; bool adjust_scroll_pos_in_progress; }; #endif // MEMORYTABLEVIEW_H ================================================ FILE: src/gui/windows/messages/messagesdock.cpp ================================================ #include "messagesdock.h" #include "assembler/messagetype.h" #include "messagesmodel.h" #include "messagesview.h" #include "ui/hexlineedit.h" #include #include #include #include #include MessagesDock::MessagesDock(QWidget *parent, QSettings *settings) : Super(parent) { setObjectName("Messages"); setWindowTitle("Messages"); this->settings = settings; QWidget *content = new QWidget(); QListView *messages_content = new MessagesView(nullptr, settings); MessagesModel *messages_model = new MessagesModel(this); messages_content->setModel(messages_model); QVBoxLayout *layout = new QVBoxLayout; layout->addWidget(messages_content); content->setLayout(layout); setWidget(content); connect(this, &MessagesDock::report_message, messages_model, &MessagesModel::insert_line); connect( this, &MessagesDock::pass_clear_messages, messages_model, &MessagesModel::clear_messages); connect( messages_content, &QAbstractItemView::activated, messages_model, &MessagesModel::activated); connect( messages_model, &MessagesModel::message_selected, this, &MessagesDock::message_selected); } void MessagesDock::insert_line( messagetype::Type type, QString file, int line, int column, QString text, QString hint) { report_message(type, std::move(file), line, column, std::move(text), std::move(hint)); } void MessagesDock::clear_messages() { emit pass_clear_messages(); } ================================================ FILE: src/gui/windows/messages/messagesdock.h ================================================ #ifndef MESSAGESDOCK_H #define MESSAGESDOCK_H #include "messagesmodel.h" #include #include #include #include class MessagesDock : public QDockWidget { Q_OBJECT using Super = QDockWidget; public: MessagesDock(QWidget *parent, QSettings *settings); public slots: void insert_line( messagetype::Type type, QString file, int line, int column, QString text, QString hint); void clear_messages(); signals: void report_message( messagetype::Type type, QString file, int line, int column, QString text, QString hint); void pass_clear_messages(); void message_selected( messagetype::Type type, QString file, int line, int column, QString text, QString hint); private: QSettings *settings; }; #endif // MESSAGESDOCK_H ================================================ FILE: src/gui/windows/messages/messagesmodel.cpp ================================================ #include "messagesmodel.h" #include #include class MessagesEntry { public: inline MessagesEntry( messagetype::Type type, QString file, int line, int column, QString text, QString hint) { this->type = type; this->file = std::move(file); this->line = line; this->column = column; this->text = std::move(text); this->hint = std::move(hint); } messagetype::Type type; QString file; int line; int column; QString text; QString hint; }; MessagesModel::MessagesModel(QObject *parent) : Super(parent) {} MessagesModel::~MessagesModel() { clear_messages(); } int MessagesModel::rowCount(const QModelIndex & /*parent*/) const { return messages.count(); } int MessagesModel::columnCount(const QModelIndex & /*parent*/) const { return 1; } QVariant MessagesModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { switch (section) { case 0: return tr("Type"); case 1: return tr("Source"); case 2: return tr("Line"); case 3: return tr("Column"); case 4: return tr("Text"); default: return tr(""); } } } return Super::headerData(section, orientation, role); } QVariant MessagesModel::data(const QModelIndex &index, int role) const { if (index.row() >= rowCount()) { return {}; } if (role == Qt::DisplayRole || role == Qt::EditRole) { MessagesEntry *ent = messages.at(index.row()); QString ret = ""; if (!ent->file.isEmpty()) { ret += ent->file + ":"; } if (ent->line) { ret += QString::number(ent->line) + ":"; } if (ent->column) { ret += QString::number(ent->column) + ":"; } ret += ent->text; return ret; } if (role == Qt::BackgroundRole) { MessagesEntry *ent = messages.at(index.row()); switch (ent->type) { case messagetype::MSG_ERROR: return QBrush(QColor(255, 230, 230)); case messagetype::MSG_WARNING: return QBrush(QColor(255, 255, 220)); default: return {}; } } return {}; } void MessagesModel::insert_line( messagetype::Type type, const QString &file, int line, int column, const QString &text, const QString &hint) { beginInsertRows(QModelIndex(), rowCount(), rowCount()); messages.append(new MessagesEntry(type, file, line, column, text, hint)); endInsertRows(); } void MessagesModel::clear_messages() { auto row_count = rowCount(); if (row_count == 0) return; beginRemoveRows(QModelIndex(), 0, row_count - 1); while (!messages.isEmpty()) { delete messages.takeFirst(); } endRemoveRows(); } void MessagesModel::activated(QModelIndex index) { if (index.row() >= rowCount()) { return; } MessagesEntry *ent = messages.at(index.row()); emit message_selected(ent->type, ent->file, ent->line, ent->column, ent->text, ent->hint); } ================================================ FILE: src/gui/windows/messages/messagesmodel.h ================================================ #ifndef MESSAGESMODEL_H #define MESSAGESMODEL_H #include "assembler/messagetype.h" #include #include #include class MessagesEntry; class MessagesModel : public QAbstractListModel { Q_OBJECT using Super = QAbstractListModel; public: explicit MessagesModel(QObject *parent); ~MessagesModel() override; [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; public slots: void insert_line( messagetype::Type type, const QString &file, int line, int column, const QString &text, const QString &hint); void clear_messages(); void activated(QModelIndex index); signals: void message_selected( messagetype::Type type, QString file, int line, int column, QString text, QString hint); private: QVector messages; }; #endif // MESSAGESMODEL_H ================================================ FILE: src/gui/windows/messages/messagesview.cpp ================================================ #include "messagesview.h" #include "messagesmodel.h" #include #include #include #include #include #include MessagesView::MessagesView(QWidget *parent, QSettings *settings) : Super(parent) { this->settings = settings; } void MessagesView::resizeEvent(QResizeEvent *event) { Super::resizeEvent(event); } void MessagesView::keyPressEvent(QKeyEvent *event) { if (event->matches(QKeySequence::Copy)) { QString text; QItemSelectionRange range = selectionModel()->selection().first(); for (auto i = range.top(); i <= range.bottom(); ++i) { QStringList rowContents; for (auto j = range.left(); j <= range.right(); ++j) { rowContents << model()->index(i, j).data().toString(); } text += rowContents.join("\t"); text += "\n"; } QApplication::clipboard()->setText(text); } else { Super::keyPressEvent(event); } } ================================================ FILE: src/gui/windows/messages/messagesview.h ================================================ #ifndef MESSAGESVIEW_H #define MESSAGESVIEW_H #include #include #include #include class MessagesView : public QListView { Q_OBJECT using Super = QListView; public: MessagesView(QWidget *parent, QSettings *settings); void resizeEvent(QResizeEvent *event) override; protected: void keyPressEvent(QKeyEvent *event) override; QSettings *settings; }; #endif // MESSAGESVIEW_H ================================================ FILE: src/gui/windows/peripherals/peripheralsdock.cpp ================================================ #include "peripheralsdock.h" PeripheralsDock::PeripheralsDock(QWidget *parent, QSettings *settings) : QDockWidget(parent) { (void)settings; top_widget = new QWidget(this); setWidget(top_widget); layout_box = new QVBoxLayout(top_widget); periph_view = new PeripheralsView(nullptr); layout_box->addWidget(periph_view); setObjectName("Peripherals"); setWindowTitle("Peripherals"); } void PeripheralsDock::setup(const machine::PeripSpiLed *perip_spi_led) { periph_view->setup(perip_spi_led); } ================================================ FILE: src/gui/windows/peripherals/peripheralsdock.h ================================================ #ifndef PERIPHERALSDOCK_H #define PERIPHERALSDOCK_H #include "machine/machine.h" #include "machine/memory/backend/peripheral.h" #include "machine/memory/backend/peripspiled.h" #include "peripheralsview.h" #include #include #include class PeripheralsDock : public QDockWidget { Q_OBJECT public: PeripheralsDock(QWidget *parent, QSettings *settings); void setup(const machine::PeripSpiLed *perip_spi_led); private: QVBoxLayout *layout_box; QWidget *top_widget, *top_form {}; QFormLayout *layout_top_form {}; PeripheralsView *periph_view; }; #endif // PERIPHERALSDOCK_H ================================================ FILE: src/gui/windows/peripherals/peripheralsview.cpp ================================================ #include "peripheralsview.h" #include "ui_peripheralsview.h" PeripheralsView::PeripheralsView(QWidget *parent) : QWidget(parent), ui(new Ui::PeripheralsView) { ui->setupUi(this); ui->dialRed->setStyleSheet("QDial { background-color: red }"); ui->dialGreen->setStyleSheet("QDial { background-color: green }"); ui->dialBlue->setStyleSheet("QDial { background-color: blue }"); connect(ui->dialRed, &QAbstractSlider::valueChanged, ui->spinRed, &QSpinBox::setValue); connect(ui->dialGreen, &QAbstractSlider::valueChanged, ui->spinGreen, &QSpinBox::setValue); connect(ui->dialBlue, &QAbstractSlider::valueChanged, ui->spinBlue, &QSpinBox::setValue); connect( ui->spinRed, QOverload::of(&QSpinBox::valueChanged), ui->dialRed, &QAbstractSlider::setValue); connect( ui->spinGreen, QOverload::of(&QSpinBox::valueChanged), ui->dialGreen, &QAbstractSlider::setValue); connect( ui->spinBlue, QOverload::of(&QSpinBox::valueChanged), ui->dialBlue, &QAbstractSlider::setValue); } void PeripheralsView::setup(const machine::PeripSpiLed *perip_spi_led) { int val; connect( ui->spinRed, QOverload::of(&QSpinBox::valueChanged), perip_spi_led, &machine::PeripSpiLed::red_knob_update); connect( ui->spinGreen, QOverload::of(&QSpinBox::valueChanged), perip_spi_led, &machine::PeripSpiLed::green_knob_update); connect( ui->spinBlue, QOverload::of(&QSpinBox::valueChanged), perip_spi_led, &machine::PeripSpiLed::blue_knob_update); val = ui->spinRed->value(); ui->spinRed->setValue(val - 1); ui->spinRed->setValue(val); val = ui->spinGreen->value(); ui->spinGreen->setValue(val - 1); ui->spinGreen->setValue(val); val = ui->spinBlue->value(); ui->spinBlue->setValue(val - 1); ui->spinBlue->setValue(val); connect( ui->checkRed, &QAbstractButton::clicked, perip_spi_led, &machine::PeripSpiLed::red_knob_push); connect( ui->checkGreen, &QAbstractButton::clicked, perip_spi_led, &machine::PeripSpiLed::green_knob_push); connect( ui->checkBlue, &QAbstractButton::clicked, perip_spi_led, &machine::PeripSpiLed::blue_knob_push); ui->checkRed->setChecked(false); ui->checkGreen->setChecked(false); ui->checkBlue->setChecked(false); ui->labelRgb1->setAutoFillBackground(true); ui->labelRgb2->setAutoFillBackground(true); connect( perip_spi_led, &machine::PeripSpiLed::led_line_changed, this, &PeripheralsView::led_line_changed); connect( perip_spi_led, &machine::PeripSpiLed::led_rgb1_changed, this, &PeripheralsView::led_rgb1_changed); connect( perip_spi_led, &machine::PeripSpiLed::led_rgb2_changed, this, &PeripheralsView::led_rgb2_changed); led_line_changed(0); led_rgb1_changed(0); led_rgb2_changed(0); } void PeripheralsView::led_line_changed(uint val) { QString s, t; s = QString::number(val, 16); t.fill('0', 8 - s.count()); ui->lineEditHex->setText(t + s); s = QString::number(val, 10); ui->lineEditDec->setText(s); s = QString::number(val, 2); t.fill('0', 32 - s.count()); ui->lineEditBin->setText(t + s); } static void set_widget_background_color(QWidget *w, uint val) { int r = (val >> 16) & 0xff; int g = (val >> 8) & 0xff; int b = (val >> 0) & 0xff; QPalette::ColorRole brole = w->backgroundRole(); QPalette pal = w->palette(); pal.setColor(brole, QColor(r, g, b)); w->setPalette(pal); } void PeripheralsView::led_rgb1_changed(uint val) { QString s, t; s = QString::number(val, 16); t.fill('0', 8 - s.count()); ui->lineEditRgb1->setText(t + s); set_widget_background_color(ui->labelRgb1, val); } void PeripheralsView::led_rgb2_changed(uint val) { QString s, t; s = QString::number(val, 16); t.fill('0', 8 - s.count()); ui->lineEditRgb2->setText(t + s); set_widget_background_color(ui->labelRgb2, val); } ================================================ FILE: src/gui/windows/peripherals/peripheralsview.h ================================================ #ifndef PERIPHERALSVIEW_H #define PERIPHERALSVIEW_H #include "common/memory_ownership.h" #include "machine/memory/backend/peripspiled.h" #include "ui_peripheralsview.h" #include class PeripheralsView : public QWidget { Q_OBJECT public: explicit PeripheralsView(QWidget *parent = nullptr); void setup(const machine::PeripSpiLed *perip_spi_led); public slots: void led_line_changed(uint val); void led_rgb1_changed(uint val); void led_rgb2_changed(uint val); private: Box ui {}; }; #endif // PERIPHERALSVIEW_H ================================================ FILE: src/gui/windows/peripherals/peripheralsview.ui ================================================ PeripheralsView 0 0 573 484 Form QLayout::SetMaximumSize LED RGB 1 Qt::AlignCenter Qt::AlignBottom|Qt::AlignHCenter true Qt::Horizontal 40 20 LED RGB 2 Qt::AlignCenter Qt::AlignBottom|Qt::AlignHCenter true QLayout::SetNoConstraint 255 true Red Knob Qt::AlignCenter Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 255 Qt::Horizontal 40 20 255 true Green Knob Qt::AlignCenter Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 255 Qt::Horizontal 40 20 255 true Blue Knob Qt::AlignCenter Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter 255 Word hexadecimal Qt::AlignBottom|Qt::AlignHCenter Qt::AlignCenter true Qt::Horizontal 40 20 Word decimal Qt::AlignBottom|Qt::AlignHCenter Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter true Word binary Qt::AlignBottom|Qt::AlignLeading|Qt::AlignLeft Qt::AlignCenter true ================================================ FILE: src/gui/windows/predictor/predictor_bht_dock.cpp ================================================ #include "predictor_bht_dock.h" LOG_CATEGORY("gui.DockPredictorBHT"); DockPredictorBHT::DockPredictorBHT(QWidget *parent) : Super(parent) { setObjectName("PredictorBHT"); setWindowTitle("Predictor Branch History"); ///////////////////////// // Assign layout // Name layout_type->addWidget(label_type); layout_type->addWidget(label_type_value); // Stats layout_stats->addWidget(label_stats_correct_text, 0, 0); layout_stats->addWidget(label_stats_correct_value, 0, 1); layout_stats->addWidget(label_stats_wrong_text, 1, 0); layout_stats->addWidget(label_stats_wrong_value, 1, 1); layout_stats->addWidget(label_stats_accuracy_text, 2, 0); layout_stats->addWidget(label_stats_accuracy_value, 2, 1); // Main layout layout_main->addLayout(layout_type); layout_main->addLayout(layout_stats); layout_main->addLayout(layout_event); layout_main->addWidget(bht); content->setLayout(layout_main); setWidget(content); ///////////////////////// // Init widget properties // Name label_type->setText("Predictor type:"); label_type->setStyleSheet("font-weight: bold;"); clear_name(); // Stats label_stats_correct_text->setText("Correct predictions:"); label_stats_wrong_text->setText("Wrong predictions:"); label_stats_accuracy_text->setText("Accuracy:"); clear_stats(); // BHT bht->setRowCount(0); bht->setColumnCount(5); bht->setHorizontalHeaderLabels({ "Index", "State", "Correct", "Incorrect", "Accuracy" }); bht->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); bht->verticalHeader()->hide(); bht->resizeRowsToContents(); } // Get BHT cell item, or create new one if needed QTableWidgetItem *DockPredictorBHT::get_bht_cell_item(uint8_t row_index, uint8_t col_index) { QTableWidgetItem *item { bht->item(row_index, col_index) }; if (item == nullptr) { item = new QTableWidgetItem(); bht->setItem(row_index, col_index, item); item->setTextAlignment(Qt::AlignCenter); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } return item; } void DockPredictorBHT::set_table_color(QColor color) { for (uint16_t row_index = 0; row_index < bht->rowCount(); row_index++) { for (uint16_t column_index = 0; column_index < bht->columnCount(); column_index++) { get_bht_cell_item(row_index, column_index)->setBackground(QBrush(color)); } } } void DockPredictorBHT::set_row_color(uint16_t row_index, QColor color) { for (uint16_t column_index = 0; column_index < bht->columnCount(); column_index++) { get_bht_cell_item(row_index, column_index)->setBackground(QBrush(color)); } } void DockPredictorBHT::setup( const machine::BranchPredictor *branch_predictor, const machine::Core *core) { clear(); number_of_bhr_bits = branch_predictor->get_number_of_bhr_bits(); number_of_bht_bits = branch_predictor->get_number_of_bht_bits(); initial_state = branch_predictor->get_initial_state(); const machine::PredictorType predictor_type { branch_predictor->get_predictor_type() }; const bool is_predictor_dynamic { machine::is_predictor_type_dynamic(predictor_type) }; const bool is_predictor_enabled { branch_predictor->get_enabled() }; if (is_predictor_enabled) { content->setDisabled(false); label_type_value->setText(branch_predictor->get_predictor_name().toString()); connect( branch_predictor, &machine::BranchPredictor::predictor_stats_updated, this, &DockPredictorBHT::update_predictor_stats); connect( branch_predictor, &machine::BranchPredictor::prediction_done, this, &DockPredictorBHT::show_new_prediction); connect(core, &machine::Core::step_started, this, &DockPredictorBHT::reset_colors); if (is_predictor_dynamic) { bht->setDisabled(false); bht->setRowCount(qPow(2, number_of_bht_bits)); clear_bht(initial_state); connect( branch_predictor, &machine::BranchPredictor::update_done, this, &DockPredictorBHT::show_new_update); connect( branch_predictor, &machine::BranchPredictor::predictor_bht_row_updated, this, &DockPredictorBHT::update_bht_row); } else { bht->setDisabled(true); bht->setRowCount(0); } } else { content->setDisabled(true); label_type_value->setText("None"); } } void DockPredictorBHT::show_new_prediction( uint16_t btb_index, uint16_t bht_index, machine::PredictionInput input, machine::BranchResult result, machine::BranchType branch_type) { UNUSED(btb_index); UNUSED(input); UNUSED(result); if (branch_type == machine::BranchType::BRANCH) { set_row_color(bht_index, Q_COLOR_PREDICT); } } void DockPredictorBHT::show_new_update( uint16_t btb_index, uint16_t bht_index, machine::PredictionFeedback feedback) { UNUSED(btb_index); if (feedback.branch_type == machine::BranchType::BRANCH) { set_row_color(bht_index, Q_COLOR_UPDATE); } } void DockPredictorBHT::update_predictor_stats(machine::PredictionStatistics stats) { label_stats_correct_value->setText(QString::number(stats.correct)); label_stats_wrong_value->setText(QString::number(stats.wrong)); label_stats_accuracy_value->setText(QString::number(stats.accuracy) + " %"); if (stats.total > 0) { label_stats_accuracy_value->setText(QString::number(stats.accuracy) + " %"); } else { label_stats_accuracy_value->setText("N/A"); } } void DockPredictorBHT::update_bht_row( uint16_t row_index, machine::BranchHistoryTableEntry bht_entry) { if (row_index >= bht->rowCount()) { WARN("BHT dock update received invalid row index: %u", row_index); return; } for (uint16_t column_index = 0; column_index < bht->columnCount(); column_index++) { get_bht_cell_item(row_index, DOCK_BHT_COL_STATE) ->setData( Qt::DisplayRole, machine::predictor_state_to_string(bht_entry.state, true).toString()); get_bht_cell_item(row_index, DOCK_BHT_COL_CORRECT) ->setData(Qt::DisplayRole, QString::number(bht_entry.stats.correct)); get_bht_cell_item(row_index, DOCK_BHT_COL_INCORRECT) ->setData(Qt::DisplayRole, QString::number(bht_entry.stats.wrong)); if (bht_entry.stats.total > 0) { get_bht_cell_item(row_index, DOCK_BHT_COL_ACCURACY) ->setData(Qt::DisplayRole, { QString::number(bht_entry.stats.accuracy) + " %" }); } else { get_bht_cell_item(row_index, DOCK_BHT_COL_ACCURACY)->setData(Qt::DisplayRole, "N/A"); } } } void DockPredictorBHT::reset_colors() { set_table_color(Q_COLOR_DEFAULT); } void DockPredictorBHT::clear_stats() { label_stats_correct_value->setText("0"); label_stats_wrong_value->setText("0"); label_stats_accuracy_value->setText("N/A"); } void DockPredictorBHT::clear_name() { label_type_value->setText(""); } void DockPredictorBHT::clear_bht(machine::PredictorState initial_state) { for (uint16_t row_index = 0; row_index < bht->rowCount(); row_index++) { get_bht_cell_item(row_index, DOCK_BHT_COL_INDEX) ->setData(Qt::DisplayRole, QString::number(row_index)); get_bht_cell_item(row_index, DOCK_BHT_COL_STATE) ->setData( Qt::DisplayRole, machine::predictor_state_to_string(initial_state, true).toString()); get_bht_cell_item(row_index, DOCK_BHT_COL_CORRECT) ->setData(Qt::DisplayRole, QString::number(0)); get_bht_cell_item(row_index, DOCK_BHT_COL_INCORRECT) ->setData(Qt::DisplayRole, QString::number(0)); get_bht_cell_item(row_index, DOCK_BHT_COL_ACCURACY) ->setData(Qt::DisplayRole, QString("N/A")); } bht->resizeRowsToContents(); set_table_color(Q_COLOR_DEFAULT); } void DockPredictorBHT::clear() { clear_name(); clear_stats(); clear_bht(); } ================================================ FILE: src/gui/windows/predictor/predictor_bht_dock.h ================================================ #ifndef PREDICTOR_BHT_DOCK_H #define PREDICTOR_BHT_DOCK_H #include "common/polyfills/qt5/qtableview.h" #include "machine/machine.h" #include "machine/memory/address.h" #include "machine/predictor.h" #include "machine/predictor_types.h" #include "ui/hexlineedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DOCK_BHT_COL_INDEX 0 #define DOCK_BHT_COL_STATE 1 #define DOCK_BHT_COL_CORRECT 2 #define DOCK_BHT_COL_INCORRECT 3 #define DOCK_BHT_COL_ACCURACY 4 #define STYLESHEET_COLOR_DEFAULT "background: rgb(255,255,255);" #define STYLESHEET_COLOR_PREDICT "background: rgb(255,173,173);" #define STYLESHEET_COLOR_UPDATE "background: rgb(173,255,229);" #define Q_COLOR_DEFAULT QColor(255, 255, 255) #define Q_COLOR_PREDICT QColor(255, 173, 173) #define Q_COLOR_UPDATE QColor(173, 255, 229) class DockPredictorBHT : public QDockWidget { Q_OBJECT using Super = QDockWidget; public: // Constructors & Destructor DockPredictorBHT(QWidget *parent); private: // Internal functions QTableWidgetItem *get_bht_cell_item(uint8_t row_index, uint8_t col_index); void set_table_color(QColor color); void set_row_color(uint16_t row_index, QColor color); public: // General functions void setup(const machine::BranchPredictor *branch_predictor, const machine::Core *core); public slots: void show_new_prediction( uint16_t btb_index, uint16_t bht_index, machine::PredictionInput input, machine::BranchResult result, machine::BranchType branch_type); void show_new_update(uint16_t btb_index, uint16_t bht_index, machine::PredictionFeedback feedback); void update_predictor_stats(machine::PredictionStatistics stats); void update_bht_row(uint16_t row_index, machine::BranchHistoryTableEntry bht_entry); void reset_colors(); void clear_name(); void clear_stats(); void clear_bht(machine::PredictorState initial_state = machine::PredictorState::UNDEFINED); void clear(); private: // Internal variables uint8_t number_of_bhr_bits { 0 }; uint8_t number_of_bht_bits { 0 }; machine::PredictorState initial_state { machine::PredictorState::UNDEFINED }; QT_OWNED QGroupBox *content { new QGroupBox() }; QT_OWNED QVBoxLayout *layout_main { new QVBoxLayout() }; // Name QT_OWNED QHBoxLayout *layout_type { new QHBoxLayout() }; QT_OWNED QLabel *label_type { new QLabel() }; QT_OWNED QLabel *label_type_value { new QLabel() }; // Stats QT_OWNED QGridLayout *layout_stats { new QGridLayout() }; QT_OWNED QLabel *label_stats_correct_text { new QLabel() }; QT_OWNED QLabel *label_stats_wrong_text { new QLabel() }; QT_OWNED QLabel *label_stats_accuracy_text { new QLabel() }; QT_OWNED QLabel *label_stats_correct_value { new QLabel() }; QT_OWNED QLabel *label_stats_wrong_value { new QLabel() }; QT_OWNED QLabel *label_stats_accuracy_value { new QLabel() }; // Prediction & Update QT_OWNED QHBoxLayout *layout_event { new QHBoxLayout() }; // BHT QT_OWNED QTableWidget *bht { new QTableWidget() }; }; #endif // PREDICTOR_BHT_DOCK_H ================================================ FILE: src/gui/windows/predictor/predictor_btb_dock.cpp ================================================ #include "predictor_btb_dock.h" LOG_CATEGORY("gui.DockPredictorBTB"); DockPredictorBTB::DockPredictorBTB(QWidget *parent) : Super(parent) { setObjectName("PredictorBTB"); setWindowTitle("Predictor Branch Target Buffer"); ///////////////////////// // Assign layout layout->addWidget(btb); content->setLayout(layout); setWidget(content); ///////////////////////// // Init widget properties // BTB btb->setRowCount(0); btb->setColumnCount(4); btb->setHorizontalHeaderLabels({ "Index", "Instr. Address", "Target Address", "Type" }); btb->horizontalHeader()->setSectionResizeMode(QHeaderView::Stretch); btb->verticalHeader()->hide(); btb->resizeRowsToContents(); } uint8_t DockPredictorBTB::init_number_of_bits(const uint8_t b) const { if (b > BP_MAX_BTB_BITS) { WARN("Number of BTB bits (%u) was larger than %u during init", b, BP_MAX_BTB_BITS); return BP_MAX_BTB_BITS; } return b; } // Get BTB cell item, or create new one if needed QTableWidgetItem *DockPredictorBTB::get_btb_cell_item(uint8_t row_index, uint8_t col_index) { QTableWidgetItem *item { btb->item(row_index, col_index) }; if (item == nullptr) { item = new QTableWidgetItem(); btb->setItem(row_index, col_index, item); item->setTextAlignment(Qt::AlignCenter); item->setFlags(Qt::ItemIsSelectable | Qt::ItemIsEnabled); } return item; } void DockPredictorBTB::set_table_color(QColor color) { for (uint16_t row_index = 0; row_index < btb->rowCount(); row_index++) { for (uint16_t column_index = 0; column_index < btb->columnCount(); column_index++) { get_btb_cell_item(row_index, column_index)->setBackground(QBrush(color)); } } } void DockPredictorBTB::set_row_color(uint16_t row_index, QColor color) { for (uint16_t column_index = 0; column_index < btb->columnCount(); column_index++) { get_btb_cell_item(row_index, column_index)->setBackground(QBrush(color)); } } void DockPredictorBTB::setup( const machine::BranchPredictor *branch_predictor, const machine::Core *core) { clear(); number_of_bits = init_number_of_bits(branch_predictor->get_number_of_btb_bits()); const bool is_predictor_enabled { branch_predictor->get_enabled() }; if (is_predictor_enabled) { btb->setRowCount(qPow(2, number_of_bits)); btb->setDisabled(false); clear_btb(); connect( branch_predictor, &machine::BranchPredictor::btb_row_updated, this, &DockPredictorBTB::update_btb_row); connect( branch_predictor, &machine::BranchPredictor::prediction_done, this, &DockPredictorBTB::highligh_row_after_prediction); connect( branch_predictor, &machine::BranchPredictor::update_done, this, &DockPredictorBTB::highligh_row_after_update); connect(core, &machine::Core::step_started, this, &DockPredictorBTB::reset_colors); } else { btb->setRowCount(0); btb->setDisabled(true); } } void DockPredictorBTB::update_btb_row( uint16_t row_index, machine::BranchTargetBufferEntry btb_entry) { if (row_index >= btb->rowCount()) { WARN("BTB dock update received invalid row index: %u", row_index); return; } if (btb_entry.entry_valid) { get_btb_cell_item(row_index, DOCK_BTB_COL_INSTR_ADDR) ->setData(Qt::DisplayRole, machine::addr_to_hex_str(btb_entry.instruction_address)); get_btb_cell_item(row_index, DOCK_BTB_COL_TARGET_ADDR) ->setData(Qt::DisplayRole, machine::addr_to_hex_str(btb_entry.target_address)); get_btb_cell_item(row_index, DOCK_BTB_COL_TYPE) ->setData( Qt::DisplayRole, machine::branch_type_to_string(btb_entry.branch_type).toString()); } else { get_btb_cell_item(row_index, DOCK_BTB_COL_INSTR_ADDR)->setData(Qt::DisplayRole, ""); get_btb_cell_item(row_index, DOCK_BTB_COL_TARGET_ADDR)->setData(Qt::DisplayRole, ""); get_btb_cell_item(row_index, DOCK_BTB_COL_TYPE)->setData(Qt::DisplayRole, ""); } } void DockPredictorBTB::highligh_row_after_prediction(uint16_t row_index) { set_row_color(row_index, Q_COLOR_PREDICT); } void DockPredictorBTB::highligh_row_after_update(uint16_t row_index) { set_row_color(row_index, Q_COLOR_UPDATE); } void DockPredictorBTB::reset_colors() { set_table_color(Q_COLOR_DEFAULT); } void DockPredictorBTB::clear_btb() { for (uint16_t row_index = 0; row_index < btb->rowCount(); row_index++) { get_btb_cell_item(row_index, DOCK_BTB_COL_INDEX) ->setData(Qt::DisplayRole, QString::number(row_index)); get_btb_cell_item(row_index, DOCK_BTB_COL_INSTR_ADDR)->setData(Qt::DisplayRole, QString("")); get_btb_cell_item(row_index, DOCK_BTB_COL_TARGET_ADDR) ->setData(Qt::DisplayRole, QString("")); get_btb_cell_item(row_index, DOCK_BTB_COL_TYPE)->setData(Qt::DisplayRole, QString("")); } btb->resizeRowsToContents(); set_table_color(Q_COLOR_DEFAULT); } void DockPredictorBTB::clear() { clear_btb(); } ================================================ FILE: src/gui/windows/predictor/predictor_btb_dock.h ================================================ #ifndef PREDICTOR_BTB_DOCK_H #define PREDICTOR_BTB_DOCK_H #include "common/polyfills/qt5/qtableview.h" #include "machine/machine.h" #include "machine/memory/address.h" #include "machine/predictor.h" #include "machine/predictor_types.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define DOCK_BTB_COL_INDEX 0 #define DOCK_BTB_COL_INSTR_ADDR 1 #define DOCK_BTB_COL_TARGET_ADDR 2 #define DOCK_BTB_COL_TYPE 3 #define Q_COLOR_DEFAULT QColor(255, 255, 255) #define Q_COLOR_PREDICT QColor(255, 173, 173) #define Q_COLOR_UPDATE QColor(173, 255, 229) // Branch Target Buffer Dock class DockPredictorBTB : public QDockWidget { Q_OBJECT using Super = QDockWidget; public: // Constructors & Destructor DockPredictorBTB(QWidget *parent); private: // Internal functions uint8_t init_number_of_bits(const uint8_t b) const; QTableWidgetItem *get_btb_cell_item(uint8_t row_index, uint8_t col_index); void set_table_color(QColor color); void set_row_color(uint16_t row_index, QColor color); public: // General functions void setup(const machine::BranchPredictor *branch_predictor, const machine::Core *core); public slots: void update_btb_row(uint16_t row_index, machine::BranchTargetBufferEntry btb_entry); void highligh_row_after_prediction(uint16_t btb_index); void highligh_row_after_update(uint16_t btb_index); void reset_colors(); void clear_btb(); void clear(); private: // Internal variables uint8_t number_of_bits { 0 }; QT_OWNED QGroupBox *content { new QGroupBox() }; QT_OWNED QVBoxLayout *layout { new QVBoxLayout() }; QT_OWNED QTableWidget *btb { new QTableWidget() }; }; #endif // PREDICTOR_BTB_DOCK_H ================================================ FILE: src/gui/windows/predictor/predictor_info_dock.cpp ================================================ #include "predictor_info_dock.h" #include #include #include #include #include #include #include LOG_CATEGORY("gui.DockPredictorInfo"); DockPredictorInfo::DockPredictorInfo(QWidget *parent) : Super(parent) { setObjectName("PredictorInfo"); setWindowTitle("Predictor Info"); ///////////////////////// // Assign layout // Stats layout_stats->addWidget(label_stats_total_text, 0, 0); layout_stats->addWidget(label_stats_total_value, 0, 1); layout_stats->addWidget(label_stats_miss_text, 1, 0); layout_stats->addWidget(label_stats_miss_value, 1, 1); layout_stats->addWidget(label_stats_accuracy_text, 2, 0); layout_stats->addWidget(label_stats_accuracy_value, 2, 1); // BHR layout_bhr->addWidget(label_bhr); layout_bhr->addWidget(value_bhr); // Prediction - BTB index layout_event_predict_index_btb->addWidget(label_event_predict_index_btb); layout_event_predict_index_btb->addWidget(value_event_predict_index_btb); // Prediction - BHT index layout_event_predict_index_bht->addWidget(label_event_predict_index_bht); layout_event_predict_index_bht->addWidget(value_event_predict_index_bht); // Prediction - Indexes layout_event_predict_index->addLayout(layout_event_predict_index_btb); layout_event_predict_index->addLayout(layout_event_predict_index_bht); // Prediction layout_event->addWidget(group_event_predict); group_event_predict->setLayout(layout_event_predict); layout_event_predict->addWidget(label_event_predict_header); layout_event_predict->addWidget(label_event_predict_instruction); layout_event_predict->addWidget(value_event_predict_instruction); layout_event_predict->addWidget(label_event_predict_address); layout_event_predict->addWidget(value_event_predict_address); layout_event_predict->addLayout(layout_event_predict_index); layout_event_predict->addWidget(label_event_predict_result); layout_event_predict->addWidget(value_event_predict_result); // Update - BTB index layout_event_update_index_btb->addWidget(label_event_update_index_btb); layout_event_update_index_btb->addWidget(value_event_update_index_btb); // Update - BHT index layout_event_update_index_bht->addWidget(label_event_update_index_bht); layout_event_update_index_bht->addWidget(value_event_update_index_bht); // Update - Indexes layout_event_update_index->addLayout(layout_event_update_index_btb); layout_event_update_index->addLayout(layout_event_update_index_bht); // Update layout_event->addWidget(group_event_update); group_event_update->setLayout(layout_event_update); layout_event_update->addWidget(label_event_update_header); layout_event_update->addWidget(label_event_update_instruction); layout_event_update->addWidget(value_event_update_instruction); layout_event_update->addWidget(label_event_update_address); layout_event_update->addWidget(value_event_update_address); layout_event_update->addLayout(layout_event_update_index); layout_event_update->addWidget(label_event_update_result); layout_event_update->addWidget(value_event_update_result); // Main layout layout_main->addLayout(layout_stats); layout_main->addLayout(layout_bhr); layout_main->addLayout(layout_event); layout_main->addSpacerItem(vertical_spacer); content->setLayout(layout_main); setWidget(content); ///////////////////////// // Init widget properties // Stats label_stats_total_text->setText("Jump/Branch count:"); label_stats_miss_text->setText("Misprediction count:"); label_stats_accuracy_text->setText("Total accuracy:"); clear_stats(); // BHR label_bhr->setText("Branch History Register:"); value_bhr->setReadOnly(true); value_bhr->setAlignment(Qt::AlignCenter); value_bhr->setFixedWidth(120); clear_bhr(); // Prediction label_event_predict_header->setText("Last prediction"); label_event_predict_header->setStyleSheet("font-weight: bold;"); label_event_predict_instruction->setText("Instruction:"); label_event_predict_address->setText("Instruction Address:"); label_event_predict_index_btb->setText("BTB index:"); label_event_predict_index_bht->setText("BHT index:"); label_event_predict_result->setText("Prediction result:"); value_event_predict_instruction->setReadOnly(true); value_event_predict_address->setReadOnly(true); value_event_predict_index_btb->setReadOnly(true); value_event_predict_index_bht->setReadOnly(true); value_event_predict_result->setReadOnly(true); value_event_predict_instruction->setAlignment(Qt::AlignCenter); value_event_predict_address->setAlignment(Qt::AlignCenter); value_event_predict_index_btb->setAlignment(Qt::AlignCenter); value_event_predict_index_bht->setAlignment(Qt::AlignCenter); value_event_predict_result->setAlignment(Qt::AlignCenter); set_predict_widget_color(STYLESHEET_COLOR_DEFAULT); clear_predict_widget(); // Update label_event_update_header->setText("Last update"); label_event_update_header->setStyleSheet("font-weight: bold;"); label_event_update_instruction->setText("Instruction:"); label_event_update_address->setText("Instruction Address:"); label_event_update_index_btb->setText("BTB index:"); label_event_update_index_bht->setText("BHT index:"); label_event_update_result->setText("Branch result:"); value_event_update_instruction->setReadOnly(true); value_event_update_address->setReadOnly(true); value_event_update_index_btb->setReadOnly(true); value_event_update_index_bht->setReadOnly(true); value_event_update_result->setReadOnly(true); value_event_update_instruction->setAlignment(Qt::AlignCenter); value_event_update_address->setAlignment(Qt::AlignCenter); value_event_update_index_btb->setAlignment(Qt::AlignCenter); value_event_update_index_bht->setAlignment(Qt::AlignCenter); value_event_update_result->setAlignment(Qt::AlignCenter); set_update_widget_color(STYLESHEET_COLOR_DEFAULT); clear_update_widget(); } void DockPredictorInfo::set_predict_widget_color(QString color_stylesheet) { value_event_predict_instruction->setStyleSheet(color_stylesheet); value_event_predict_address->setStyleSheet(color_stylesheet); value_event_predict_index_btb->setStyleSheet(color_stylesheet); if (is_predictor_dynamic) { value_event_predict_index_bht->setStyleSheet(color_stylesheet); } value_event_predict_result->setStyleSheet(color_stylesheet); } void DockPredictorInfo::set_update_widget_color(QString color_stylesheet) { value_event_update_instruction->setStyleSheet(color_stylesheet); value_event_update_address->setStyleSheet(color_stylesheet); value_event_update_index_btb->setStyleSheet(color_stylesheet); if (is_predictor_dynamic) { value_event_update_index_bht->setStyleSheet(color_stylesheet); } value_event_update_result->setStyleSheet(color_stylesheet); } void DockPredictorInfo::setup( const machine::BranchPredictor *branch_predictor, const machine::Core *core) { clear(); number_of_bhr_bits = branch_predictor->get_number_of_bhr_bits(); initial_state = branch_predictor->get_initial_state(); const machine::PredictorType predictor_type { branch_predictor->get_predictor_type() }; is_predictor_dynamic = machine::is_predictor_type_dynamic(predictor_type); is_predictor_enabled = branch_predictor->get_enabled(); if (is_predictor_enabled) { connect( branch_predictor, &machine::BranchPredictor::total_stats_updated, this, &DockPredictorInfo::update_stats); connect( branch_predictor, &machine::BranchPredictor::prediction_done, this, &DockPredictorInfo::show_new_prediction); connect(core, &machine::Core::step_started, this, &DockPredictorInfo::reset_colors); connect( branch_predictor, &machine::BranchPredictor::update_done, this, &DockPredictorInfo::show_new_update); if (is_predictor_dynamic) { connect( branch_predictor, &machine::BranchPredictor::bhr_updated, this, &DockPredictorInfo::update_bhr); } } // Toggle BHT index display if (is_predictor_dynamic) { label_event_predict_index_bht->setEnabled(true); value_event_predict_index_bht->setEnabled(true); label_event_update_index_bht->setEnabled(true); value_event_update_index_bht->setEnabled(true); } else { label_event_predict_index_bht->setEnabled(false); value_event_predict_index_bht->setEnabled(false); label_event_update_index_bht->setEnabled(false); value_event_update_index_bht->setEnabled(false); } // Toggle BHR display if (is_predictor_dynamic && number_of_bhr_bits > 0) { label_bhr->setEnabled(true); value_bhr->setEnabled(true); } else { label_bhr->setEnabled(false); value_bhr->setEnabled(false); } // Toggle whole widget if (is_predictor_enabled) { content->setDisabled(false); } else { content->setDisabled(true); } clear_bhr(); } void DockPredictorInfo::update_bhr(uint8_t number_of_bhr_bits, uint16_t register_value) { if (number_of_bhr_bits > 0) { QString binary_value, zero_padding; binary_value = QString::number(register_value, 2); zero_padding.fill('0', number_of_bhr_bits - binary_value.count()); value_bhr->setText("0b" + zero_padding + binary_value); } else { value_bhr->setText(""); } } void DockPredictorInfo::show_new_prediction( uint16_t btb_index, uint16_t bht_index, machine::PredictionInput input, machine::BranchResult result, machine::BranchType branch_type) { value_event_predict_instruction->setText(input.instruction.to_str()); value_event_predict_address->setText(addr_to_hex_str(input.instruction_address)); value_event_predict_index_btb->setText(QString::number(btb_index)); if (!is_predictor_dynamic) { value_event_predict_index_bht->setText(""); } else if (branch_type == machine::BranchType::BRANCH) { value_event_predict_index_bht->setText(QString::number(bht_index)); } else { value_event_predict_index_bht->setText("N/A"); } value_event_predict_result->setText(machine::branch_result_to_string(result).toString()); set_predict_widget_color(STYLESHEET_COLOR_PREDICT); } void DockPredictorInfo::show_new_update( uint16_t btb_index, uint16_t bht_index, machine::PredictionFeedback feedback) { value_event_update_instruction->setText(feedback.instruction.to_str()); value_event_update_address->setText(addr_to_hex_str(feedback.instruction_address)); value_event_update_index_btb->setText(QString::number(btb_index)); if (!is_predictor_dynamic) { value_event_update_index_bht->setText(""); } else if (feedback.branch_type == machine::BranchType::BRANCH) { value_event_update_index_bht->setText(QString::number(bht_index)); } else { value_event_update_index_bht->setText("N/A"); } value_event_update_result->setText( machine::branch_result_to_string(feedback.result).toString()); set_update_widget_color(STYLESHEET_COLOR_UPDATE); } void DockPredictorInfo::update_stats(machine::PredictionStatistics stats) { label_stats_total_value->setText(QString::number(stats.correct)); label_stats_miss_value->setText(QString::number(stats.wrong)); label_stats_accuracy_value->setText(QString::number(stats.accuracy) + " %"); } void DockPredictorInfo::reset_colors() { set_predict_widget_color(STYLESHEET_COLOR_DEFAULT); set_update_widget_color(STYLESHEET_COLOR_DEFAULT); } void DockPredictorInfo::clear_stats() { label_stats_total_value->setText("0"); label_stats_miss_value->setText("0"); label_stats_accuracy_value->setText("N/A"); } void DockPredictorInfo::clear_bhr() { if (number_of_bhr_bits > 0) { QString zero_padding; zero_padding.fill('0', number_of_bhr_bits); value_bhr->setText("0b" + zero_padding); } else { value_bhr->setText(""); } } void DockPredictorInfo::clear_predict_widget() { value_event_predict_instruction->setText(""); value_event_predict_address->setText(""); value_event_predict_index_btb->setText(""); value_event_predict_index_bht->setText(""); value_event_predict_result->setText(""); set_predict_widget_color(STYLESHEET_COLOR_DEFAULT); } void DockPredictorInfo::clear_update_widget() { value_event_update_instruction->setText(""); value_event_update_address->setText(""); value_event_update_index_btb->setText(""); value_event_update_index_bht->setText(""); value_event_update_result->setText(""); set_update_widget_color(STYLESHEET_COLOR_DEFAULT); } void DockPredictorInfo::clear() { clear_stats(); clear_bhr(); clear_predict_widget(); clear_update_widget(); } ================================================ FILE: src/gui/windows/predictor/predictor_info_dock.h ================================================ #ifndef PREDICTOR_INFO_DOCK_H #define PREDICTOR_INFO_DOCK_H #include "common/polyfills/qt5/qtableview.h" #include "machine/machine.h" #include "machine/memory/address.h" #include "machine/predictor.h" #include "machine/predictor_types.h" #include "ui/hexlineedit.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define STYLESHEET_COLOR_DEFAULT "background: rgb(255,255,255);" #define STYLESHEET_COLOR_PREDICT "background: rgb(255,173,173);" #define STYLESHEET_COLOR_UPDATE "background: rgb(173,255,229);" #define Q_COLOR_DEFAULT QColor(255, 255, 255) #define Q_COLOR_PREDICT QColor(255, 173, 173) #define Q_COLOR_UPDATE QColor(173, 255, 229) class DockPredictorInfo : public QDockWidget { Q_OBJECT using Super = QDockWidget; public: // Constructors & Destructor DockPredictorInfo(QWidget *parent); private: // Internal functions void set_predict_widget_color(QString color_stylesheet); void set_update_widget_color(QString color_stylesheet); public: // General functions void setup(const machine::BranchPredictor *branch_predictor, const machine::Core *core); public slots: void update_bhr(uint8_t number_of_bhr_bits, uint16_t register_value); void show_new_prediction( uint16_t btb_index, uint16_t bht_index, machine::PredictionInput input, machine::BranchResult result, machine::BranchType branch_type); void show_new_update(uint16_t btb_index, uint16_t bht_index, machine::PredictionFeedback feedback); void update_stats(machine::PredictionStatistics stats); void reset_colors(); void clear_stats(); void clear_bhr(); void clear_predict_widget(); void clear_update_widget(); void clear(); private: // Internal variables bool is_predictor_enabled { false }; bool is_predictor_dynamic { false }; uint8_t number_of_bhr_bits { 0 }; machine::PredictorState initial_state { machine::PredictorState::UNDEFINED }; QT_OWNED QGroupBox *content { new QGroupBox() }; QT_OWNED QVBoxLayout *layout_main { new QVBoxLayout() }; QT_OWNED QSpacerItem *vertical_spacer { new QSpacerItem(0, 0, QSizePolicy::Minimum, QSizePolicy::Expanding) }; // Stats QT_OWNED QGridLayout *layout_stats { new QGridLayout() }; QT_OWNED QLabel *label_stats_total_text { new QLabel() }; QT_OWNED QLabel *label_stats_miss_text { new QLabel() }; QT_OWNED QLabel *label_stats_accuracy_text { new QLabel() }; QT_OWNED QLabel *label_stats_total_value { new QLabel() }; QT_OWNED QLabel *label_stats_miss_value { new QLabel() }; QT_OWNED QLabel *label_stats_accuracy_value { new QLabel() }; // Prediction & Update QT_OWNED QHBoxLayout *layout_event { new QHBoxLayout() }; // Prediction QT_OWNED QGroupBox *group_event_predict { new QGroupBox() }; QT_OWNED QVBoxLayout *layout_event_predict { new QVBoxLayout() }; QT_OWNED QHBoxLayout *layout_event_predict_index { new QHBoxLayout() }; QT_OWNED QVBoxLayout *layout_event_predict_index_btb { new QVBoxLayout() }; QT_OWNED QVBoxLayout *layout_event_predict_index_bht { new QVBoxLayout() }; QT_OWNED QLabel *label_event_predict_header { new QLabel() }; QT_OWNED QLabel *label_event_predict_instruction { new QLabel() }; QT_OWNED QLabel *label_event_predict_address { new QLabel() }; QT_OWNED QLabel *label_event_predict_index_btb { new QLabel() }; QT_OWNED QLabel *label_event_predict_index_bht { new QLabel() }; QT_OWNED QLabel *label_event_predict_result { new QLabel() }; QT_OWNED QLineEdit *value_event_predict_instruction { new QLineEdit() }; QT_OWNED QLineEdit *value_event_predict_address { new QLineEdit() }; QT_OWNED QLineEdit *value_event_predict_index_btb { new QLineEdit() }; QT_OWNED QLineEdit *value_event_predict_index_bht { new QLineEdit() }; QT_OWNED QLineEdit *value_event_predict_result { new QLineEdit() }; // Update QT_OWNED QGroupBox *group_event_update { new QGroupBox() }; QT_OWNED QVBoxLayout *layout_event_update { new QVBoxLayout() }; QT_OWNED QHBoxLayout *layout_event_update_index { new QHBoxLayout() }; QT_OWNED QVBoxLayout *layout_event_update_index_btb { new QVBoxLayout() }; QT_OWNED QVBoxLayout *layout_event_update_index_bht { new QVBoxLayout() }; QT_OWNED QLabel *label_event_update_header { new QLabel() }; QT_OWNED QLabel *label_event_update_instruction { new QLabel() }; QT_OWNED QLabel *label_event_update_address { new QLabel() }; QT_OWNED QLabel *label_event_update_index_btb { new QLabel() }; QT_OWNED QLabel *label_event_update_index_bht { new QLabel() }; QT_OWNED QLabel *label_event_update_result { new QLabel() }; QT_OWNED QLineEdit *value_event_update_instruction { new QLineEdit() }; QT_OWNED QLineEdit *value_event_update_address { new QLineEdit() }; QT_OWNED QLineEdit *value_event_update_index_btb { new QLineEdit() }; QT_OWNED QLineEdit *value_event_update_index_bht { new QLineEdit() }; QT_OWNED QLineEdit *value_event_update_result { new QLineEdit() }; // BHR QT_OWNED QHBoxLayout *layout_bhr { new QHBoxLayout() }; QT_OWNED QLabel *label_bhr { new QLabel() }; QT_OWNED QLineEdit *value_bhr { new QLineEdit() }; }; #endif // PREDICTOR_INFO_DOCK_H ================================================ FILE: src/gui/windows/program/programdock.cpp ================================================ #include "programdock.h" #include "helper/async_modal.h" #include "programmodel.h" #include "programtableview.h" #include "ui/hexlineedit.h" #include #include #include #include #include ProgramDock::ProgramDock(QWidget *parent, QSettings *settings) : Super(parent) { setObjectName("Program"); setWindowTitle("Program"); this->settings = settings; follow_source = (enum FollowSource)settings->value("ProgramViewFollowSource", FOLLOWSRC_FETCH).toInt(); for (auto &i : follow_addr) { i = machine::Address::null(); } auto *content = new QWidget(); auto *follow_inst = new QComboBox(); follow_inst->addItem("Follow none"); follow_inst->addItem("Follow fetch"); follow_inst->addItem("Follow decode"); follow_inst->addItem("Follow execute"); follow_inst->addItem("Follow memory"); follow_inst->addItem("Follow writeback"); follow_inst->setCurrentIndex((int)follow_source); auto *program_content = new ProgramTableView(nullptr, settings); // program_content->setSizePolicy(); auto *program_model = new ProgramModel(this); program_content->setModel(program_model); program_content->verticalHeader()->hide(); // program_content->setHorizontalHeader(program_model->); auto *go_edit = new HexLineEdit(nullptr, 8, 16, "0x"); auto *layout = new QVBoxLayout; layout->addWidget(follow_inst); layout->addWidget(program_content); layout->addWidget(go_edit); content->setLayout(layout); setWidget(content); connect(this, &ProgramDock::machine_setup, program_model, &ProgramModel::setup); connect( go_edit, &HexLineEdit::value_edit_finished, program_content, [program_content](uint32_t value) { program_content->go_to_address(machine::Address(value)); }); connect(program_content, &ProgramTableView::address_changed, go_edit, &HexLineEdit::set_value); connect(this, &ProgramDock::jump_to_pc, program_content, &ProgramTableView::go_to_address); connect( follow_inst, QOverload::of(&QComboBox::currentIndexChanged), this, &ProgramDock::set_follow_inst); connect(this, &ProgramDock::focus_addr, program_content, &ProgramTableView::focus_address); connect( this, &ProgramDock::focus_addr_with_save, program_content, &ProgramTableView::focus_address_with_save); connect( program_content, &QAbstractItemView::doubleClicked, program_model, &ProgramModel::toggle_hw_break); connect( this, &ProgramDock::stage_addr_changed, program_model, &ProgramModel::update_stage_addr); connect(program_model, &ProgramModel::report_error, this, &ProgramDock::report_error); connect(this, &ProgramDock::request_update_all, program_model, &ProgramModel::update_all); } void ProgramDock::setup(machine::Machine *machine) { machine::Address pc; emit machine_setup(machine); if (machine == nullptr) { return; } pipeline_handle = &machine->core()->get_state(); pc = machine->registers()->read_pc(); for (machine::Address &address : follow_addr) { address = pc; } update_follow_position(); } void ProgramDock::showEvent(QShowEvent *event) { QDockWidget::showEvent(event); update_follow_position(); if (pipeline_handle != nullptr) { update_pipeline_addrs(*pipeline_handle); } } void ProgramDock::set_follow_inst(int follow) { follow_source = (enum FollowSource)follow; settings->setValue("ProgramViewFollowSource", (int)follow_source); update_follow_position(); } void ProgramDock::update_pipeline_addrs(const machine::CoreState &s) { if (isHidden()) { return; } const machine::Pipeline &p = s.pipeline; fetch_inst_addr(p.fetch.result.inst_addr); decode_inst_addr(p.decode.result.inst_addr); execute_inst_addr(p.execute.result.inst_addr); memory_inst_addr(p.memory.result.inst_addr); writeback_inst_addr(p.writeback.internal.inst_addr); } void ProgramDock::fetch_inst_addr(machine::Address addr) { if (addr != machine::STAGEADDR_NONE) { follow_addr[FOLLOWSRC_FETCH] = addr; } emit stage_addr_changed(ProgramModel::STAGEADDR_FETCH, addr); if (follow_source == FOLLOWSRC_FETCH) { update_follow_position(); } } void ProgramDock::decode_inst_addr(machine::Address addr) { if (addr != machine::STAGEADDR_NONE) { follow_addr[FOLLOWSRC_DECODE] = addr; } emit stage_addr_changed(ProgramModel::STAGEADDR_DECODE, addr); if (follow_source == FOLLOWSRC_DECODE) { update_follow_position(); } } void ProgramDock::execute_inst_addr(machine::Address addr) { if (addr != machine::STAGEADDR_NONE) { follow_addr[FOLLOWSRC_EXECUTE] = addr; } emit stage_addr_changed(ProgramModel::STAGEADDR_EXECUTE, addr); if (follow_source == FOLLOWSRC_EXECUTE) { update_follow_position(); } } void ProgramDock::memory_inst_addr(machine::Address addr) { if (addr != machine::STAGEADDR_NONE) { follow_addr[FOLLOWSRC_MEMORY] = addr; } emit stage_addr_changed(ProgramModel::STAGEADDR_MEMORY, addr); if (follow_source == FOLLOWSRC_MEMORY) { update_follow_position(); } } void ProgramDock::writeback_inst_addr(machine::Address addr) { if (addr != machine::STAGEADDR_NONE) { follow_addr[FOLLOWSRC_WRITEBACK] = addr; } emit stage_addr_changed(ProgramModel::STAGEADDR_WRITEBACK, addr); if (follow_source == FOLLOWSRC_WRITEBACK) { update_follow_position(); } } void ProgramDock::update_follow_position() { if (follow_source != FOLLOWSRC_NONE) { emit focus_addr(follow_addr[follow_source]); } } void ProgramDock::report_error(const QString &error) { showAsyncMessageBox(this, QMessageBox::Critical, "Simulator Error", error); } ================================================ FILE: src/gui/windows/program/programdock.h ================================================ #ifndef PROGRAMDOCK_H #define PROGRAMDOCK_H #include "machine/machine.h" #include "windows/peripherals/peripheralsview.h" #include #include #include class ProgramDock : public QDockWidget { Q_OBJECT using Super = QDockWidget; public: ProgramDock(QWidget *parent, QSettings *settings); void setup(machine::Machine *machine); void showEvent(QShowEvent *event) override; signals: void machine_setup(machine::Machine *machine); void jump_to_pc(machine::Address); void focus_addr(machine::Address); void focus_addr_with_save(machine::Address); void stage_addr_changed(uint stage, machine::Address addr); void request_update_all(); public slots: void set_follow_inst(int); void fetch_inst_addr(machine::Address addr); void decode_inst_addr(machine::Address addr); void execute_inst_addr(machine::Address addr); void memory_inst_addr(machine::Address addr); void writeback_inst_addr(machine::Address addr); void report_error(const QString &error); void update_pipeline_addrs(const machine::CoreState &p); private: enum FollowSource { FOLLOWSRC_NONE, FOLLOWSRC_FETCH, FOLLOWSRC_DECODE, FOLLOWSRC_EXECUTE, FOLLOWSRC_MEMORY, FOLLOWSRC_WRITEBACK, FOLLOWSRC_COUNT, }; void update_follow_position(); enum FollowSource follow_source; machine::Address follow_addr[FOLLOWSRC_COUNT] {}; QSettings *settings; const machine::CoreState *pipeline_handle = nullptr; }; #endif // PROGRAMDOCK_H ================================================ FILE: src/gui/windows/program/programmodel.cpp ================================================ #include "programmodel.h" #include using ae = machine::AccessEffects; // For enum values, the type is obvious from context. ProgramModel::ProgramModel(QObject *parent) : Super(parent), data_font("Monospace") { index0_offset = machine::Address::null(); data_font.setStyleHint(QFont::TypeWriter); machine = nullptr; memory_change_counter = 0; cache_program_change_counter = 0; for (auto &i : stage_addr) { i = machine::STAGEADDR_NONE; } stages_need_update = false; } const machine::FrontendMemory *ProgramModel::mem_access() const { if (machine == nullptr) { return nullptr; } if (machine->memory_data_bus() != nullptr) { return machine->memory_data_bus(); } throw std::logic_error("Use of backend memory in frontend."); // TODO // return machine->memory(); } machine::FrontendMemory *ProgramModel::mem_access_rw() const { if (machine == nullptr) { return nullptr; } if (machine->memory_data_bus_rw() != nullptr) { return machine->memory_data_bus_rw(); } throw std::logic_error("Use of backend memory in frontend."); // TODO // return machine->memory_rw(); } int ProgramModel::rowCount(const QModelIndex & /*parent*/) const { return 750; } int ProgramModel::columnCount(const QModelIndex & /*parent*/) const { return 4; } QVariant ProgramModel::headerData(int section, Qt::Orientation orientation, int role) const { if (orientation == Qt::Horizontal) { if (role == Qt::DisplayRole) { switch (section) { case 0: return tr("BP"); case 1: return tr("Address"); case 2: return tr("Code"); case 3: return tr("Instruction"); default: return tr(""); } } } return Super::headerData(section, orientation, role); } QVariant ProgramModel::data(const QModelIndex &index, int role) const { const machine::FrontendMemory *mem; if (role == Qt::DisplayRole || role == Qt::EditRole) { QString s, t; machine::Address address; if (!get_row_address(address, index.row())) { return QString(""); } if (index.column() == 1) { t = QString::number(address.get_raw(), 16); s.fill('0', 8 - t.count()); return { "0x" + s + t }; } mem = mem_access(); if (mem == nullptr) { return QString(" "); } machine::Instruction inst(mem->read_u32(address)); switch (index.column()) { case 0: if (machine->is_hwbreak(address)) { return QString("B"); } else { return QString(" "); } case 2: t = QString::number(inst.data(), 16); s.fill('0', 8 - t.count()); return { s + t }; case 3: return inst.to_str(address); default: return tr(""); } } if (role == Qt::BackgroundRole) { machine::Address address; if (!get_row_address(address, index.row()) || machine == nullptr) { return {}; } if (index.column() == 2 && machine->cache_program() != nullptr) { machine::LocationStatus loc_stat; loc_stat = machine->cache_program()->location_status(address); if (loc_stat & machine::LOCSTAT_CACHED) { QBrush bgd(Qt::lightGray); return bgd; } } else if (index.column() == 0 && machine->is_hwbreak(address)) { QBrush bgd(Qt::red); return bgd; } else if (index.column() == 3) { if (address == stage_addr[STAGEADDR_WRITEBACK]) { QBrush bgd(QColor(255, 173, 230)); return bgd; } else if (address == stage_addr[STAGEADDR_MEMORY]) { QBrush bgd(QColor(173, 255, 229)); return bgd; } else if (address == stage_addr[STAGEADDR_EXECUTE]) { QBrush bgd(QColor(193, 255, 173)); return bgd; } else if (address == stage_addr[STAGEADDR_DECODE]) { QBrush bgd(QColor(255, 212, 173)); return bgd; } else if (address == stage_addr[STAGEADDR_FETCH]) { QBrush bgd(QColor(255, 173, 173)); return bgd; } } return {}; } if (role == Qt::FontRole) { return data_font; } if (role == Qt::TextAlignmentRole) { if (index.column() == 0) { return Qt::AlignCenter; } return Qt::AlignLeft; } return {}; } void ProgramModel::setup(machine::Machine *machine) { this->machine = machine; for (auto &i : stage_addr) { i = machine::STAGEADDR_NONE; } if (machine != nullptr) { connect(machine, &machine::Machine::post_tick, this, &ProgramModel::check_for_updates); } if (mem_access() != nullptr) { connect( mem_access(), &machine::FrontendMemory::external_change_notify, this, &ProgramModel::check_for_updates); } emit update_all(); } void ProgramModel::update_all() { const machine::FrontendMemory *mem; mem = mem_access(); if (mem != nullptr) { memory_change_counter = mem->get_change_counter(); if (machine->cache_program() != nullptr) { cache_program_change_counter = machine->cache_program()->get_change_counter(); } } stages_need_update = false; emit dataChanged(index(0, 0), index(rowCount() - 1, columnCount() - 1)); } void ProgramModel::check_for_updates() { bool need_update = stages_need_update; const machine::FrontendMemory *mem; mem = mem_access(); if (mem == nullptr) { return; } if (memory_change_counter != mem->get_change_counter()) { need_update = true; } if (machine->cache_data() != nullptr) { if (cache_program_change_counter != machine->cache_program()->get_change_counter()) { need_update = true; } } if (!need_update) { return; } update_all(); } bool ProgramModel::adjustRowAndOffset(int &row, machine::Address address) { row = rowCount() / 2; address -= address.get_raw() % cellSizeBytes(); uint32_t row_bytes = cellSizeBytes(); uint32_t diff = row * row_bytes; if (diff > address.get_raw()) { row = address.get_raw() / row_bytes; if (row == 0) { index0_offset = machine::Address::null(); } else { index0_offset = address - row * row_bytes; } } else { index0_offset = address - diff; } return get_row_for_address(row, address); } void ProgramModel::toggle_hw_break(const QModelIndex &index) { machine::Address address; if (index.column() != 0 || machine == nullptr) { return; } if (!get_row_address(address, index.row())) { return; } if (machine->is_hwbreak(address)) { machine->remove_hwbreak(address); } else { machine->insert_hwbreak(address); } update_all(); } Qt::ItemFlags ProgramModel::flags(const QModelIndex &index) const { if (index.column() != 2 && index.column() != 3) { return QAbstractTableModel::flags(index); } else { return QAbstractTableModel::flags(index) | Qt::ItemIsEditable; } } bool ProgramModel::setData(const QModelIndex &index, const QVariant &value, int role) { if (role == Qt::EditRole) { bool ok; QString error; machine::Address address; uint32_t data; machine::FrontendMemory *mem; if (!get_row_address(address, index.row())) { return false; } if (index.column() == 0 || machine == nullptr) { return false; } mem = mem_access_rw(); if (mem == nullptr) { return false; } switch (index.column()) { case 2: data = value.toString().toULong(&ok, 16); if (!ok) { return false; } mem->write_u32(address, data, ae::INTERNAL); break; case 3: try { machine::Instruction::code_from_string(&data, 4, value.toString(), address); } catch (machine::Instruction::ParseError &e) { emit report_error(tr("instruction 1 parse error - %2.").arg(e.message)); return false; } mem->write_u32(address, data, ae::INTERNAL); break; default: return false; } } return true; } void ProgramModel::update_stage_addr(uint stage, machine::Address addr) { if (stage < STAGEADDR_COUNT) { if (stage_addr[stage] != addr) { stage_addr[stage] = addr; stages_need_update = true; } } } ================================================ FILE: src/gui/windows/program/programmodel.h ================================================ #ifndef PROGRAMMODEL_H #define PROGRAMMODEL_H #include "machine/machine.h" #include #include class ProgramModel : public QAbstractTableModel { Q_OBJECT using Super = QAbstractTableModel; public: explicit ProgramModel(QObject *parent); [[nodiscard]] int rowCount(const QModelIndex &parent = QModelIndex()) const override; [[nodiscard]] int columnCount(const QModelIndex &parent = QModelIndex()) const override; [[nodiscard]] QVariant headerData(int section, Qt::Orientation orientation, int role) const override; [[nodiscard]] QVariant data(const QModelIndex &index, int role = Qt::DisplayRole) const override; [[nodiscard]] Qt::ItemFlags flags(const QModelIndex &index) const override; bool setData(const QModelIndex &index, const QVariant &value, int role) override; bool adjustRowAndOffset(int &row, machine::Address address); [[nodiscard]] inline const QFont *getFont() const { return &data_font; } [[nodiscard]] inline machine::Address getIndex0Offset() const { return index0_offset; } [[nodiscard]] static inline unsigned int cellSizeBytes() { return 4; } inline bool get_row_address(machine::Address &address, int row) const { address = index0_offset + row * cellSizeBytes(); return address >= index0_offset; } inline bool get_row_for_address(int &row, machine::Address address) const { if (address < index0_offset) { row = -1; return false; } row = (address - index0_offset) / cellSizeBytes(); if (((address - index0_offset) > 0x80000000) || row > rowCount()) { row = rowCount(); return false; } return true; } enum StageAddress { STAGEADDR_FETCH, STAGEADDR_DECODE, STAGEADDR_EXECUTE, STAGEADDR_MEMORY, STAGEADDR_WRITEBACK, STAGEADDR_COUNT, }; signals: void report_error(QString error); public slots: void setup(machine::Machine *machine); void check_for_updates(); void toggle_hw_break(const QModelIndex &index); void update_stage_addr(uint stage, machine::Address addr); void update_all(); private: [[nodiscard]] const machine::FrontendMemory *mem_access() const; [[nodiscard]] machine::FrontendMemory *mem_access_rw() const; machine::Address index0_offset; QFont data_font; machine::Machine *machine; uint32_t memory_change_counter; uint32_t cache_program_change_counter; machine::Address stage_addr[STAGEADDR_COUNT] {}; bool stages_need_update; }; #endif // PROGRAMMODEL_H ================================================ FILE: src/gui/windows/program/programtableview.cpp ================================================ #include "programtableview.h" #include "hinttabledelegate.h" #include "programmodel.h" #include #include #include #include #include #include ProgramTableView::ProgramTableView(QWidget *parent, QSettings *settings) : Super(parent) { setItemDelegate(new HintTableDelegate(this)); connect( verticalScrollBar(), &QAbstractSlider::valueChanged, this, &ProgramTableView::adjust_scroll_pos_check); connect( this, &ProgramTableView::adjust_scroll_pos_queue, this, &ProgramTableView::adjust_scroll_pos_process, Qt::QueuedConnection); this->settings = settings; initial_address = machine::Address(settings->value("ProgramViewAddr0", 0).toULongLong()); adjust_scroll_pos_in_progress = false; need_addr0_save = false; setTextElideMode(Qt::ElideNone); } void ProgramTableView::addr0_save_change(machine::Address val) { need_addr0_save = false; settings->setValue("ProgramViewAddr0", qint64(val.get_raw())); } void ProgramTableView::adjustColumnCount() { QModelIndex idx; auto *m = dynamic_cast(model()); if (m == nullptr) { return; } auto *delegate = dynamic_cast(itemDelegate()); if (delegate == nullptr) { return; } QStyleOptionViewItem viewOpts; initViewItemOption(&viewOpts); int totwidth = 0; idx = m->index(0, 0); auto cwidth_dh0 = delegate->sizeHintForText(viewOpts, idx, "BP").width() + 2; horizontalHeader()->setSectionResizeMode(0, QHeaderView::Fixed); horizontalHeader()->resizeSection(0, cwidth_dh0); totwidth += cwidth_dh0; idx = m->index(0, 1); auto cwidth_dh1 = delegate->sizeHintForText(viewOpts, idx, "0x00000000").width() + 2; horizontalHeader()->setSectionResizeMode(1, QHeaderView::Fixed); horizontalHeader()->resizeSection(1, cwidth_dh1); totwidth += cwidth_dh1; idx = m->index(0, 2); auto cwidth_dh2 = delegate->sizeHintForText(viewOpts, idx, "00000000").width() + 2; horizontalHeader()->setSectionResizeMode(2, QHeaderView::Fixed); horizontalHeader()->resizeSection(2, cwidth_dh2); totwidth += cwidth_dh2; horizontalHeader()->setSectionResizeMode(3, QHeaderView::Stretch); idx = m->index(0, 3); totwidth += delegate->sizeHintForText(viewOpts, idx, "BEQ $18, $17, 0x00000258").width() + 2; totwidth += verticalHeader()->width(); setColumnHidden(2, totwidth > width()); setColumnHidden(1, totwidth - cwidth_dh2 > width()); setColumnHidden(0, totwidth - cwidth_dh2 - cwidth_dh1 > width()); if (!initial_address.is_null()) { go_to_address(initial_address); initial_address = machine::Address::null(); } } void ProgramTableView::adjust_scroll_pos_check() { if (!adjust_scroll_pos_in_progress) { adjust_scroll_pos_in_progress = true; emit adjust_scroll_pos_queue(); } } void ProgramTableView::adjust_scroll_pos_process() { adjust_scroll_pos_in_progress = false; machine::Address address; auto *m = dynamic_cast(model()); if (m == nullptr) { return; } QModelIndex prev_index = currentIndex(); auto row_bytes = machine::Address(ProgramModel::cellSizeBytes()); machine::Address index0_offset = m->getIndex0Offset(); do { int row = rowAt(0); int prev_row = row; if (row < m->rowCount() / 8) { if ((row == 0) && (index0_offset < row_bytes) && (!index0_offset.is_null())) { m->adjustRowAndOffset(row, machine::Address::null()); } else if (index0_offset >= row_bytes) { m->get_row_address(address, row); m->adjustRowAndOffset(row, address); } else { break; } } else if (row > m->rowCount() - m->rowCount() / 8) { m->get_row_address(address, row); m->adjustRowAndOffset(row, address); } else { break; } scrollTo(m->index(row, 0), QAbstractItemView::PositionAtTop); setCurrentIndex(m->index(prev_index.row() + row - prev_row, prev_index.column())); emit m->update_all(); } while (false); m->get_row_address(address, rowAt(0)); if (need_addr0_save) { addr0_save_change(address); } emit address_changed(address.get_raw()); } void ProgramTableView::resizeEvent(QResizeEvent *event) { auto *m = dynamic_cast(model()); machine::Address address; bool keep_row0 = false; if (m != nullptr) { if (initial_address.is_null()) { keep_row0 = m->get_row_address(address, rowAt(0)); } else { address = initial_address; } } Super::resizeEvent(event); adjustColumnCount(); if (keep_row0) { initial_address = machine::Address::null(); go_to_address(address); } } void ProgramTableView::go_to_address_priv(machine::Address address) { auto *m = dynamic_cast(model()); int row; if (m == nullptr) { return; } m->adjustRowAndOffset(row, address); scrollTo(m->index(row, 0), QAbstractItemView::PositionAtTop); setCurrentIndex(m->index(row, 1)); if (need_addr0_save) { addr0_save_change(address); } emit m->update_all(); } void ProgramTableView::go_to_address(machine::Address address) { need_addr0_save = true; go_to_address_priv(address); } void ProgramTableView::focus_address(machine::Address address) { int row; auto *m = dynamic_cast(model()); if (m == nullptr) { return; } if (!m->get_row_for_address(row, address)) { go_to_address_priv(address); } if (!m->get_row_for_address(row, address)) { return; } setCurrentIndex(m->index(row, 3)); } void ProgramTableView::focus_address_with_save(machine::Address address) { need_addr0_save = true; focus_address(address); } void ProgramTableView::keyPressEvent(QKeyEvent *event) { if (event->matches(QKeySequence::Copy)) { QString text; QItemSelectionRange range = selectionModel()->selection().first(); for (auto i = range.top(); i <= range.bottom(); ++i) { QStringList rowContents; for (auto j = range.left(); j <= range.right(); ++j) { rowContents << model()->index(i, j).data().toString(); } text += rowContents.join("\t"); text += "\n"; } QApplication::clipboard()->setText(text); } else { Super::keyPressEvent(event); } } ================================================ FILE: src/gui/windows/program/programtableview.h ================================================ #ifndef PROGRAMTABLEVIEW_H #define PROGRAMTABLEVIEW_H #include "common/polyfills/qt5/qtableview.h" #include "machine/memory/address.h" #include #include #include class ProgramTableView : public Poly_QTableView { Q_OBJECT using Super = Poly_QTableView; public: ProgramTableView(QWidget *parent, QSettings *settings); void resizeEvent(QResizeEvent *event) override; signals: void address_changed(uint32_t address); void adjust_scroll_pos_queue(); public slots: void go_to_address(machine::Address address); void focus_address(machine::Address address); void focus_address_with_save(machine::Address address); protected: void keyPressEvent(QKeyEvent *event) override; private slots: void adjust_scroll_pos_check(); void adjust_scroll_pos_process(); private: void go_to_address_priv(machine::Address address); void addr0_save_change(machine::Address val); void adjustColumnCount(); QSettings *settings; machine::Address initial_address; bool adjust_scroll_pos_in_progress; bool need_addr0_save; }; #endif // PROGRAMTABLEVIEW_H ================================================ FILE: src/gui/windows/registers/registersdock.cpp ================================================ #include "registersdock.h" #include "machine/instruction.h" RegistersDock::RegistersDock(QWidget *parent, machine::Xlen xlen) : QDockWidget(parent) , xlen(xlen) , scroll_area(new QScrollArea(this)) , table_widget(new StaticTable(scroll_area.data())) , pal_normal(createPalette(QColor(0, 0, 0))) , pal_updated(createPalette(QColor(240, 0, 0))) , pal_read(createPalette(QColor(0, 0, 240))) { scroll_area->setWidgetResizable(true); gp_highlighted.reset(); for (size_t i = 0; i < gp.size(); i++) { gp[i] = addRegisterLabel(QString("x%1/%2").arg(i).arg(machine::Rv_regnames[i])); } pc = addRegisterLabel("pc"); scroll_area->setWidget(table_widget.data()); setWidget(scroll_area.data()); setObjectName("Registers"); setWindowTitle("Registers"); } const char *RegistersDock::sizeHintText() { if (xlen == machine::Xlen::_64) return "0x0000000000000000"; else return "0x00000000"; } QLabel *RegistersDock::addRegisterLabel(const QString &title) { auto *data_label = new QLabel(sizeHintText(), table_widget.data()); data_label->setTextFormat(Qt::PlainText); data_label->setFixedSize(data_label->sizeHint()); data_label->setText(""); data_label->setPalette(pal_normal); data_label->setTextInteractionFlags(Qt::TextSelectableByMouse); auto *title_label = new QLabel(title, table_widget.data()); title_label->setPalette(pal_normal); // Add row take ownership of the labels. table_widget->addRow({ OWNED title_label, OWNED data_label }); return BORROWED data_label; } void RegistersDock::connectToMachine(machine::Machine *machine) { if (machine == nullptr) { // Reset data pc->setText(""); for (auto &i : gp) { i->setText(""); } return; } regs_handle = machine->registers(); // if xlen changes adjust space to show full value if (xlen != machine->config().get_simulated_xlen()) { xlen = machine->config().get_simulated_xlen(); auto *dumy_data_label = new QLabel(sizeHintText(), table_widget.data()); for (auto &i : gp) { i->setFixedSize(dumy_data_label->sizeHint()); } pc->setFixedSize(dumy_data_label->sizeHint()); delete dumy_data_label; } if (regs_handle == nullptr) { return; } reload(); connect(regs_handle, &machine::Registers::pc_update, this, &RegistersDock::pc_changed); connect(regs_handle, &machine::Registers::gp_update, this, &RegistersDock::gp_changed); connect(regs_handle, &machine::Registers::gp_read, this, &RegistersDock::gp_read); connect(machine, &machine::Machine::tick, this, &RegistersDock::clear_highlights); } void RegistersDock::showEvent(QShowEvent *event) { reload(); QDockWidget::showEvent(event); } void RegistersDock::pc_changed(machine::Address val) { if (isHidden()) { return; } setRegisterValueToLabel(pc, val.get_raw()); } void RegistersDock::gp_changed(machine::RegisterId i, machine::RegisterValue val) { if (isHidden()) { return; } setRegisterValueToLabel(gp[i], val); gp[i]->setPalette(pal_updated); gp_highlighted[i] = true; } void RegistersDock::gp_read(machine::RegisterId i, machine::RegisterValue val) { Q_UNUSED(val) if (isHidden()) { return; } if (!(gp_highlighted[i])) { gp[i]->setPalette(pal_read); gp_highlighted[i] = true; } } void RegistersDock::clear_highlights() { if (gp_highlighted.any()) { for (size_t i = 0; i < gp.size(); i++) { if (gp_highlighted[i]) { gp[i]->setPalette(pal_normal); } } } gp_highlighted.reset(); } void RegistersDock::reload() { if (regs_handle == nullptr) { return; } setRegisterValueToLabel(pc, regs_handle->read_pc().get_raw()); for (size_t i = 0; i < gp.size(); i++) { setRegisterValueToLabel(gp[i], regs_handle->read_gp_internal(i)); } clear_highlights(); } void RegistersDock::setRegisterValueToLabel(QLabel *label, machine::RegisterValue value) { label->setText(QString("0x%1").arg(value.as_xlen(xlen), 0, 16)); } QPalette RegistersDock::createPalette(const QColor &color) const { QPalette palette = this->palette(); palette.setColor(QPalette::WindowText, color); return palette; } ================================================ FILE: src/gui/windows/registers/registersdock.h ================================================ #ifndef REGISTERSDOCK_H #define REGISTERSDOCK_H #include "machine/machine.h" #include "statictable.h" #include #include #include #include #include using std::array; using std::bitset; /** * NOTE: RV64 ready */ class RegistersDock final : public QDockWidget { Q_OBJECT public: explicit RegistersDock(QWidget *parent, machine::Xlen xlen); void connectToMachine(machine::Machine *machine); void showEvent(QShowEvent *event) override; private slots: void pc_changed(machine::Address val); void gp_changed(machine::RegisterId i, machine::RegisterValue val); void gp_read(machine::RegisterId i, machine::RegisterValue val); void clear_highlights(); private: // Do full update of all registers. Clear all highlights. void reload(); machine::Xlen xlen; // Used for batch updates when registers are shown. const machine::Registers *regs_handle {}; const char *sizeHintText(); Box scroll_area; Box table_widget; BORROWED QLabel *pc {}; array gp {}; bitset gp_highlighted { false }; QPalette pal_normal; QPalette pal_updated; QPalette pal_read; private: void setRegisterValueToLabel(QLabel *label, machine::RegisterValue value); BORROWED QLabel *addRegisterLabel(const QString &title); [[nodiscard]] QPalette createPalette(const QColor &color) const; }; #endif // REGISTERSDOCK_H ================================================ FILE: src/gui/windows/terminal/terminaldock.cpp ================================================ #include "terminaldock.h" #include "machine/memory/backend/serialport.h" #include #include #include TerminalDock::TerminalDock(QWidget *parent, QSettings *settings) : QDockWidget(parent) { (void)settings; top_widget = new QWidget(this); setWidget(top_widget); layout_box = new QVBoxLayout(top_widget); terminal_text = new QTextEdit(top_widget); terminal_text->setMinimumSize(30, 30); layout_box->addWidget(terminal_text); append_cursor.reset(new QTextCursor(terminal_text->document())); layout_bottom_box = new QHBoxLayout(); layout_bottom_box->addWidget(new QLabel("Input:")); input_edit = new QLineEdit(); layout_bottom_box->addWidget(input_edit); layout_box->addLayout(layout_bottom_box); // insert newline on enter (it will be displayed as space) connect(input_edit, &QLineEdit::returnPressed, [this]() { input_edit->setText(input_edit->text() + '\n'); }); setObjectName("Terminal"); setWindowTitle("Terminal"); } void TerminalDock::setup(machine::SerialPort *ser_port) { if (ser_port == nullptr) { return; } connect( ser_port, &machine::SerialPort::tx_byte, this, QOverload::of(&TerminalDock::tx_byte)); connect(ser_port, &machine::SerialPort::rx_byte_pool, this, &TerminalDock::rx_byte_pool); connect(input_edit, &QLineEdit::textChanged, ser_port, &machine::SerialPort::rx_queue_check); } void TerminalDock::tx_byte(unsigned int data) { bool at_end = terminal_text->textCursor().atEnd(); if (data == '\n') { append_cursor->insertBlock(); } else { append_cursor->insertText(QString(QChar(data))); } if (at_end) { QTextCursor cursor = QTextCursor(terminal_text->document()); cursor.movePosition(QTextCursor::End); terminal_text->setTextCursor(cursor); } } void TerminalDock::tx_byte(int fd, unsigned int data) { (void)fd; tx_byte(data); } void TerminalDock::rx_byte_pool(int fd, unsigned int &data, bool &available) { (void)fd; QString str = input_edit->text(); available = false; if (str.count() > 0) { data = str[0].toLatin1(); input_edit->setText(str.remove(0, 1)); available = true; } } ================================================ FILE: src/gui/windows/terminal/terminaldock.h ================================================ #ifndef TERMINALDOCK_H #define TERMINALDOCK_H #include "machine/machine.h" #include #include #include #include #include #include class TerminalDock : public QDockWidget { Q_OBJECT public: TerminalDock(QWidget *parent, QSettings *settings); void setup(machine::SerialPort *ser_port); public slots: void tx_byte(unsigned int data); void tx_byte(int fd, unsigned int data); void rx_byte_pool(int fd, unsigned int &data, bool &available); private: QVBoxLayout *layout_box; QHBoxLayout *layout_bottom_box; QWidget *top_widget, *top_form {}; QFormLayout *layout_top_form {}; QTextEdit *terminal_text; Box append_cursor; QLineEdit *input_edit; }; #endif // TERMINALDOCK_H ================================================ FILE: src/gui/windows/tlb/tlbdock.cpp ================================================ #include "tlbdock.h" TLBDock::TLBDock(QWidget *parent, const QString &type) : QDockWidget(parent) , tlbscene(nullptr) , connected_tlb(nullptr) { top_widget = new QWidget(this); setWidget(top_widget); layout_box = new QVBoxLayout(top_widget); top_form = new QWidget(top_widget); top_form->setVisible(false); layout_box->addWidget(top_form); layout_top_form = new QFormLayout(top_form); l_hit = new QLabel("0", top_form); l_hit->setTextFormat(Qt::PlainText); layout_top_form->addRow("Hit:", l_hit); l_miss = new QLabel("0", top_form); l_miss->setTextFormat(Qt::PlainText); layout_top_form->addRow("Miss:", l_miss); l_m_reads = new QLabel("0", top_form); l_m_reads->setTextFormat(Qt::PlainText); layout_top_form->addRow("Memory reads:", l_m_reads); l_m_writes = new QLabel("0", top_form); l_m_writes->setTextFormat(Qt::PlainText); layout_top_form->addRow("Memory writes:", l_m_writes); l_stalled = new QLabel("0", top_form); l_stalled->setTextFormat(Qt::PlainText); layout_top_form->addRow("Memory stall cycles:", l_stalled); l_hit_rate = new QLabel("0.000%", top_form); l_hit_rate->setTextFormat(Qt::PlainText); layout_top_form->addRow("Hit rate:", l_hit_rate); l_speed = new QLabel("100%", top_form); l_speed->setTextFormat(Qt::PlainText); layout_top_form->addRow("Improved speed:", l_speed); graphicsview = new GraphicsView(top_widget); graphicsview->setVisible(false); layout_box->addWidget(graphicsview); tlbscene = nullptr; no_tlb = new QLabel("No " + type + " TLB configured", top_widget); layout_box->addWidget(no_tlb); setObjectName(type + "TLB"); setWindowTitle(type + " TLB"); } void TLBDock::setup(machine::TLB *tlb) { memory_reads = 0; memory_writes = 0; hit = 0; miss = 0; stalled = 0; speed_improv = 0.0; hit_rate = 0.0; l_hit->setText("0"); l_miss->setText("0"); l_stalled->setText("0"); l_m_reads->setText("0"); l_m_writes->setText("0"); l_hit_rate->setText("0.000%"); l_speed->setText("100%"); if (tlb != nullptr) { connect(tlb, &machine::TLB::hit_update, this, &TLBDock::hit_update, Qt::UniqueConnection); connect(tlb, &machine::TLB::miss_update, this, &TLBDock::miss_update, Qt::UniqueConnection); connect( tlb, &machine::TLB::statistics_update, this, &TLBDock::statistics_update, Qt::UniqueConnection); connect( tlb, &machine::TLB::memory_reads_update, this, &TLBDock::memory_reads_update, Qt::UniqueConnection); connect( tlb, &machine::TLB::memory_writes_update, this, &TLBDock::memory_writes_update, Qt::UniqueConnection); } connected_tlb = const_cast(tlb); top_form->setVisible(tlb != nullptr); no_tlb->setVisible(tlb == nullptr); delete tlbscene; tlbscene = nullptr; if (tlb != nullptr) { tlbscene = new TLBViewScene(tlb); graphicsview->setScene(tlbscene); } else { graphicsview->setScene(nullptr); } graphicsview->setVisible(tlb != nullptr); } void TLBDock::paintEvent(QPaintEvent *event) { l_stalled->setText(QString::number(stalled)); l_hit_rate->setText(QString::number(hit_rate, 'f', 3) + QString("%")); l_speed->setText(QString::number(speed_improv, 'f', 0) + QString("%")); l_hit->setText(QString::number(hit)); l_miss->setText(QString::number(miss)); l_m_reads->setText(QString::number(memory_reads)); l_m_writes->setText(QString::number(memory_writes)); QDockWidget::paintEvent(event); } void TLBDock::hit_update(unsigned val) { hit = val; update(); } void TLBDock::miss_update(unsigned val) { miss = val; update(); } void TLBDock::statistics_update(unsigned stalled_cycles, double speed_improv_v, double hit_rate_v) { stalled = stalled_cycles; speed_improv = speed_improv_v; hit_rate = hit_rate_v; update(); } void TLBDock::memory_reads_update(unsigned val) { memory_reads = val; l_m_reads->setText(QString::number(memory_reads)); update(); } void TLBDock::memory_writes_update(unsigned val) { memory_writes = val; l_m_writes->setText(QString::number(memory_writes)); update(); } ================================================ FILE: src/gui/windows/tlb/tlbdock.h ================================================ #ifndef TLBDOCK_H #define TLBDOCK_H #include "graphicsview.h" #include "machine/machine.h" #include "tlbview.h" #include #include #include #include class TLBDock : public QDockWidget { Q_OBJECT public: TLBDock(QWidget *parent, const QString &type); void setup(machine::TLB *tlb); void paintEvent(QPaintEvent *event) override; private slots: void hit_update(unsigned val); void miss_update(unsigned val); void statistics_update(unsigned stalled_cycles, double speed_improv, double hit_rate); void memory_reads_update(unsigned val); void memory_writes_update(unsigned val); private: QVBoxLayout *layout_box; QWidget *top_widget; QWidget *top_form; QFormLayout *layout_top_form; QLabel *l_hit; QLabel *l_miss; QLabel *l_stalled; QLabel *l_speed; QLabel *l_hit_rate; QLabel *no_tlb; QLabel *l_m_reads; QLabel *l_m_writes; GraphicsView *graphicsview; TLBViewScene *tlbscene; unsigned memory_reads = 0; unsigned memory_writes = 0; unsigned hit = 0; unsigned miss = 0; unsigned stalled = 0; double speed_improv = 0.0; double hit_rate = 0.0; QPointer connected_tlb; }; #endif // TLBDOCK_H ================================================ FILE: src/gui/windows/tlb/tlbview.cpp ================================================ #include "tlbview.h" #include #include #include #include static const int ROW_HEIGHT = 16; static const int VCOL_WIDTH = 18; static const int FIELD_WIDTH = 120; TLBAddressBlock::TLBAddressBlock(machine::TLB *tlb, unsigned width) { this->width = width; rows = tlb->get_config().get_tlb_num_sets(); s_row = rows > 1 ? (32 - __builtin_clz(rows - 1)) : 0; s_tag = 32 - s_row - 2; tag = 0; row = 0; connect(tlb, &machine::TLB::tlb_update, this, &TLBAddressBlock::tlb_update); } QRectF TLBAddressBlock::boundingRect() const { return QRectF(0, 0, width, 40); } void TLBAddressBlock::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { QFont fnt; fnt.setPointSize(8); painter->setFont(fnt); painter->drawText(QRectF(0, 0, width, 14), Qt::AlignCenter, "TLB Address"); painter->drawText(QRectF(5, 18, 100, 16), Qt::AlignLeft, QString("Set: %1").arg(row)); } void TLBAddressBlock::tlb_update(unsigned, unsigned set, bool, unsigned, quint64, quint64, bool) { this->row = set; update(); } /////////////////////////////////////////// TLBViewBlock::TLBViewBlock(machine::TLB *tlb, unsigned way) : tlb(tlb), way_index(way) { rows = tlb->get_config().get_tlb_num_sets(); curr_row = 0; last_set = 0; last_highlighted = false; QFont font; font.setPixelSize(10); validity.reserve(rows); asid.reserve(rows); vpn.reserve(rows); phys.reserve(rows); int y = 2; for (unsigned i = 0; i < rows; ++i) { int x = 2; auto *v = new QGraphicsSimpleTextItem("0", this); v->setFont(font); v->setPos(x, y); x += VCOL_WIDTH; auto *a = new QGraphicsSimpleTextItem("", this); a->setFont(font); a->setPos(x, y); x += 60; auto *vpnItem = new QGraphicsSimpleTextItem("", this); vpnItem->setFont(font); vpnItem->setPos(x, y); x += FIELD_WIDTH; auto *physItem = new QGraphicsSimpleTextItem("", this); physItem->setFont(font); physItem->setPos(x, y); validity.push_back(v); asid.push_back(a); vpn.push_back(vpnItem); phys.push_back(physItem); y += ROW_HEIGHT; } auto *l_v = new QGraphicsSimpleTextItem("V", this); l_v->setFont(font); l_v->setPos(2, -14); auto *l_asid = new QGraphicsSimpleTextItem("ASID", this); l_asid->setFont(font); l_asid->setPos(2 + VCOL_WIDTH + 2, -14); auto *l_vpn = new QGraphicsSimpleTextItem("VPN", this); l_vpn->setFont(font); l_vpn->setPos(2 + VCOL_WIDTH + 62, -14); auto *l_phys = new QGraphicsSimpleTextItem("PBASE", this); l_phys->setFont(font); l_phys->setPos(2 + VCOL_WIDTH + 62 + FIELD_WIDTH, -14); connect(tlb, &machine::TLB::tlb_update, this, &TLBViewBlock::tlb_update); } QRectF TLBViewBlock::boundingRect() const { return QRectF(-2, -18, VCOL_WIDTH + 60 + FIELD_WIDTH + 200, rows * ROW_HEIGHT + 40); } void TLBViewBlock::paint(QPainter *painter, const QStyleOptionGraphicsItem *, QWidget *) { int width = boundingRect().width(); painter->drawRect(0, 0, width, rows * ROW_HEIGHT); for (unsigned i = 0; i <= rows; ++i) { painter->drawLine(0, i * ROW_HEIGHT, width, i * ROW_HEIGHT); } } void TLBViewBlock::tlb_update( unsigned way, unsigned set, bool valid, unsigned asid_v, quint64 vpn_v, quint64 phys_v, bool write) { if (way != way_index) return; validity[set]->setText(valid ? "1" : "0"); asid[set]->setText(valid ? QString::number(asid_v) : QString()); vpn[set]->setText( valid ? QString("0x%1").arg(QString::number(vpn_v, 16).toUpper()) : QString()); phys[set]->setText( valid ? QString("0x%1").arg(QString::number(phys_v, 16).toUpper()) : QString()); if (last_highlighted) { validity[last_set]->setBrush(QBrush(QColor(0, 0, 0))); asid[last_set]->setBrush(QBrush(QColor(0, 0, 0))); vpn[last_set]->setBrush(QBrush(QColor(0, 0, 0))); phys[last_set]->setBrush(QBrush(QColor(0, 0, 0))); } if (valid) { QColor c = write ? QColor(200, 0, 0) : QColor(0, 0, 150); validity[set]->setBrush(QBrush(c)); asid[set]->setBrush(QBrush(c)); vpn[set]->setBrush(QBrush(c)); phys[set]->setBrush(QBrush(c)); } last_highlighted = true; last_set = set; } /////////////////////////////////////////// TLBViewScene::TLBViewScene(machine::TLB *tlb) { associativity = tlb->get_config().get_tlb_associativity(); block.reserve(associativity); int offset = 0; for (unsigned i = 0; i < associativity; ++i) { auto b = std::make_unique(tlb, i); addItem(b.get()); b->setPos(1, offset); offset += b->boundingRect().height(); block.push_back(std::move(b)); } ablock = std::make_unique( tlb, block.empty() ? FIELD_WIDTH : static_cast(block[0]->boundingRect().width())); addItem(ablock.get()); ablock->setPos(0, -ablock->boundingRect().height() - 16); } TLBViewScene::~TLBViewScene() { for (auto &bptr : block) { if (bptr) removeItem(bptr.get()); } block.clear(); if (ablock) { removeItem(ablock.get()); ablock.reset(); } } ================================================ FILE: src/gui/windows/tlb/tlbview.h ================================================ #ifndef TLBVIEW_H #define TLBVIEW_H #include "machine/memory/tlb/tlb.h" #include "svgscene/utils/memory_ownership.h" #include #include #include #include class TLBAddressBlock : public QGraphicsObject { Q_OBJECT public: TLBAddressBlock(machine::TLB *tlb, unsigned width); QRectF boundingRect() const override; void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; public slots: void tlb_update( unsigned way, unsigned set, bool valid, unsigned asid, quint64 vpn, quint64 phys, bool write); private: unsigned width; unsigned rows; unsigned tag, row; unsigned s_row, s_tag; }; class TLBViewBlock : public QGraphicsObject { Q_OBJECT public: TLBViewBlock(machine::TLB *tlb, unsigned way); ~TLBViewBlock() override = default; QRectF boundingRect() const override; void paint(QPainter *, const QStyleOptionGraphicsItem *, QWidget *) override; public slots: void tlb_update( unsigned way, unsigned set, bool valid, unsigned asid, quint64 vpn, quint64 phys, bool write); private: QPointer tlb; unsigned way_index; unsigned rows; std::vector validity; std::vector asid; std::vector vpn; std::vector phys; unsigned curr_row; unsigned last_set; bool last_highlighted; }; class TLBViewScene : public QGraphicsScene { public: TLBViewScene(machine::TLB *tlb); ~TLBViewScene() override; private: unsigned associativity; std::vector> block; std::unique_ptr ablock; }; #endif // TLBVIEW_H ================================================ FILE: src/machine/CMakeLists.txt ================================================ project(machine DESCRIPTION "The actual simulator as a library. Link with an UI of your choice.") set(CMAKE_AUTOMOC ON) set(machine_SOURCES execute/alu.cpp csr/controlstate.cpp core.cpp instruction.cpp machine.cpp machineconfig.cpp memory/backend/lcddisplay.cpp memory/backend/memory.cpp memory/backend/peripheral.cpp memory/backend/peripspiled.cpp memory/backend/serialport.cpp memory/backend/aclintmtimer.cpp memory/backend/aclintmswi.cpp memory/backend/aclintsswi.cpp memory/cache/cache.cpp memory/cache/cache_policy.cpp memory/frontend_memory.cpp memory/memory_bus.cpp memory/tlb/tlb.cpp memory/tlb/tlb_policy.cpp memory/virtual/page_table_walker.cpp programloader.cpp predictor.cpp registers.cpp simulator_exception.cpp symboltable.cpp ) set(machine_HEADERS execute/alu.h csr/controlstate.h core.h core/core_state.h csr/address.h instruction.h machine.h machineconfig.h config_isa.h machinedefs.h memory/address.h memory/address_range.h memory/backend/backend_memory.h memory/backend/lcddisplay.h memory/backend/memory.h memory/backend/peripheral.h memory/backend/peripspiled.h memory/backend/serialport.h memory/backend/aclintmtimer.h memory/backend/aclintmswi.h memory/backend/aclintsswi.h memory/cache/cache.h memory/cache/cache_policy.h memory/cache/cache_types.h memory/frontend_memory.h memory/memory_bus.h memory/memory_utils.h programloader.h predictor_types.h predictor.h pipeline.h registers.h register_value.h simulator_exception.h symboltable.h utils.h execute/alu_op.h execute/mul_op.h memory/virtual/virtual_address.h memory/tlb/tlb.h memory/virtual/sv32.h memory/tlb/tlb_policy.h memory/virtual/page_table_walker.h memory/address_with_mode.h ) # Object library is preferred, because the library archive is never really # needed. This option skips the archive creation and links directly .o files. add_library(machine STATIC ${machine_SOURCES} ${machine_HEADERS}) target_link_libraries(machine PRIVATE ${QtLib}::Core PUBLIC elf++ dwarf++) target_include_directories(machine PUBLIC "${PROJECT_SOURCE_DIR}/external/libelfin") if (NOT ${WASM}) # Machine tests (not available on WASM) add_executable(alu_test execute/alu.test.cpp execute/alu.test.h execute/alu.cpp execute/alu.h ) target_link_libraries(alu_test PRIVATE ${QtLib}::Core ${QtLib}::Test) add_test(NAME alu COMMAND alu_test) add_executable(registers_test register_value.h registers.cpp registers.h registers.test.cpp registers.test.h simulator_exception.cpp simulator_exception.h ) target_link_libraries(registers_test PRIVATE ${QtLib}::Core ${QtLib}::Test) add_test(NAME registers COMMAND registers_test) add_executable(memory_test machineconfig.cpp machineconfig.h memory/backend/backend_memory.h memory/backend/memory.cpp memory/backend/memory.h memory/backend/memory.test.cpp memory/backend/memory.test.h memory/frontend_memory.cpp memory/frontend_memory.h memory/tlb/tlb.h memory/tlb/tlb.cpp memory/tlb/tlb_policy.h memory/tlb/tlb_policy.cpp memory/virtual/page_table_walker.h memory/virtual/page_table_walker.cpp memory/memory_bus.cpp memory/memory_bus.h simulator_exception.cpp simulator_exception.h tests/utils/integer_decomposition.h ) target_link_libraries(memory_test PRIVATE ${QtLib}::Core ${QtLib}::Test) add_test(NAME memory COMMAND memory_test) add_executable(cache_test machineconfig.cpp machineconfig.h config_isa.h memory/backend/backend_memory.h memory/backend/memory.cpp memory/backend/memory.h memory/cache/cache.cpp memory/cache/cache.h memory/cache/cache.test.cpp memory/cache/cache.test.h memory/cache/cache_policy.cpp memory/cache/cache_policy.h memory/frontend_memory.cpp memory/frontend_memory.h memory/tlb/tlb.h memory/tlb/tlb.cpp memory/tlb/tlb_policy.h memory/tlb/tlb_policy.cpp memory/virtual/page_table_walker.h memory/virtual/page_table_walker.cpp memory/memory_bus.cpp memory/memory_bus.h simulator_exception.cpp simulator_exception.h tests/data/cache_test_performance_data.h tests/utils/integer_decomposition.h ) target_link_libraries(cache_test PRIVATE ${QtLib}::Core ${QtLib}::Test) add_test(NAME cache COMMAND cache_test) add_executable(instruction_test csr/controlstate.cpp csr/controlstate.h instruction.cpp instruction.h instruction.test.cpp instruction.test.h simulator_exception.cpp simulator_exception.h ) target_link_libraries(instruction_test PRIVATE ${QtLib}::Core ${QtLib}::Test) add_test(NAME instruction COMMAND instruction_test) add_executable(program_loader_test csr/controlstate.cpp csr/controlstate.h instruction.cpp instruction.h memory/backend/backend_memory.h memory/backend/memory.cpp memory/backend/memory.h programloader.cpp programloader.h programloader.test.cpp programloader.test.h simulator_exception.cpp simulator_exception.h symboltable.cpp symboltable.h ) target_link_libraries(program_loader_test PRIVATE ${QtLib}::Core ${QtLib}::Test elf++ dwarf++) target_include_directories(program_loader_test PRIVATE "${PROJECT_SOURCE_DIR}/external/libelfin") add_test(NAME program_loader COMMAND program_loader_test) add_executable(core_test csr/controlstate.cpp csr/controlstate.h core.cpp core.h core.test.cpp core.test.h execute/alu.cpp execute/alu.h instruction.cpp instruction.h memory/backend/backend_memory.h memory/backend/memory.cpp memory/backend/memory.h memory/cache/cache.cpp memory/cache/cache.h memory/cache/cache_policy.cpp memory/cache/cache_policy.h memory/frontend_memory.cpp memory/frontend_memory.h memory/memory_bus.cpp memory/memory_bus.h registers.cpp registers.h predictor.cpp predictor.h predictor_types.h simulator_exception.cpp simulator_exception.h machineconfig.cpp ) target_link_libraries(core_test PRIVATE ${QtLib}::Core ${QtLib}::Test elf++ dwarf++) target_include_directories(core_test PRIVATE "${PROJECT_SOURCE_DIR}/external/libelfin") add_test(NAME core COMMAND core_test) add_custom_target(machine_unit_tests DEPENDS alu_test registers_test memory_test cache_test instruction_test program_loader_test core_test) endif () ================================================ FILE: src/machine/bitfield.h ================================================ /** * Utility to parse and encode binary encoded fields. * * Use BitField if the field is guaranteed to be continuous. * Use SplitBitField if the field is not guaranteed to be continuous. * * @file */ #ifndef QTRVSIM_BITFIELD_H #define QTRVSIM_BITFIELD_H #include "common/containers/cvector.h" #include struct BitField { uint8_t count; uint8_t offset; template [[nodiscard]] T decode(T val) const { return (val >> offset) & (((uint64_t)1 << count) - 1); } template [[nodiscard]] T encode(T val) const { return ((val & (((uint64_t)1 << count) - 1)) << offset); } [[nodiscard]] uint64_t mask() const { return (((uint64_t)1 << count) - 1) << offset; } }; template struct SplitBitField { cvector fields; size_t shift = 0; [[nodiscard]] typename decltype(fields)::const_iterator begin() const { return fields.cbegin(); } [[nodiscard]] typename decltype(fields)::const_iterator end() const { return fields.cend(); } [[nodiscard]] uint32_t decode(uint32_t ins) const { uint32_t ret = 0; size_t offset = 0; for (BitField field : *this) { ret |= field.decode(ins) << offset; offset += field.count; } return ret << shift; } [[nodiscard]] uint32_t encode(uint32_t imm) const { uint32_t ret = 0; imm >>= shift; for (BitField field : *this) { ret |= field.encode(imm); imm >>= field.count; } return ret; } }; #endif // QTRVSIM_BITFIELD_H ================================================ FILE: src/machine/config_isa.h ================================================ #ifndef MACHINE_CONFIG_ISA_H #define MACHINE_CONFIG_ISA_H #include #include namespace machine { struct ConfigIsaWord { constexpr ConfigIsaWord() : bits(0) {}; constexpr ConfigIsaWord(const quint64 &abits) : bits(abits) {}; constexpr ConfigIsaWord(const ConfigIsaWord &isaWord) = default; //> Copy constructor constexpr ConfigIsaWord &operator=(const ConfigIsaWord &isaWord) = default; //> Assign // constructor constexpr static ConfigIsaWord empty() { return ConfigIsaWord(); }; constexpr ConfigIsaWord &operator&=(const ConfigIsaWord &isaWord) { bits &= isaWord.bits; return *this; } constexpr ConfigIsaWord &operator|=(const ConfigIsaWord &isaWord) { bits |= isaWord.bits; return *this; } constexpr ConfigIsaWord operator~() const { ConfigIsaWord ans(~bits); return ans; } friend constexpr ConfigIsaWord operator|(ConfigIsaWord lhs, const ConfigIsaWord &rhs) { lhs |= rhs; // reuse compound assignment return lhs; // return the result by value (uses move constructor) } friend constexpr ConfigIsaWord operator&(ConfigIsaWord lhs, const ConfigIsaWord &rhs) { lhs &= rhs; // reuse compound assignment return lhs; // return the result by value (uses move constructor) } constexpr friend bool operator==(const ConfigIsaWord &lhs, const ConfigIsaWord &rhs) { return lhs.bits == rhs.bits; } static constexpr ConfigIsaWord byChar(char ch) { if (ch >= 'A' && ch <= 'Z') ch -= 'A'; else if (ch >= 'a' && ch <= 'z') ch -= 'a'; else ch = 0; auto abits = static_cast(1) << ch; return ConfigIsaWord(abits); }; constexpr bool isEmpty() const { return bits == 0; }; constexpr bool contains(char ch) const { return !(*this & byChar(ch)).isEmpty(); }; constexpr bool contains(ConfigIsaWord &isaWord) const { return (*this & isaWord) == isaWord; }; ConfigIsaWord &modify(ConfigIsaWord &mask, ConfigIsaWord &val) { (*this) &= ~mask | val; (*this) |= mask & val; return *this; } constexpr auto toUnsigned() const { return bits; }; quint64 bits; }; } // namespace machine Q_DECLARE_METATYPE(machine::ConfigIsaWord) #endif // MACHINE_CONFIG_ISA_H ================================================ FILE: src/machine/core/core_state.h ================================================ #ifndef QTRVSIM_CORE_STATE_H #define QTRVSIM_CORE_STATE_H #include "common/memory_ownership.h" #include "machinedefs.h" #include "memory/address_range.h" #include "pipeline.h" #include #include #include using std::uint32_t; namespace machine { struct CoreState { Pipeline pipeline = {}; AddressRange LoadReservedRange; uint32_t stall_count = 0; uint32_t cycle_count = 0; unsigned current_privilege_u = static_cast(CSR::PrivilegeLevel::MACHINE); unsigned current_asid_u = 0u; [[nodiscard]] CSR::PrivilegeLevel current_privilege() const noexcept { return static_cast(current_privilege_u); } void set_current_privilege(CSR::PrivilegeLevel p) noexcept { current_privilege_u = static_cast(p); } [[nodiscard]] uint16_t current_asid() const noexcept { return static_cast(current_asid_u & 0x1FFu); } void set_current_asid(uint16_t a) noexcept { current_asid_u = static_cast(a & 0x1FFu); } }; } // namespace machine #endif // QTRVSIM_CORE_STATE_H ================================================ FILE: src/machine/core.cpp ================================================ #include "core.h" #include "common/logging.h" #include "execute/alu.h" #include "utils.h" #include LOG_CATEGORY("machine.core"); using namespace machine; static InstructionFlags unsupported_inst_flags_to_check(Xlen xlen, ConfigIsaWord isa_word) { unsigned flags_to_check = IMF_SUPPORTED; if (xlen == Xlen::_32) flags_to_check |= IMF_RV64; if (!isa_word.contains('A')) flags_to_check |= IMF_AMO; if (!isa_word.contains('M')) flags_to_check |= IMF_MUL; return InstructionFlags(flags_to_check); } Core::Core( Registers *regs, BranchPredictor *predictor, FrontendMemory *mem_program, FrontendMemory *mem_data, CSR::ControlState *control_state, Xlen xlen, ConfigIsaWord isa_word) : pc_if(state.pipeline.pc.final) , if_id(state.pipeline.fetch.final) , id_ex(state.pipeline.decode.final) , ex_mem(state.pipeline.execute.final) , mem_wb(state.pipeline.memory.final) , xlen(xlen) , check_inst_flags_val(IMF_SUPPORTED) , check_inst_flags_mask(unsupported_inst_flags_to_check(xlen, isa_word)) , regs(regs) , control_state(control_state) , predictor(predictor) , mem_data(mem_data) , mem_program(mem_program) , ex_handlers() , ex_default_handler(new StopExceptionHandler()) { stop_on_exception.fill(true); step_over_exception.fill(true); step_over_exception[EXCAUSE_INT] = false; } void Core::step(bool skip_break) { emit step_started(); state.cycle_count++; do_step(skip_break); emit step_done(state); } void Core::reset() { state.cycle_count = 0; state.stall_count = 0; do_reset(); set_current_privilege(CSR::PrivilegeLevel::MACHINE); } unsigned Core::get_cycle_count() const { return state.cycle_count; } unsigned Core::get_stall_count() const { return state.stall_count; } Registers *Core::get_regs() const { return regs; } CSR::ControlState *Core::get_control_state() const { return control_state; } FrontendMemory *Core::get_mem_data() const { return mem_data; } FrontendMemory *Core::get_mem_program() const { return mem_program; } BranchPredictor *Core::get_predictor() const { return predictor; } const CoreState &Core::get_state() const { return state; } void Core::insert_hwbreak(Address address) { hw_breaks.insert(address, new hwBreak(address)); } void Core::remove_hwbreak(Address address) { hwBreak *hwbrk = hw_breaks.take(address); delete hwbrk; } bool Core::is_hwbreak(Address address) const { hwBreak *hwbrk = hw_breaks.value(address); return hwbrk != nullptr; } void Core::set_stop_on_exception(enum ExceptionCause excause, bool value) { stop_on_exception[excause] = value; } bool Core::get_stop_on_exception(enum ExceptionCause excause) const { return stop_on_exception[excause]; } void Core::set_step_over_exception(enum ExceptionCause excause, bool value) { step_over_exception[excause] = value; } bool Core::get_step_over_exception(enum ExceptionCause excause) const { return step_over_exception[excause]; } Xlen Core::get_xlen() const { return xlen; } void Core::set_current_privilege(CSR::PrivilegeLevel privilege) { state.set_current_privilege(privilege); const unsigned PRIV_BITS = unsigned(IMF_PRIV_M) | unsigned(IMF_PRIV_H) | unsigned(IMF_PRIV_S); InstructionFlags base_mask = InstructionFlags(unsigned(check_inst_flags_mask) & ~PRIV_BITS); InstructionFlags base_val = InstructionFlags(unsigned(check_inst_flags_val) & ~PRIV_BITS); unsigned allowed_priv = 0; if (privilege >= CSR::PrivilegeLevel::SUPERVISOR) { allowed_priv |= unsigned(IMF_PRIV_S); } if (privilege >= CSR::PrivilegeLevel::HYPERVISOR) { allowed_priv |= unsigned(IMF_PRIV_H); } if (privilege >= CSR::PrivilegeLevel::MACHINE) { allowed_priv |= unsigned(IMF_PRIV_M); } unsigned disallowed_priv = (PRIV_BITS & ~allowed_priv); InstructionFlags new_mask = InstructionFlags(unsigned(base_mask) | disallowed_priv); InstructionFlags new_val = base_val; check_inst_flags_mask = new_mask; check_inst_flags_val = new_val; } CSR::PrivilegeLevel Core::get_current_privilege() const { return state.current_privilege(); }; void Core::register_exception_handler(ExceptionCause excause, ExceptionHandler *exhandler) { if (excause == EXCAUSE_NONE) { ex_default_handler.reset(exhandler); } else { ExceptionHandler *old = ex_handlers.take(excause); delete old; ex_handlers.insert(excause, exhandler); } } bool Core::handle_exception( ExceptionCause excause, const Instruction &inst, Address inst_addr, Address next_addr, Address jump_branch_pc, Address mem_ref_addr) { if (excause == EXCAUSE_INSN_ILLEGAL) { throw SIMULATOR_EXCEPTION( UnsupportedInstruction, "Instruction with following encoding is not supported", QString::number(inst.data(), 16)); } if (excause == EXCAUSE_HWBREAK) { regs->write_pc(inst_addr); } if (control_state != nullptr) { control_state->write_internal(CSR::Id::MEPC, inst_addr.get_raw()); control_state->update_exception_cause(excause); if (control_state->read_internal(CSR::Id::MTVEC) != 0 && !get_step_over_exception(excause)) { control_state->exception_initiate( get_current_privilege(), CSR::PrivilegeLevel::MACHINE); set_current_privilege(CSR::PrivilegeLevel::MACHINE); regs->write_pc(control_state->exception_pc_address()); } } bool ret = false; ExceptionHandler *exhandler = ex_handlers.value(excause, ex_default_handler.data()); if (exhandler != nullptr) { ret = exhandler->handle_exception( this, regs, excause, inst_addr, next_addr, jump_branch_pc, mem_ref_addr); } if (get_stop_on_exception(excause)) { emit stop_on_exception_reached(); } return ret; } static int32_t amo32_operations(enum AccessControl memctl, int32_t a, int32_t b) { switch (memctl) { case AC_AMOSWAP32: return b; case AC_AMOADD32: return a + b; case AC_AMOXOR32: return a ^ b; case AC_AMOAND32: return a & b; case AC_AMOOR32: return a | b; case AC_AMOMIN32: return a < b ? a : b; case AC_AMOMAX32: return a < b ? b : a; case AC_AMOMINU32: return (uint32_t)a < (uint32_t)b ? a : b; case AC_AMOMAXU32: return (uint32_t)a < (uint32_t)b ? b : a; default: break; } return 0; } static int64_t amo64_operations(enum AccessControl memctl, int64_t a, int64_t b) { switch (memctl) { case AC_AMOSWAP64: return b; case AC_AMOADD64: return a + b; case AC_AMOXOR64: return a ^ b; case AC_AMOAND64: return a & b; case AC_AMOOR64: return a | b; case AC_AMOMIN64: return a < b ? a : b; case AC_AMOMAX64: return a < b ? b : a; case AC_AMOMINU64: return (uint64_t)a < (uint64_t)b ? a : b; case AC_AMOMAXU64: return (uint64_t)a < (uint64_t)b ? b : a; default: break; } return 0; } enum ExceptionCause Core::memory_special( enum AccessControl memctl, int mode, bool memread, bool memwrite, RegisterValue &towrite_val, RegisterValue rt_value, Address mem_addr) { Q_UNUSED(mode) switch (memctl) { case AC_CACHE_OP: mem_data->sync(); mem_program->sync(); predictor->flush(); break; case AC_LR32: if (!memread) { break; } state.LoadReservedRange = AddressRange(mem_addr, mem_addr + 3); towrite_val = (int32_t)(mem_data->read_u32(mem_addr)); break; case AC_SC32: if (!memwrite) { break; } if (state.LoadReservedRange.contains(AddressRange(mem_addr, mem_addr + 3))) { mem_data->write_u32(mem_addr, rt_value.as_u32()); towrite_val = 0; } else { towrite_val = 1; } state.LoadReservedRange.reset(); break; case AC_LR64: if (!memread) { break; } state.LoadReservedRange = AddressRange(mem_addr, mem_addr + 7); towrite_val = mem_data->read_u64(mem_addr); break; case AC_SC64: if (!memwrite) { break; } if (state.LoadReservedRange.contains(AddressRange(mem_addr, mem_addr + 7))) { mem_data->write_u64(mem_addr, rt_value.as_u64()); towrite_val = 0; } else { towrite_val = 1; } break; case AC_FISRT_AMO_MODIFY32 ... AC_LAST_AMO_MODIFY32: { if (!memread || !memwrite) { break; } int32_t fetched_value; fetched_value = (int32_t)(mem_data->read_u32(mem_addr)); towrite_val = amo32_operations(memctl, fetched_value, rt_value.as_u32()); mem_data->write_u32(mem_addr, towrite_val.as_u32()); towrite_val = fetched_value; break; } case AC_FISRT_AMO_MODIFY64 ... AC_LAST_AMO_MODIFY64: { if (!memread || !memwrite) { break; } int64_t fetched_value; fetched_value = (int64_t)(mem_data->read_u64(mem_addr)); towrite_val = (uint64_t)amo64_operations(memctl, fetched_value, rt_value.as_u64()); mem_data->write_u64(mem_addr, towrite_val.as_u64()); towrite_val = fetched_value; break; } default: break; } return EXCAUSE_NONE; } FetchState Core::fetch(PCInterstage pc, bool skip_break) { if (pc.stop_if) { return {}; } const AddressWithMode inst_addr = AddressWithMode(regs->read_pc(), make_access_mode(state)); const Instruction inst(mem_program->read_u32(inst_addr)); ExceptionCause excause = EXCAUSE_NONE; if (!skip_break && hw_breaks.contains(inst_addr)) { excause = EXCAUSE_HWBREAK; } if (control_state != nullptr) { control_state->increment_internal(CSR::Id::MCYCLE, 1); } if (control_state != nullptr && excause == EXCAUSE_NONE) { if (control_state->core_interrupt_request()) { excause = EXCAUSE_INT; } } return { FetchInternalState { .fetched_value = inst.data() }, FetchInterstage { .inst = inst, .inst_addr = inst_addr, .next_inst_addr = inst_addr + inst.size(), .predicted_next_inst_addr = predictor->predict_next_pc_address(inst, inst_addr), .excause = excause, .is_valid = true, } }; } DecodeState Core::decode(const FetchInterstage &dt) { InstructionFlags flags; bool w_operation = this->xlen != Xlen::_64; AluCombinedOp alu_op {}; AccessControl mem_ctl; ExceptionCause excause = dt.excause; dt.inst.flags_alu_op_mem_ctl(flags, alu_op, mem_ctl); CSR::PrivilegeLevel inst_xret_priv = CSR::PrivilegeLevel::UNPRIVILEGED; if (flags & IMF_XRET) { if (flags & IMF_PRIV_M) { inst_xret_priv = CSR::PrivilegeLevel::MACHINE; } else if (flags & IMF_PRIV_H) { inst_xret_priv = CSR::PrivilegeLevel::HYPERVISOR; } else if (flags & IMF_PRIV_S) { inst_xret_priv = CSR::PrivilegeLevel::SUPERVISOR; } else { inst_xret_priv = CSR::PrivilegeLevel::UNPRIVILEGED; } } if ((flags ^ check_inst_flags_val) & check_inst_flags_mask) { excause = EXCAUSE_INSN_ILLEGAL; } RegisterId num_rs = (flags & (IMF_ALU_REQ_RS | IMF_ALU_RS_ID)) ? dt.inst.rs() : 0; RegisterId num_rt = (flags & IMF_ALU_REQ_RT) ? dt.inst.rt() : 0; RegisterId num_rd = (flags & IMF_REGWRITE) ? dt.inst.rd() : 0; // When instruction does not specify register, it is set to x0 as operations on x0 have no // side effects (not even visualization). RegisterValue val_rs = (flags & IMF_ALU_RS_ID) ? uint64_t(size_t(num_rs)) : regs->read_gp(num_rs); RegisterValue val_rt = regs->read_gp(num_rt); RegisterValue immediate_val = dt.inst.immediate(); const bool regwrite = flags & IMF_REGWRITE; CSR::Address csr_address = (flags & IMF_CSR) ? dt.inst.csr_address() : CSR::Address(0); RegisterValue csr_read_val = ((control_state != nullptr && (flags & IMF_CSR))) ? control_state->read(csr_address, get_current_privilege()) : 0; bool csr_write = (flags & IMF_CSR) && (!(flags & IMF_CSR_TO_ALU) || (num_rs != 0)); if ((flags & IMF_EXCEPTION) && (excause == EXCAUSE_NONE)) { if (flags & IMF_EBREAK) { excause = EXCAUSE_BREAK; } else if (flags & IMF_ECALL) { excause = EXCAUSE_ECALL_M; // TODO: EXCAUSE_ECALL_S, EXCAUSE_ECALL_U } } if (flags & IMF_FORCE_W_OP) w_operation = true; return { DecodeInternalState { .alu_op_num = static_cast(alu_op.alu_op), .excause_num = static_cast(excause), .inst_bus = dt.inst.data(), .alu_mul = bool(flags & IMF_MUL), }, DecodeInterstage { .inst = dt.inst, .inst_addr = dt.inst_addr, .next_inst_addr = dt.next_inst_addr, .predicted_next_inst_addr = dt.predicted_next_inst_addr, .val_rs = val_rs, .val_rs_orig = val_rs, .val_rt = val_rt, .val_rt_orig = val_rt, .immediate_val = immediate_val, .csr_read_val = csr_read_val, .csr_address = csr_address, .excause = excause, .ff_rs = FORWARD_NONE, .ff_rt = FORWARD_NONE, .alu_component = (flags & IMF_AMO) ? AluComponent::PASS : (flags & IMF_MUL) ? AluComponent::MUL : AluComponent::ALU, .aluop = alu_op, .memctl = mem_ctl, .num_rs = num_rs, .num_rt = num_rt, .num_rd = num_rd, .memread = bool(flags & IMF_MEMREAD), .memwrite = bool(flags & IMF_MEMWRITE), .alusrc = bool(flags & IMF_ALUSRC), .regwrite = regwrite, .alu_req_rs = bool(flags & IMF_ALU_REQ_RS), .alu_req_rt = bool(flags & IMF_ALU_REQ_RT), .branch_bxx = bool(flags & IMF_BRANCH), .branch_jal = bool(flags & IMF_JUMP), .branch_val = bool(flags & IMF_BJ_NOT), .branch_jalr = bool(flags & IMF_BRANCH_JALR), .stall = false, .is_valid = dt.is_valid, .w_operation = w_operation, .alu_mod = bool(flags & IMF_ALU_MOD), .alu_pc = bool(flags & IMF_PC_TO_ALU), .csr = bool(flags & IMF_CSR), .csr_to_alu = bool(flags & IMF_CSR_TO_ALU), .csr_write = csr_write, .xret = bool(flags & IMF_XRET), .xret_privlev = inst_xret_priv, .insert_stall_before = bool(flags & IMF_CSR) } }; } ExecuteState Core::execute(const DecodeInterstage &dt) { enum ExceptionCause excause = dt.excause; // TODO refactor to produce multiplexor index and multiplex function const RegisterValue alu_fst = [=] { if (dt.alu_pc) return RegisterValue(dt.inst_addr.get_raw()); return dt.val_rs; }(); const RegisterValue alu_sec = [=] { if (dt.csr_to_alu) return dt.csr_read_val; if (dt.alusrc) return dt.immediate_val; return dt.val_rt; }(); const RegisterValue alu_val = [=] { if (excause != EXCAUSE_NONE) return RegisterValue(0); return alu_combined_operate( dt.aluop, dt.alu_component, dt.w_operation, dt.alu_mod, alu_fst, alu_sec); }(); const Address branch_jal_target = dt.inst_addr + dt.immediate_val.as_i64(); const unsigned stall_status = [=] { if (dt.stall) return 1; if (dt.ff_rs != FORWARD_NONE || dt.ff_rt != FORWARD_NONE) return 2; return 0; }(); return { ExecuteInternalState { .alu_src1 = dt.val_rs, .alu_src2 = alu_sec, .immediate = dt.immediate_val, .rs = dt.val_rs_orig, .rt = dt.val_rt_orig, .stall_status = stall_status, .alu_op_num = static_cast(dt.aluop.alu_op), .forward_from_rs1_num = static_cast(dt.ff_rs), .forward_from_rs2_num = static_cast(dt.ff_rt), .excause_num = static_cast(dt.excause), .alu_src = dt.alusrc, .alu_mul = dt.alu_component == AluComponent::MUL, .branch_bxx = dt.branch_bxx, .alu_pc = dt.alu_pc, }, ExecuteInterstage { .inst = dt.inst, .inst_addr = dt.inst_addr, .next_inst_addr = dt.next_inst_addr, .predicted_next_inst_addr = dt.predicted_next_inst_addr, .branch_jal_target = branch_jal_target, .val_rt = dt.val_rt, .alu_val = alu_val, .immediate_val = dt.immediate_val, .csr_read_val = dt.csr_read_val, .csr_address = dt.csr_address, .excause = excause, .memctl = dt.memctl, .num_rd = dt.num_rd, .memread = dt.memread, .memwrite = dt.memwrite, .regwrite = dt.regwrite, .is_valid = dt.is_valid, .branch_bxx = dt.branch_bxx, .branch_jal = dt.branch_jal, .branch_val = dt.branch_val, .branch_jalr = dt.branch_jalr, .alu_zero = alu_val == 0, .csr = dt.csr, .csr_write = dt.csr_write, .xret = dt.xret, .xret_privlev = dt.xret_privlev, } }; } MemoryState Core::memory(const ExecuteInterstage &dt) { RegisterValue towrite_val = dt.alu_val; auto mem_addr = AddressWithMode(get_xlen_from_reg(dt.alu_val), make_access_mode(state)); bool memread = dt.memread; bool memwrite = dt.memwrite; bool regwrite = dt.regwrite; Address computed_next_inst_addr; enum ExceptionCause excause = dt.excause; if (excause == EXCAUSE_NONE) { if (is_special_access(dt.memctl)) { excause = memory_special( dt.memctl, dt.inst.rt(), memread, memwrite, towrite_val, dt.val_rt, mem_addr); } else if (is_regular_access(dt.memctl)) { if (memwrite) { mem_data->write_ctl(dt.memctl, mem_addr, dt.val_rt); } if (memread) { towrite_val = mem_data->read_ctl(dt.memctl, mem_addr); } } else { Q_ASSERT(dt.memctl == AC_NONE); // AC_NONE is memory NOP } } if (dt.excause != EXCAUSE_NONE) { memread = false; memwrite = false; regwrite = false; } // Conditional branch (BXX = BEQ | BNE...) is executed and should be taken. const bool branch_bxx_taken = dt.branch_bxx && (!dt.branch_val ^ !dt.alu_zero); // Unconditional jump should be taken (JALX = JAL | JALR). const bool branch_jalx = dt.branch_jalr || dt.branch_jal; computed_next_inst_addr = compute_next_inst_addr(dt, branch_bxx_taken); // Predictor update if (dt.branch_jal) { // JAL Jump instruction (J-type (alternative to U-type with different immediate bit order)) predictor->update( dt.inst, dt.inst_addr, dt.branch_jal_target, BranchType::JUMP, BranchResult::TAKEN); } else if (dt.branch_jalr) { // JALR Jump register instruction (I-type) predictor->update( dt.inst, dt.inst_addr, Address(get_xlen_from_reg(dt.alu_val)), BranchType::JUMP, BranchResult::TAKEN); } else if (dt.branch_bxx) { // BXX Conditional branch instruction (B-type (alternative to S-type with different // immediate bit order)) predictor->update( dt.inst, dt.inst_addr, dt.branch_jal_target, BranchType::BRANCH, branch_bxx_taken ? BranchResult::TAKEN : BranchResult::NOT_TAKEN); } bool csr_written = false; if (control_state != nullptr && dt.is_valid && dt.excause == EXCAUSE_NONE) { control_state->increment_internal(CSR::Id::MINSTRET, 1); if (dt.csr_write) { control_state->write(dt.csr_address, dt.alu_val, get_current_privilege()); csr_written = true; } if (dt.xret) { CSR::PrivilegeLevel restored = control_state->exception_return(get_current_privilege(), dt.xret_privlev); set_current_privilege(restored); if (this->xlen == Xlen::_32) computed_next_inst_addr = Address(control_state->read_internal(CSR::Id::MEPC).as_u32()); else computed_next_inst_addr = Address(control_state->read_internal(CSR::Id::MEPC).as_u64()); csr_written = true; } } // Predictor statistics update if (computed_next_inst_addr != dt.predicted_next_inst_addr) { predictor->increment_mispredictions(); } return { MemoryInternalState { .mem_read_val = towrite_val, .mem_write_val = dt.val_rt, .mem_addr = dt.alu_val, .excause_num = static_cast(excause), .memwrite = memwrite, .memread = memread, .branch_bxx = dt.branch_bxx, .branch_jal = dt.branch_jal, // PC should be modified by branch/jump instruction. .branch_outcome = branch_bxx_taken || branch_jalx, .branch_jalx = branch_jalx, .branch_jalr = dt.branch_jalr, .xret = dt.xret, }, MemoryInterstage { .inst = dt.inst, .inst_addr = dt.inst_addr, .next_inst_addr = dt.next_inst_addr, .predicted_next_inst_addr = dt.predicted_next_inst_addr, .computed_next_inst_addr = computed_next_inst_addr, .mem_addr = mem_addr, .towrite_val = [=]() -> RegisterValue { if (dt.csr) return dt.csr_read_val; if (dt.branch_jalr || dt.branch_jal) return dt.next_inst_addr.get_raw(); return towrite_val; }(), .excause = dt.excause, .num_rd = dt.num_rd, .memtoreg = memread, .regwrite = regwrite, .is_valid = dt.is_valid, .csr_written = csr_written, .xret_privlev = dt.xret_privlev, } }; } WritebackState Core::writeback(const MemoryInterstage &dt) { if (dt.regwrite) { regs->write_gp(dt.num_rd, dt.towrite_val); } return WritebackState { WritebackInternalState { .inst = (dt.excause == EXCAUSE_NONE) ? dt.inst : Instruction::NOP, .inst_addr = dt.inst_addr, .value = dt.towrite_val, .num_rd = dt.num_rd, .regwrite = dt.regwrite, .memtoreg = dt.memtoreg, } }; } Address Core::compute_next_inst_addr(const ExecuteInterstage &exec, bool branch_taken) const { if (branch_taken || exec.branch_jal) { return exec.branch_jal_target; } if (exec.branch_jalr) { return Address(get_xlen_from_reg(exec.alu_val)); } return exec.next_inst_addr; } uint64_t Core::get_xlen_from_reg(RegisterValue reg) const { switch (this->xlen) { case Xlen::_32: return reg.as_u32(); case Xlen::_64: return reg.as_u64(); default: UNREACHABLE } } CoreSingle::CoreSingle( Registers *regs, BranchPredictor *predictor, FrontendMemory *mem_program, FrontendMemory *mem_data, CSR::ControlState *control_state, Xlen xlen, ConfigIsaWord isa_word) : Core(regs, predictor, mem_program, mem_data, control_state, xlen, isa_word) { reset(); } void CoreSingle::do_step(bool skip_break) { Pipeline &p = state.pipeline; p.fetch = fetch(pc_if, skip_break); p.decode = decode(p.fetch.final); p.execute = execute(p.decode.final); p.memory = memory(p.execute.final); p.writeback = writeback(p.memory.final); regs->write_pc(mem_wb.computed_next_inst_addr); if (mem_wb.excause != EXCAUSE_NONE) { handle_exception( mem_wb.excause, mem_wb.inst, mem_wb.inst_addr, regs->read_pc(), prev_inst_addr, mem_wb.mem_addr); return; } prev_inst_addr = mem_wb.inst_addr; } void CoreSingle::do_reset() { state.pipeline = {}; prev_inst_addr = Address::null(); } CorePipelined::CorePipelined( Registers *regs, BranchPredictor *predictor, FrontendMemory *mem_program, FrontendMemory *mem_data, CSR::ControlState *control_state, Xlen xlen, ConfigIsaWord isa_word, MachineConfig::HazardUnit hazard_unit) : Core(regs, predictor, mem_program, mem_data, control_state, xlen, isa_word) { this->hazard_unit = hazard_unit; reset(); } void CorePipelined::do_step(bool skip_break) { Pipeline &p = state.pipeline; const Address jump_branch_pc = mem_wb.inst_addr; const FetchInterstage saved_if_id = if_id; p.writeback = writeback(mem_wb); p.memory = memory(ex_mem); p.execute = execute(id_ex); p.decode = decode(if_id); p.fetch = fetch(pc_if, skip_break); bool exception_in_progress = mem_wb.excause != EXCAUSE_NONE; if (exception_in_progress) { ex_mem.flush(); } exception_in_progress |= ex_mem.excause != EXCAUSE_NONE; if (exception_in_progress) { id_ex.flush(); } exception_in_progress |= id_ex.excause != EXCAUSE_NONE; if (exception_in_progress) { if_id.flush(); } bool stall = false; if (hazard_unit != MachineConfig::HU_NONE) { stall |= handle_data_hazards(); } /* PC and exception pseudo stage * ============================== */ pc_if = {}; if (mem_wb.excause != EXCAUSE_NONE) { /* By default, execution continues with the next instruction after exception. */ regs->write_pc(mem_wb.computed_next_inst_addr); /* Exception handler may override this behavior and change the PC (e.g. hwbreak). */ handle_exception( mem_wb.excause, mem_wb.inst, mem_wb.inst_addr, mem_wb.computed_next_inst_addr, jump_branch_pc, mem_wb.mem_addr); } else if (detect_mispredicted_jump() || mem_wb.csr_written) { /* If the jump was predicted incorrectly or csr register was written, we need to flush the * pipeline. */ flush_and_continue_from_address(mem_wb.computed_next_inst_addr); } else if (exception_in_progress) { /* An exception is in progress which caused the pipeline before the exception to be flushed. * Therefore, next pc cannot be determined from if_id (now NOP). * To make the visualization cleaner we stop fetching (and PC update) until the exception * is handled. */ pc_if.stop_if = true; } else if (stall || is_stall_requested()) { /* Fetch from the same PC is repeated due to stall in the pipeline. */ handle_stall(saved_if_id); } else { /* Normal execution. */ regs->write_pc(if_id.predicted_next_inst_addr); } } void CorePipelined::flush_and_continue_from_address(Address next_pc) { regs->write_pc(next_pc); if_id.flush(); id_ex.flush(); ex_mem.flush(); } void CorePipelined::handle_stall(const FetchInterstage &saved_if_id) { /* * Stall handing: * - IF fetches new instruction, but it is not allowed to save into IF/ID register. This is * simulated by restoring the `if_id` to its original value. * - ID continues normally. On next cycle, perform the same as before as IF/ID will be * unchanged. * - EX is where stall is inserted by flush. The flushed instruction will be re-executed * as ID repeats its execution. */ if_id = saved_if_id; id_ex.flush(); id_ex.stall = true; // for visualization state.stall_count++; } bool CorePipelined::detect_mispredicted_jump() const { return mem_wb.computed_next_inst_addr != mem_wb.predicted_next_inst_addr; } bool CorePipelined::is_stall_requested() const { return id_ex.insert_stall_before && ex_mem.is_valid; } template bool is_hazard_in_stage(const InterstageReg &interstage, const DecodeInterstage &id_ex) { return ( interstage.regwrite && interstage.num_rd != 0 && ((id_ex.alu_req_rs && interstage.num_rd == id_ex.num_rs) || (id_ex.alu_req_rt && interstage.num_rd == id_ex.num_rt))); // Note: We make exception with $0 as that has no effect and is used in nop instruction } bool CorePipelined::handle_data_hazards() { // Note: We make exception with $0 as that has no effect when // written and is used in nop instruction bool stall = false; if (is_hazard_in_stage(mem_wb, id_ex)) { if (hazard_unit == MachineConfig::HU_STALL_FORWARD) { // Forward result value if (id_ex.alu_req_rs && mem_wb.num_rd == id_ex.num_rs) { id_ex.val_rs = mem_wb.towrite_val; id_ex.ff_rs = FORWARD_FROM_W; } if (id_ex.alu_req_rt && mem_wb.num_rd == id_ex.num_rt) { id_ex.val_rt = mem_wb.towrite_val; id_ex.ff_rt = FORWARD_FROM_W; } } else { stall = true; } } if (is_hazard_in_stage(ex_mem, id_ex)) { if (hazard_unit == MachineConfig::HU_STALL_FORWARD) { if (ex_mem.memread) { stall = true; } else { // Forward result value if (id_ex.alu_req_rs && ex_mem.num_rd == id_ex.num_rs) { id_ex.val_rs = ex_mem.alu_val; id_ex.ff_rs = FORWARD_FROM_M; } if (id_ex.alu_req_rt && ex_mem.num_rd == id_ex.num_rt) { id_ex.val_rt = ex_mem.alu_val; id_ex.ff_rt = FORWARD_FROM_M; } } } else { stall = true; } } return stall; } void CorePipelined::do_reset() { state.pipeline = {}; } bool StopExceptionHandler::handle_exception( Core *core, Registers *regs, ExceptionCause excause, Address inst_addr, Address next_addr, Address jump_branch_pc, Address mem_ref_addr) { Q_UNUSED(core) DEBUG( "Exception cause %d instruction PC 0x%08" PRIx64 " next PC 0x%08" PRIx64 " jump branch PC 0x%08" PRIx64 "registers PC 0x%08" PRIx64 " mem ref 0x%08" PRIx64, excause, inst_addr.get_raw(), next_addr.get_raw(), jump_branch_pc.get_raw(), regs->read_pc().get_raw(), mem_ref_addr.get_raw()); return true; } ================================================ FILE: src/machine/core.h ================================================ #ifndef CORE_H #define CORE_H #include "common/memory_ownership.h" #include "core/core_state.h" #include "csr/controlstate.h" #include "instruction.h" #include "machineconfig.h" #include "memory/address.h" #include "memory/frontend_memory.h" #include "pipeline.h" #include "predictor.h" #include "register_value.h" #include "registers.h" #include "simulator_exception.h" #include namespace machine { using std::array; class ExceptionHandler; class StopExceptionHandler; struct hwBreak; class Core : public QObject { Q_OBJECT public: Core( Registers *regs, BranchPredictor *predictor, FrontendMemory *mem_program, FrontendMemory *mem_data, CSR::ControlState *control_state, Xlen xlen, ConfigIsaWord isa_word); void step(bool skip_break = false); void reset(); // Reset core (only core, memory and registers has to be reset separately). unsigned get_cycle_count() const; unsigned get_stall_count() const; Registers *get_regs() const; CSR::ControlState *get_control_state() const; BranchPredictor *get_predictor() const; FrontendMemory *get_mem_data() const; FrontendMemory *get_mem_program() const; const CoreState &get_state() const; Xlen get_xlen() const; void insert_hwbreak(Address address); void remove_hwbreak(Address address); bool is_hwbreak(Address address) const; void register_exception_handler(ExceptionCause excause, ExceptionHandler *exhandler); void set_stop_on_exception(enum ExceptionCause excause, bool value); bool get_stop_on_exception(enum ExceptionCause excause) const; void set_step_over_exception(enum ExceptionCause excause, bool value); bool get_step_over_exception(enum ExceptionCause excause) const; void set_current_privilege(CSR::PrivilegeLevel privilege); CSR::PrivilegeLevel get_current_privilege() const; static inline AccessMode make_access_mode(const CoreState &st) { CSR::PrivilegeLevel priv = st.current_privilege(); uint16_t asid = st.current_asid(); bool uncached = false; return AccessMode::pack(asid, priv, uncached); } /** * Abstracts XLEN from code flow. XLEN core will obtain XLEN value from register value. * The value will be zero extended to u64. */ uint64_t get_xlen_from_reg(RegisterValue reg) const; protected: CoreState state {}; /** * Shortcuts to interstage registers * Interstage registers are stored in the core state struct in 2 copies. One (result) is the * state after combinatorial logic of each stage has been applied. This is used to visualize * the internal state of a stage. The should be modified ONLY by the stage logic functions. The * other (final) is the one actually written to HW interstage register. Any operation within * core should happen on the final registers. * * The bellow references provide shortcuts to the final interstage registers. */ /** Reference to pseudo interstage register PC/IF inside core state. */ PCInterstage &pc_if; /** Reference to interstage register IF/ID inside core state. */ FetchInterstage &if_id; /** Reference to interstage register ID/EX inside core state. */ DecodeInterstage &id_ex; /** Reference to interstage register EX/MEM inside core state. */ ExecuteInterstage &ex_mem; /** Reference to interstage register MEM/WB inside core state. */ MemoryInterstage &mem_wb; signals: void stop_on_exception_reached(); void step_started(); void step_done(const CoreState &); protected: virtual void do_step(bool skip_break) = 0; virtual void do_reset() = 0; bool handle_exception( ExceptionCause excause, const Instruction &inst, Address inst_addr, Address next_addr, Address jump_branch_pc, Address mem_ref_addr); const Xlen xlen; InstructionFlags check_inst_flags_val; InstructionFlags check_inst_flags_mask; BORROWED Registers *const regs; BORROWED CSR::ControlState *const control_state; BORROWED BranchPredictor *const predictor; BORROWED FrontendMemory *const mem_data, *const mem_program; array stop_on_exception {}; array step_over_exception {}; QMap hw_breaks {}; QMap ex_handlers; Box ex_default_handler; FetchState fetch(PCInterstage pc, bool skip_break); DecodeState decode(const FetchInterstage &); static ExecuteState execute(const DecodeInterstage &); MemoryState memory(const ExecuteInterstage &); WritebackState writeback(const MemoryInterstage &); /** * This function computes the address, the next executed instruction should be on. The word * `computed` is used in contrast with predicted value by the branch predictor. */ Address compute_next_inst_addr(const ExecuteInterstage &exec, bool branch_taken) const; enum ExceptionCause memory_special( enum AccessControl memctl, int mode, bool memread, bool memwrite, RegisterValue &towrite_val, RegisterValue rt_value, Address mem_addr); }; class CoreSingle : public Core { public: CoreSingle( Registers *regs, BranchPredictor *predictor, FrontendMemory *mem_program, FrontendMemory *mem_data, CSR::ControlState *control_state, Xlen xlen, ConfigIsaWord isa_word); protected: void do_step(bool skip_break) override; void do_reset() override; private: Address prev_inst_addr {}; }; class CorePipelined : public Core { public: CorePipelined( Registers *regs, BranchPredictor *predictor, FrontendMemory *mem_program, FrontendMemory *mem_data, CSR::ControlState *control_state, Xlen xlen, ConfigIsaWord isa_word, // Default value is used to keep same interface as core single. // Forward was chosen as the most conservative variant (regarding correctness). MachineConfig::HazardUnit hazard_unit = MachineConfig::HazardUnit::HU_STALL_FORWARD); protected: void do_step(bool skip_break) override; void do_reset() override; private: MachineConfig::HazardUnit hazard_unit; bool handle_data_hazards(); bool detect_mispredicted_jump() const; /** Some special instruction require that all issued instructions are committed before this * instruction is fetched and issued as it may rely on side-effects of uncommitted instructions. * Typical examples are csr modifying instructions. */ bool is_stall_requested() const; void handle_stall(const FetchInterstage &saved_if_id); /** * Typically, problem in execution is discovered in memory stage. This function flushed all * stages containing instructions, that would not execute in a single cycle CPU and continues * execution from given address. * * @param next_pc address to continue execution from */ void flush_and_continue_from_address(Address next_pc); }; class ExceptionHandler : public QObject { Q_OBJECT public: virtual bool handle_exception( Core *core, Registers *regs, ExceptionCause excause, Address inst_addr, Address next_addr, Address jump_branch_pc, Address mem_ref_addr) = 0; }; class StopExceptionHandler : public ExceptionHandler { Q_OBJECT public: bool handle_exception( Core *core, Registers *regs, ExceptionCause excause, Address inst_addr, Address next_addr, Address jump_branch_pc, Address mem_ref_addr) override; }; struct hwBreak { explicit hwBreak(Address addr) : addr(addr), flags(0), count(0) {}; Address addr; unsigned int flags; unsigned int count; }; } // namespace machine #endif // CORE_H ================================================ FILE: src/machine/core.test.cpp ================================================ #include "core.test.h" #include "machine/core.h" #include "machine/machineconfig.h" #include "machine/memory/backend/memory.h" #include "machine/memory/cache/cache.h" #include "machine/memory/memory_bus.h" #include "machine/predictor.h" #include using std::vector; using namespace machine; Q_DECLARE_METATYPE(Xlen) // NOLINT(performance-no-int-to-ptr) /** * Compiles program with no relocations into memory * * @param memory memory to save program to * @param init_pc address, where the compiled program will start * @param instructions vector of instructions * @return number of instruction compiled (number of steps, that can be executed) */ size_t compile_simple_program( FrontendMemory &memory, Address init_pc, const std::vector &instructions) { Address pc = init_pc; uint32_t code[2]; for (auto &instruction : instructions) { size_t size = Instruction::code_from_string(code, 8, instruction, pc); for (size_t i = 0; i < size; i += 4, pc += 4) { memory.write_u32(pc, code[i]); } } return (pc - init_pc) / 4; } template void test_program_with_single_result() { QFETCH(vector, instructions); QFETCH(Registers, registers); QFETCH(RegisterValue, x10_result); QFETCH(Xlen, xlen); Memory memory_backend(BIG); TrivialBus memory(&memory_backend); BranchPredictor predictor {}; CSR::ControlState controlst {}; Core core( ®isters, &predictor, &memory, &memory, &controlst, Xlen::_32, config_isa_word_default); size_t instruction_count = compile_simple_program(memory, 0x200_addr, instructions); if (typeid(Core) == typeid(CorePipelined)) { instruction_count += 3; } // finish pipeline for (size_t i = 0; i < instruction_count; i++) { core.step(); } QCOMPARE(registers.read_gp(10).as_xlen(xlen), x10_result.as_xlen(xlen)); } static void core_regs_data() { QSKIP("Switched RV."); QTest::addColumn("i"); QTest::addColumn("init"); QTest::addColumn("res"); // Note that we shouldn't be touching program counter as that is handled // automatically and differs if we use pipelining // Arithmetic instructions { Registers regs_init; regs_init.write_gp(24, 24); regs_init.write_gp(25, 12); Registers regs_res(regs_init); regs_res.write_gp(26, 36); // QTest::newRow("ADD") << Instruction(0, 24, 25, 26, 0, 32) << regs_init << // regs_res; QTest::newRow("ADDU") << Instruction(0, 24, 25, 26, 0, 33) << // regs_init // << regs_res; QTest::newRow("ADDI") << Instruction(8, 24, 26, 12) << regs_init // << regs_res; QTest::newRow("ADDIU") << Instruction(9, 24, 26, 12) << regs_init // << regs_res; regs_res.write_gp(26, 12); // QTest::newRow("SUB") << Instruction(0, 24, 25, 26, 0, 34) << regs_init << // regs_res; QTest::newRow("SUBU") << Instruction(0, 24, 25, 26, 0, 35) << // regs_init // << regs_res; } { Registers regs_init; regs_init.write_gp(24, 12); regs_init.write_gp(25, 24); Registers regs_res(regs_init); regs_res.write_gp(26, 1); // QTest::newRow("SLT") << Instruction(0, 24, 25, 26, 0, 42) << regs_init << // regs_res; QTest::newRow("SLTU") << Instruction(0, 24, 25, 26, 0, 43) << // regs_init // << regs_res; QTest::newRow("SLTI") << Instruction(10, 24, 26, 24) << regs_init // << regs_res; QTest::newRow("SLTIU") << Instruction(11, 24, 26, 24) << // regs_init << regs_res; } // Shift instructions { Registers regs_init; regs_init.write_gp(24, 0xf0); regs_init.write_gp(25, 3); Registers regs_res(regs_init); regs_res.write_gp(26, 0x780); // QTest::newRow("SLL") << Instruction(0, 0, 24, 26, 3, 0) << regs_init << // regs_res; QTest::newRow("SLLV") << Instruction(0, 25, 24, 26, 0, 4) << // regs_init << regs_res; regs_res.write_gp(26, 0x1e); // QTest::newRow("SLR") << Instruction(0, 0, 24, 26, 3, 2) << regs_init << // regs_res; QTest::newRow("SLRV") << Instruction(0, 25, 24, 26, 0, 6) << // regs_init << regs_res; } { Registers regs_init; regs_init.write_gp(24, 0x800000f0); regs_init.write_gp(25, 3); Registers regs_res(regs_init); // Cast is needed to correctly work with any internal size of register. regs_res.write_gp(26, (int32_t)0xF000001e); // QTest::newRow("SRA") << Instruction(0, 0, 24, 26, 3, 3) << regs_init << // regs_res; QTest::newRow("SRAV") << Instruction(0, 25, 24, 26, 0, 7) << // regs_init << regs_res; } // Logical instructions { Registers regs_init; regs_init.write_gp(24, 0xf0); regs_init.write_gp(25, 0xe1); Registers regs_res(regs_init); regs_res.write_gp(26, 0xe0); // QTest::newRow("AND") << Instruction(0, 24, 25, 26, 0, 36) << regs_init << // regs_res; QTest::newRow("ANDI") << Instruction(12, 24, 26, 0xe1) << regs_init // << regs_res; regs_res.write_gp(26, 0xf1); // QTest::newRow("OR") << Instruction(0, 24, 25, 26, 0, 37) << regs_init << // regs_res; QTest::newRow("ORI") << Instruction(13, 24, 26, 0xe1) << regs_init // << regs_res; regs_res.write_gp(26, 0x11); // QTest::newRow("XOR") << Instruction(0, 24, 25, 26, 0, 38) << regs_init << // regs_res; QTest::newRow("XORI") << Instruction(14, 24, 26, 0xe1) << regs_init // << regs_res; regs_res.write_gp(26, 0xffffff0e); // QTest::newRow("NOR") << Instruction(0, 24, 25, 26, 0, 39) << regs_init << // regs_res; regs_res.write_gp(26, 0xf00f0000); QTest::newRow("LUI") << // Instruction(15, 0, 26, 0xf00f) << regs_init << regs_res; } // Move instructions { Registers regs_init; Registers regs_res(regs_init); regs_res.write_gp(26, 24); // QTest::newRow("MFHI") << Instruction(0, 0, 0, 26, 0, 16) << regs_init << // regs_res; regs_res.write_gp(26, 28); // QTest::newRow("MFLO") << Instruction(0, 0, 0, 26, 0, 18) << regs_init << // regs_res; regs_res.write_gp(26, 0); // QTest::newRow("MTHI") << Instruction(0, 27, 0, 0, 0, 17) << regs_init << // regs_res; QTest::newRow("MTLO") << Instruction(0, 28, 0, 0, 0, 19) << // regs_init << regs_res; QTest::newRow("MOVZ-F") << Instruction(0, 24, 24, 25, // 0, 10) << regs_init << regs_res; QTest::newRow("MOVN-F") << Instruction(0, 24, // 1, 25, 0, 11) << regs_init // << regs_res; regs_res.write_gp(25, 55); // QTest::newRow("MOVZ-T") << Instruction(0, 24, 1, 25, 0, 10) << regs_init << // regs_res; QTest::newRow("MOVN-T") << Instruction(0, 24, 24, 25, 0, 11) << // regs_init << regs_res; } } void TestCore::singlecore_regs_data() { QSKIP("Switched ALU to RV."); core_regs_data(); } void TestCore::pipecore_regs_data() { QSKIP("Switched ALU to RV."); core_regs_data(); } void TestCore::singlecore_regs() { QSKIP("Switched ALU to RV."); QFETCH(Instruction, i); QFETCH(Registers, init); QFETCH(Registers, res); Memory mem(BIG); // Just memory (it shouldn't be used here except // instruction) TrivialBus mem_frontend(&mem); memory_write_u32(&mem, res.read_pc().get_raw(), i.data()); // Store single // instruction // (anything else // should be 0 so // NOP // effectively) Memory mem_used(mem); // Create memory copy TrivialBus mem_used_frontend(&mem); // CoreSingle core(&init, &mem_used_frontend, &mem_used_frontend, true, nullptr, // Xlen::_32); core.step(); // Single step should be enought as this is risc without // pipeline // core.step(); // res.pc_inc(); // res.pc_inc(); // We did single step so increment program counter accordingly QCOMPARE(init, res); // After doing changes from initial state this should // be same state as in case of passed expected result } void TestCore::pipecore_regs() { QSKIP("Switched ALU to RV."); QFETCH(Instruction, i); QFETCH(Registers, init); QFETCH(Registers, res); Memory mem(BIG); // Just memory (it shouldn't be used here except // instruction) TrivialBus mem_frontend(&mem); memory_write_u32(&mem, res.read_pc().get_raw(), i.data()); // Store single // instruction // (anything else // should be 0 so // NOP // effectively) Memory mem_used(mem); TrivialBus mem_used_frontend(&mem_used); res.write_pc(0x14_addr); // CorePipelined core( // &init, &mem_used_frontend, &mem_used_frontend, nullptr, MachineConfig::HU_NONE, 0, // nullptr, Xlen::_32); // for (int i = 0; i < 5; i++) { // core.step(); // Fire steps for five pipelines stages // } // // cout << "well:" << init.read_gp(26) << ":" << regs_used.read_gp(26) << endl; // QCOMPARE(init, res); // After doing changes from initial state this should // be same state as in case of passed expected result QCOMPARE(mem, mem_used); // There // should be // no change in // memory } static void core_jmp_data() { QSKIP("Switched RV."); QTest::addColumn("i"); QTest::addColumn("regs"); QTest::addColumn("pc"); Registers regs; regs.write_gp(14, -22); regs.write_gp(15, 22); regs.write_gp(16, -22); regs.write_gp(12, 0x80040000); // QTest::newRow("B") << Instruction(4, 0, 0, 61) << regs // << regs.read_pc().get_raw() + 4 + (61 << 2); // QTest::newRow("BEQ") << Instruction(4, 14, 16, 61) << regs // << regs.read_pc().get_raw() + 4 + (61 << 2); // QTest::newRow("BEQ-BACK") << Instruction(4, 14, 16, -4) << regs // << regs.read_pc().get_raw() + 4 - 16; // QTest::newRow("BNE") << Instruction(5, 14, 15, 61) << regs // << regs.read_pc().get_raw() + 4 + (61 << 2); // QTest::newRow("BGEZ") << Instruction(1, 15, 1, 61) << regs // << regs.read_pc().get_raw() + 4 + (61 << 2); // QTest::newRow("BGTZ") << Instruction(7, 15, 0, 61) << regs // << regs.read_pc().get_raw() + 4 + (61 << 2); // QTest::newRow("BLEZ") << Instruction(6, 14, 0, 61) << regs // << regs.read_pc().get_raw() + 4 + (61 << 2); // QTest::newRow("BLTZ") << Instruction(1, 14, 0, 61) << regs // << regs.read_pc().get_raw() + 4 + (61 << 2); // QTest::newRow("J") << Instruction(2, Address(24)) << regs // << Address(0x80000000).get_raw() + (24 << 2); // QTest::newRow("JR") << Instruction(0, 12, 0, 0, 0, 8) << regs << // Address(0x80040000).get_raw(); } void TestCore::singlecore_jmp_data() { QSKIP("Switched ALU to RV."); core_jmp_data(); } void TestCore::pipecore_jmp_data() { QSKIP("Switched ALU to RV."); core_jmp_data(); } void TestCore::singlecore_jmp() { QSKIP("Switched ALU to RV."); QFETCH(Instruction, i); QFETCH(Registers, regs); // QFETCH(uint64_t, pc); Memory mem(BIG); TrivialBus mem_frontend(&mem); memory_write_u32(&mem, regs.read_pc().get_raw(), i.data()); Memory mem_used(mem); TrivialBus mem_used_frontend(&mem_used); Registers regs_used(regs); // CoreSingle core( // ®s_used, &mem_used_frontend, &mem_used_frontend, true, nullptr, Xlen::_32); // core.step(); // QCOMPARE(regs.read_pc() + 4, regs_used.read_pc()); // First execute delay // slot // core.step(); // QCOMPARE(pc, regs_used.read_pc().get_raw()); // Now do jump // // QCOMPARE(mem, mem_used); // There should be no change in memory // regs_used.pc_abs_jmp(regs.read_pc()); // Reset program counter before we do // registers compare // QCOMPARE(regs, regs_used); // There should be no change in registers now } void TestCore::pipecore_jmp() { QSKIP("Switched ALU to RV."); QFETCH(Instruction, i); QFETCH(Registers, regs); // QFETCH(uint64_t, pc); Memory mem(BIG); TrivialBus mem_frontend(&mem); memory_write_u32(&mem, regs.read_pc().get_raw(), i.data()); Memory mem_used(mem); TrivialBus mem_used_frontend(&mem_used); Registers regs_used(regs); // CorePipelined core( // ®s_used, &mem_used_frontend, &mem_used_frontend, nullptr, // MachineConfig::HU_NONE, 0, nullptr, Xlen::_32); // core.step(); // QCOMPARE(regs.read_pc() + 4, regs_used.read_pc()); // First just fetch // core.step(); // QCOMPARE(pc, regs_used.read_pc().get_raw()); // Now do jump // for (int i = 0; i < 3; i++) { // core.step(); // Follow up with three other steps to complete pipeline to // } // be sure that instruction has no side effects // // QCOMPARE(mem, mem_used); // There should be no change in memory // regs.pc_abs_jmp(Address(pc + 12)); // Set reference pc to three more // instructions later (where regs_used // should be) // QCOMPARE(regs, regs_used); // There should be no change in registers now // (except pc) } static void core_mem_data() { QTest::addColumn("i"); QTest::addColumn("regs_init"); QTest::addColumn("regs_res"); QTest::addColumn("mem_init"); QTest::addColumn("mem_res"); // Load { Memory mem(BIG); memory_write_u32(&mem, 0x24, 0xA3242526); Registers regs; regs.write_gp(1, 0x22); Registers regs_res(regs); // Cast to get proper sign extension. regs_res.write_gp(21, (int32_t)0xFFFFFFA3); // QTest::newRow("LB") << Instruction(32, 1, 21, 0x2) << regs << regs_res << mem // << mem; // Cast to get proper sign extension. // regs_res.write_gp(21, (int32_t)0xFFFFA324); // QTest::newRow("LH") << Instruction(33, 1, 21, 0x2) << regs << regs_res << mem // << mem; regs_res.write_gp(21, 0xA3242526); QTest::newRow("LW") << // Instruction(35, 1, 21, 0x2) << regs << regs_res << mem << mem; // regs_res.write_gp(21, 0x000000A3); QTest::newRow("LBU") << Instruction(36, 1, // 21, 0x2) << regs << regs_res << mem << mem; regs_res.write_gp(21, 0x0000A324); // QTest::newRow("LHU") << Instruction(37, 1, 21, 0x2) << regs << regs_res << mem // << mem; } // Store { Registers regs; regs.write_gp(1, 0x22); regs.write_gp(21, 0x23242526); Memory mem(BIG); memory_write_u8(&mem, 0x24, 0x26); // Note: store least significant byte // QTest::newRow("SB") << Instruction(40, 1, 21, 0x2) << regs << regs << // Memory(BIG) // << mem; memory_write_u16(&mem, 0x24, 0x2526); QTest::newRow("SH") << // Instruction(41, 1, 21, 0x2) << regs << regs << Memory(BIG) << mem; // memory_write_u32(&mem, 0x24, 0x23242526); // QTest::newRow("SW") << Instruction(43, 1, 21, 0x2) << regs << regs << // Memory(BIG) // << mem; } } void TestCore::singlecore_mem_data() { QSKIP("Switched ALU to RV."); core_mem_data(); } void TestCore::pipecore_mem_data() { QSKIP("Switched ALU to RV."); core_mem_data(); } void TestCore::singlecore_mem() { QSKIP("Switched ALU to RV."); QFETCH(Instruction, i); QFETCH(Registers, regs_init); QFETCH(Registers, regs_res); QFETCH(Memory, mem_init); QFETCH(Memory, mem_res); // Write instruction to both memories memory_write_u32(&mem_init, regs_init.read_pc().get_raw(), i.data()); memory_write_u32(&mem_res, regs_init.read_pc().get_raw(), i.data()); TrivialBus mem_init_frontend(&mem_init); // CoreSingle core( // ®s_init, &mem_init_frontend, &mem_init_frontend, true, nullptr, Xlen::_32); // core.step(); // core.step(); // // regs_res.pc_inc(); // regs_res.pc_inc(); // QCOMPARE(regs_init, regs_res); // QCOMPARE(mem_init, mem_res); } void TestCore::pipecore_mem() { QSKIP("Switched ALU to RV."); QFETCH(Instruction, i); QFETCH(Registers, regs_init); QFETCH(Registers, regs_res); QFETCH(Memory, mem_init); QFETCH(Memory, mem_res); // Write instruction to both memories memory_write_u32(&mem_init, regs_init.read_pc().get_raw(), i.data()); memory_write_u32(&mem_res, regs_init.read_pc().get_raw(), i.data()); TrivialBus mem_init_frontend(&mem_init); // CorePipelined core( // ®s_init, &mem_init_frontend, &mem_init_frontend, nullptr, // MachineConfig::HU_NONE, 0, nullptr, Xlen::_32); // for (int i = 0; i < 5; i++) { // core.step(); // Fire steps for five pipelines stages // } // // regs_res.pc_jmp(20); // QCOMPARE(regs_init, regs_res); // QCOMPARE(mem_init, mem_res); } /*======================================================================*/ static void core_alu_forward_data() { QTest::addColumn>("code"); QTest::addColumn("reg_init"); QTest::addColumn("reg_res"); // Note that we shouldn't be touching program counter as that is handled // automatically and differs if we use pipelining // Test forwarding of ALU operands { QVector code { // objdump --disassembler-options=no-aliases,numeric -d test-hazards // sed -n -e 's/^[ \t]*[^ \t]\+:[ \t]\+\([0-9a-f]\+\)[ \t]\+\([^ \t].*\)$/0x\1, \/\/ // \2/p' 0x00100113, // addi x2,x0,1 0x11100093, // addi x1,x0,273 0x22200093, // addi x1,x0,546 0x002081b3, // add x3,x1,x2 0x00208233, // add x4,x1,x2 0x00300113, // addi x2,x0,3 0x11100093, // addi x1,x0,273 0x22200093, // addi x1,x0,546 0x001102b3, // add x5,x2,x1 0x00110333, // add x6,x2,x1 0x00000013, // addi x0,x0,0 0x00000063, // beq x0,x0,10080 }; Registers regs_init; regs_init.write_pc(0x200_addr); Registers regs_res(regs_init); regs_res.write_gp(1, 0x222); regs_res.write_gp(2, 3); regs_res.write_gp(3, 0x223); regs_res.write_gp(4, 0x223); regs_res.write_gp(5, 0x225); regs_res.write_gp(6, 0x225); regs_res.write_pc(regs_init.read_pc() + 4 * code.length() - 4); QTest::newRow("alu_forward_1") << code << regs_init << regs_res; } // Test forwarding in JR and JALR { QVector code { // start: 0x01111537, // lui x10,0x1111 0x022225b7, // lui x11,0x2222 0x03333637, // lui x12,0x3333 0x034000ef, // jal x1,240 0x00a00733, // add x14,x0,x10 0x0140006f, // jal x0,228 0x44400413, // addi x8,x0,1092 0x55500913, // addi x18,x0,1365 0x66600993, // addi x19,x0,1638 0x77700a13, // addi x20,x0,1911 // skip: 0x00000297, // auipc x5,0x0 0x02828293, // addi x5,x5,40 # 250 0x000280e7, // jalr x1,0(x5) 0x2cd00513, // addi x10,x0,717 0x00050493, // addi x9,x10,0 # 1111000 <__global_pointer$+0x1108400> 0x01c0006f, // jal x0,258 // fnc_add: 0x00b50533, // add x10,x10,x11 0x00c50533, // add x10,x10,x12 0x00008067, // jalr x0,0(x1) 0x7f100693, // addi x13,x0,2033 // fnc_short 0x00008067, // jalr x0,0(x1) 0x7f200693, // addi x13,x0,2034 // test_end: 0x00000013, // addi x0,x0,0 0x00000013, // addi x0,x0,0 // loop: 0x00000063, // beq x0,x0,260 }; Registers regs_init; regs_init.write_pc(0x200_addr); Registers regs_res(regs_init); regs_res.write_gp(1, 0x00000234); regs_res.write_gp(5, 0x00000250); regs_res.write_gp(9, 0x000002cd); regs_res.write_gp(10, 0x000002cd); regs_res.write_gp(11, 0x02222000); regs_res.write_gp(12, 0x03333000); regs_res.write_gp(14, 0x06666000); regs_res.write_pc(regs_init.read_pc() + 4 * code.length() - 4); QTest::newRow("j_jal_jalr") << code << regs_init << regs_res; } // Test multiplication and division { QVector code { 0x12345137, // lui x2,0x12345 0x67810113, // addi x2,x2,1656 0xabcdf1b7, // lui x3,0xabcdf 0xf0118193, // addi x3,x3,-255 0x02310833, // mul x16,x2,x3 0x023118b3, // mulh x17,x2,x3 0x02310933, // mul x18,x2,x3 0x023139b3, // mulhu x19,x2,x3 0x0221ca33, // div x20,x3,x2 0x0221eab3, // rem x21,x3,x2 0x0221db33, // divu x22,x3,x2 0x0221fbb3, // remu x23,x3,x2 0x0000006f, // jal x0,230 }; Registers regs_init; regs_init.write_pc(0x80020000_addr); Registers regs_res(regs_init); uint32_t val_a = 0x12345678; uint32_t val_b = 0xabcdef01; uint64_t val_u64; int64_t val_s64; regs_res.write_gp(2, (int32_t)val_a); regs_res.write_gp(3, (int32_t)val_b); val_s64 = (int64_t)(int32_t)val_a * (int32_t)val_b; regs_res.write_gp(16, (int32_t)(val_s64 & 0xffffffff)); regs_res.write_gp(17, (int32_t)(val_s64 >> 32)); val_u64 = (uint64_t)val_a * val_b; regs_res.write_gp(18, (int32_t)(val_u64 & 0xffffffff)); regs_res.write_gp(19, (int32_t)(val_u64 >> 32)); regs_res.write_gp(20, (int32_t)((int32_t)val_b / (int32_t)val_a)); regs_res.write_gp(21, (int32_t)((int32_t)val_b % (int32_t)val_a)); regs_res.write_gp(22, val_b / val_a); regs_res.write_gp(23, val_b % val_a); regs_res.write_pc(regs_init.read_pc() + 4 * code.length() - 4); QTest::newRow("mul-div") << code << regs_init << regs_res; } // branches { QVector code { 0xfff00093, // addi x1,x0,-1 0x00100113, // addi x2,x0,1 0x00000193, // addi x3,x0,0 0x00200213, // addi x4,x0,2 0x00100293, // addi x5,x0,1 0x0000ca63, // blt x1,x0,228 0x00000013, // addi x0,x0,0 0x00000293, // addi x5,x0,0 0x10018193, // addi x3,x3,256 0x001181b3, // add x3,x3,x1 0x00100293, // addi x5,x0,1 0x00105a63, // bge x0,x1,240 0x00000013, // addi x0,x0,0 0x00000293, // addi x5,x0,0 0x01018193, // addi x3,x3,16 0x001181b3, // add x3,x3,x1 0x00100293, // addi x5,x0,1 0x0000da63, // bge x1,x0,258 0x00000013, // addi x0,x0,0 0x00000293, // addi x5,x0,0 0x20018193, // addi x3,x3,512 0x001181b3, // add x3,x3,x1 0x00100293, // addi x5,x0,1 0x00104a63, // blt x0,x1,270 0x00000013, // addi x0,x0,0 0x00000293, // addi x5,x0,0 0x02018193, // addi x3,x3,32 0x001181b3, // add x3,x3,x1 0x00100293, // addi x5,x0,1 0x00209a63, // bne x1,x2,288 0x00000013, // addi x0,x0,0 0x00000293, // addi x5,x0,0 0x40018193, // addi x3,x3,1024 0x001181b3, // add x3,x3,x1 0x00100293, // addi x5,x0,1 0x00208a63, // beq x1,x2,2a0 0x00000013, // addi x0,x0,0 0x00000293, // addi x5,x0,0 0x04018193, // addi x3,x3,64 0x001181b3, // add x3,x3,x1 0x00108093, // addi x1,x1,1 0xf64096e3, // bne x1,x4,210 0x00000013, // addi x0,x0,0 0x00000013, // addi x0,x0,0 0x00000013, // addi x0,x0,0 0x00000063, // beq x0,x0,2b4 }; Registers regs_init; regs_init.write_pc(0x200_addr); Registers regs_res(regs_init); regs_res.write_gp(1, 2); regs_res.write_gp(2, 1); regs_res.write_gp(3, 0x8d0); regs_res.write_gp(4, 2); regs_res.write_gp(5, 1); regs_res.write_pc(regs_init.read_pc() + 4 * code.length()); QTest::newRow("branch_conditions_test") << code << regs_init << regs_res; } } void TestCore::singlecore_alu_forward_data() { core_alu_forward_data(); } void TestCore::pipecore_alu_forward_data() { core_alu_forward_data(); } void TestCore::pipecorestall_alu_forward_data() { core_alu_forward_data(); } static void run_code_fragment( Core &core, Registers ®_init, Registers ®_res, Memory &mem_init, Memory &mem_res, QVector &code) { uint64_t addr = reg_init.read_pc().get_raw(); foreach (uint32_t i, code) { memory_write_u32(&mem_init, addr, i); memory_write_u32(&mem_res, addr, i); addr += 4; } for (int k = 10000; k; k--) { core.step(); // Single step should be enought as this is risc without // pipeline if (reg_init.read_pc() == reg_res.read_pc() && k > 6) { // reached end // of // the code // fragment k = 6; // add some cycles to finish processing } } reg_res.write_pc(reg_init.read_pc()); // We do not compare result pc QCOMPARE(reg_init, reg_res); // After doing changes from initial state this // should be same state as in case of passed // expected result QCOMPARE(mem_init, mem_res); // There should be no change in memory } void TestCore::singlecore_alu_forward() { QFETCH(QVector, code); QFETCH(Registers, reg_init); QFETCH(Registers, reg_res); Memory mem_init(LITTLE); TrivialBus mem_init_frontend(&mem_init); Memory mem_res(LITTLE); TrivialBus mem_res_frontend(&mem_res); BranchPredictor predictor {}; CSR::ControlState controlst {}; CoreSingle core( ®_init, &predictor, &mem_init_frontend, &mem_init_frontend, &controlst, Xlen::_32, config_isa_word_default); run_code_fragment(core, reg_init, reg_res, mem_init, mem_res, code); } void TestCore::pipecore_alu_forward() { QFETCH(QVector, code); QFETCH(Registers, reg_init); QFETCH(Registers, reg_res); Memory mem_init(LITTLE); TrivialBus mem_init_frontend(&mem_init); Memory mem_res(LITTLE); TrivialBus mem_res_frontend(&mem_res); BranchPredictor predictor {}; CSR::ControlState controlst {}; CorePipelined core( ®_init, &predictor, &mem_init_frontend, &mem_init_frontend, &controlst, Xlen::_32, config_isa_word_default, MachineConfig::HazardUnit::HU_STALL_FORWARD); run_code_fragment(core, reg_init, reg_res, mem_init, mem_res, code); } void TestCore::pipecorestall_alu_forward() { QFETCH(QVector, code); QFETCH(Registers, reg_init); QFETCH(Registers, reg_res); Memory mem_init(LITTLE); TrivialBus mem_init_frontend(&mem_init); Memory mem_res(LITTLE); TrivialBus mem_res_frontend(&mem_res); BranchPredictor predictor {}; CSR::ControlState controlst {}; CorePipelined core( ®_init, &predictor, &mem_init_frontend, &mem_init_frontend, &controlst, Xlen::_32, config_isa_word_default, MachineConfig::HazardUnit::HU_STALL); run_code_fragment(core, reg_init, reg_res, mem_init, mem_res, code); } /*======================================================================*/ static void core_memory_tests_data() { QTest::addColumn>("code"); QTest::addColumn("reg_init"); QTest::addColumn("reg_res"); QTest::addColumn("mem_init"); QTest::addColumn("mem_res"); // Test { QVector code { // objdump --disassembler-options=no-aliases,numeric -d test-hazards // sed -n -e 's/^[ \t]*[^ \t]\+:[ \t]\+\([0-9a-f]\+\)[ \t]\+\([^ \t].*\)$/0x\1, \/\/ // \2/p' // _start: 0x40000513, // addi x10,x0,1024 0x00000413, // addi x8,x0,0 0x03c00493, // addi x9,x0,60 0x00800933, // add x18,x0,x8 // main_cycle: 0x04940a63, // beq x8,x9,264 0x008502b3, // add x5,x10,x8 0x0002aa03, // lw x20,0(x5) 0x000409b3, // add x19,x8,x0 0x00040933, // add x18,x8,x0 // inner_cycle: 0x02990263, // beq x18,x9,248 0x012502b3, // add x5,x10,x18 0x0002aa83, // lw x21,0(x5) 0x015a22b3, // slt x5,x20,x21 0x00029663, // bne x5,x0,240 0x00090993, // addi x19,x18,0 0x000a8a13, // addi x20,x21,0 // not_minimum: 0x00490913, // addi x18,x18,4 0xfe1ff06f, // jal x0,224 // inner_cycle_end: 0x008502b3, // add x5,x10,x8 0x0002aa83, // lw x21,0(x5) 0x0142a023, // sw x20,0(x5) 0x013502b3, // add x5,x10,x19 0x0152a023, // sw x21,0(x5) 0x00440413, // addi x8,x8,4 0xfb1ff06f, // jal x0,210 // main_cycle_end: 0x0ff0000f, // fence iorw,iorw 0x00000013, // addi x0,x0,0 // the mai_cycle loop end has 0x00000013, // addi x0,x0,0 // to be separated because else fetch 0x00000013, // addi x0,x0,0 // reaches stop address prematurely 0x00000063, // beq x0,x0,10080 }; QVector data_init { 5, 3, 4, 1, 15, 8, 9, 2, 10, 6, 11, 1, 6, 9, 12 }; QVector data_res { 1, 1, 2, 3, 4, 5, 6, 6, 8, 9, 9, 10, 11, 12, 15 }; Registers regs_init; regs_init.write_pc(0x200_addr); Registers regs_res(regs_init); regs_res.write_gp(5, 0x438); regs_res.write_gp(8, 0x3c); regs_res.write_gp(9, 0x3c); regs_res.write_gp(10, 0x400); regs_res.write_gp(18, 0x3c); regs_res.write_gp(19, 0x38); regs_res.write_gp(20, 0xf); regs_res.write_gp(21, 0xf); regs_res.write_pc(regs_init.read_pc() + 4 * code.length() - 4); uint32_t addr; Memory mem_init(LITTLE); addr = 0x400; foreach (uint32_t i, data_init) { memory_write_u32(&mem_init, addr, i); addr += 4; } Memory mem_res(LITTLE); addr = 0x400; foreach (uint32_t i, data_res) { memory_write_u32(&mem_res, addr, i); addr += 4; } QTest::newRow("cache_insert_sort") << code << regs_init << regs_res << mem_init << mem_res; } // unaligned lw a lw and sw { QVector code { 0xaabbdd37, // lui x26,0xaabbd 0xcddd0d13, // addi x26,x26,-803 0x000d0113, // addi x2,x26,0 0x000d0193, // addi x3,x26,0 0x000d0213, // addi x4,x26,0 0x000d0293, // addi x5,x26,0 0x000d0313, // addi x6,x26,0 0x000d0393, // addi x7,x26,0 0x000d0413, // addi x8,x26,0 0x000d0493, // addi x9,x26,0 0x000d0513, // addi x10,x26,0 0x80020db7, // lui x27,0x80020 0x100d8d93, // addi x27,x27,256 0x000da103, // lw x2,0(x27) 0x001da183, // lw x3,1(x27) 0x002da203, // lw x4,2(x27) 0x003da283, // lw x5,3(x27) 0x01ada023, // sw x26,0(x27) 0x01ada2a3, // sw x26,5(x27) 0x01ada523, // sw x26,10(x27) 0x01ada7a3, // sw x26,15(x27) 0x000da503, // lw x10,0(x27) 0x004da583, // lw x11,4(x27) 0x008da603, // lw x12,8(x27) 0x00cda683, // lw x13,12(x27) 0x010da703, // lw x14,16(x27) 0x014da783, // lw x15,20(x27) 0x018da803, // lw x16,24(x27) 0x01cda883, // lw x17,28(x27) 0x020da903, // lw x18,32(x27) 0x020da983, // lw x19,32(x27) 0x0ff0000f, // fence iorw,iorw 0x00000013, // addi x0,x0,0 0x00000013, // addi x0,x0,0 0xfe000ce3, // beq x0,x0,27c }; Registers regs_init; regs_init.write_pc(0x200_addr); Registers regs_res(regs_init); regs_res.write_gp(2, (int32_t)0x04030201); regs_res.write_gp(3, (int32_t)0x05040302); regs_res.write_gp(4, (int32_t)0x06050403); regs_res.write_gp(5, (int32_t)0x07060504); regs_res.write_gp(6, (int32_t)0xaabbccdd); regs_res.write_gp(7, (int32_t)0xaabbccdd); regs_res.write_gp(8, (int32_t)0xaabbccdd); regs_res.write_gp(9, (int32_t)0xaabbccdd); regs_res.write_gp(10, (int32_t)0xaabbccdd); regs_res.write_gp(11, (int32_t)0xbbccdd05); regs_res.write_gp(12, (int32_t)0xccdd0aaa); regs_res.write_gp(13, (int32_t)0xdd0faabb); regs_res.write_gp(14, (int32_t)0x14aabbcc); regs_res.write_gp(15, (int32_t)0x18171615); regs_res.write_gp(16, (int32_t)0x1c1b1a19); regs_res.write_gp(17, (int32_t)0x101f1e1d); regs_res.write_gp(18, (int32_t)0x24232221); regs_res.write_gp(19, (int32_t)0x24232221); regs_res.write_gp(26, (int32_t)0xaabbccdd); regs_res.write_gp(27, (int32_t)0x80020100); uint32_t addr; Memory mem_init(LITTLE); addr = 0x80020100; QVector data_init { 0x04030201, 0x08070605, 0x0c0b0a09, 0x000f0e0d, 0x14131211, 0x18171615, 0x1c1b1a19, 0x101f1e1d, 0x24232221, 0x28272625, 0x2c2b2a29, 0x202f2e2d }; foreach (uint32_t i, data_init) { memory_write_u32(&mem_init, addr, i); addr += 4; } Memory mem_res(LITTLE); addr = 0x80020100; QVector data_res { 0xaabbccdd, 0xbbccdd05, 0xccdd0aaa, 0xdd0faabb, 0x14aabbcc, 0x18171615, 0x1c1b1a19, 0x101f1e1d, 0x24232221, 0x28272625, 0x2c2b2a29, 0x202f2e2d }; foreach (uint32_t i, data_res) { memory_write_u32(&mem_res, addr, i); addr += 4; } regs_res.write_pc(regs_init.read_pc() + 4 * code.length() - 4); QTest::newRow("lw_sw_unaligned_be") << code << regs_init << regs_res << mem_init << mem_res; } } void TestCore::singlecore_memory_tests_data() { core_memory_tests_data(); } void TestCore::pipecore_nc_memory_tests_data() { core_memory_tests_data(); } void TestCore::pipecore_wt_na_memory_tests_data() { core_memory_tests_data(); } void TestCore::pipecore_wt_a_memory_tests_data() { core_memory_tests_data(); } void TestCore::pipecore_wb_memory_tests_data() { core_memory_tests_data(); } void TestCore::singlecore_memory_tests() { QFETCH(QVector, code); QFETCH(Registers, reg_init); QFETCH(Registers, reg_res); QFETCH(Memory, mem_init); QFETCH(Memory, mem_res); TrivialBus mem_init_frontend(&mem_init); TrivialBus mem_res_frontend(&mem_res); BranchPredictor predictor {}; CSR::ControlState controlst {}; CoreSingle core( ®_init, &predictor, &mem_init_frontend, &mem_init_frontend, &controlst, Xlen::_32, config_isa_word_default); run_code_fragment(core, reg_init, reg_res, mem_init, mem_res, code); } void TestCore::pipecore_nc_memory_tests() { QFETCH(QVector, code); QFETCH(Registers, reg_init); QFETCH(Registers, reg_res); QFETCH(Memory, mem_init); QFETCH(Memory, mem_res); TrivialBus mem_init_frontend(&mem_init); TrivialBus mem_res_frontend(&mem_res); BranchPredictor predictor {}; CSR::ControlState controlst {}; CorePipelined core( ®_init, &predictor, &mem_init_frontend, &mem_init_frontend, &controlst, Xlen::_32, config_isa_word_default); run_code_fragment(core, reg_init, reg_res, mem_init, mem_res, code); } void TestCore::pipecore_wt_na_memory_tests() { QFETCH(QVector, code); QFETCH(Registers, reg_init); QFETCH(Registers, reg_res); QFETCH(Memory, mem_init); QFETCH(Memory, mem_res); TrivialBus mem_init_frontend(&mem_init); TrivialBus mem_res_frontend(&mem_res); CacheConfig cache_conf; cache_conf.set_enabled(true); cache_conf.set_set_count(2); // Number of sets cache_conf.set_block_size(1); // Number of blocks cache_conf.set_associativity(2); // Degree of associativity cache_conf.set_replacement_policy(CacheConfig::RP_LRU); cache_conf.set_write_policy(CacheConfig::WP_THROUGH_NOALLOC); Cache i_cache(&mem_init_frontend, &cache_conf); Cache d_cache(&mem_init_frontend, &cache_conf); BranchPredictor predictor {}; CSR::ControlState controlst {}; CorePipelined core( ®_init, &predictor, &i_cache, &d_cache, &controlst, Xlen::_32, config_isa_word_default); run_code_fragment(core, reg_init, reg_res, mem_init, mem_res, code); } void TestCore::pipecore_wt_a_memory_tests() { QFETCH(QVector, code); QFETCH(Registers, reg_init); QFETCH(Registers, reg_res); QFETCH(Memory, mem_init); QFETCH(Memory, mem_res); TrivialBus mem_init_frontend(&mem_init); TrivialBus mem_res_frontend(&mem_res); CacheConfig cache_conf; cache_conf.set_enabled(true); cache_conf.set_set_count(2); // Number of sets cache_conf.set_block_size(1); // Number of blocks cache_conf.set_associativity(2); // Degree of associativity cache_conf.set_replacement_policy(CacheConfig::RP_LRU); cache_conf.set_write_policy(CacheConfig::WP_THROUGH_ALLOC); Cache i_cache(&mem_init_frontend, &cache_conf); Cache d_cache(&mem_init_frontend, &cache_conf); BranchPredictor predictor {}; CSR::ControlState controlst {}; CorePipelined core( ®_init, &predictor, &i_cache, &d_cache, &controlst, Xlen::_32, config_isa_word_default); run_code_fragment(core, reg_init, reg_res, mem_init, mem_res, code); } void TestCore::pipecore_wb_memory_tests() { QFETCH(QVector, code); QFETCH(Registers, reg_init); QFETCH(Registers, reg_res); QFETCH(Memory, mem_init); QFETCH(Memory, mem_res); TrivialBus mem_init_frontend(&mem_init); TrivialBus mem_res_frontend(&mem_res); CacheConfig cache_conf; cache_conf.set_enabled(true); cache_conf.set_set_count(4); // Number of sets cache_conf.set_block_size(2); // Number of blocks cache_conf.set_associativity(2); // Degree of associativity cache_conf.set_replacement_policy(CacheConfig::RP_LRU); cache_conf.set_write_policy(CacheConfig::WP_BACK); Cache i_cache(&mem_init_frontend, &cache_conf); Cache d_cache(&mem_init_frontend, &cache_conf); BranchPredictor predictor {}; CSR::ControlState controlst {}; CorePipelined core( ®_init, &predictor, &i_cache, &d_cache, &controlst, Xlen::_32, config_isa_word_default); run_code_fragment(core, reg_init, reg_res, mem_init, mem_res, code); } void extension_m_data() { QTest::addColumn>("instructions"); QTest::addColumn("registers"); QTest::addColumn("x10_result"); QTest::addColumn("xlen"); Registers registers {}; registers.write_gp(1, 1111111); registers.write_gp(2, 7); QTest::addRow("mul") << vector { "mul x10, x1, x2", "nop" } << registers << RegisterValue { 7777777 } << Xlen::_32; registers.write_gp(1, 7777777); QTest::addRow("div") << vector { "div x10, x1, x2", "nop" } << registers << RegisterValue { 1111111 } << Xlen::_32; registers.write_gp(2, 1000); QTest::addRow("rem") << vector { "rem x10, x1, x2", "nop" } << registers << RegisterValue { 777 } << Xlen::_32; registers.write_gp(1, 15); registers.write_gp(2, -10); QTest::addRow("mulh 15x-10") << vector { "mulh x10, x1, x2", "nop" } << registers << RegisterValue { (uint64_t)0xffffffffffffffffULL } << Xlen::_32; QTest::addRow("mulhu 15x-10") << vector { "mulhu x10, x1, x2", "nop" } << registers << RegisterValue { 14 } << Xlen::_32; QTest::addRow("mulhsu 15x-10") << vector { "mulhsu x10, x1, x2", "nop" } << registers << RegisterValue { 14 } << Xlen::_32; QTest::addRow("mulh -10x15") << vector { "mulh x10, x2, x1", "nop" } << registers << RegisterValue { (uint64_t)0xffffffffffffffffULL } << Xlen::_32; QTest::addRow("mulhu -10x15") << vector { "mulhu x10, x2, x1", "nop" } << registers << RegisterValue { 14 } << Xlen::_32; QTest::addRow("mulhsu -10x15") << vector { "mulhsu x10, x2, x1", "nop" } << registers << RegisterValue { (uint64_t)0xffffffffffffffffULL } << Xlen::_32; registers.write_gp(1, -1); registers.write_gp(2, 0x10000); QTest::addRow("divu") << vector { "divu x10, x1, x2", "nop" } << registers << RegisterValue { 0xFFFF } << Xlen::_32; QTest::addRow("remu") << vector { "remu x10, x1, x2", "nop" } << registers << RegisterValue { 0xFFFF } << Xlen::_32; } // Extensions: // ================================================================================================= // RV32M void TestCore::singlecore_extension_m_data() { extension_m_data(); } void TestCore::pipecore_extension_m_data() { extension_m_data(); } void TestCore::singlecore_extension_m() { test_program_with_single_result(); } void TestCore::pipecore_extension_m() { test_program_with_single_result(); } QTEST_APPLESS_MAIN(TestCore) ================================================ FILE: src/machine/core.test.h ================================================ #ifndef CORE_TEST_H #define CORE_TEST_H #include class TestCore : public QObject { Q_OBJECT private slots: // Not ported to RV: void singlecore_regs(); void singlecore_regs_data(); void pipecore_regs(); void pipecore_regs_data(); void singlecore_jmp(); void singlecore_jmp_data(); void pipecore_jmp(); void pipecore_jmp_data(); void singlecore_mem_data(); void singlecore_mem(); void pipecore_mem(); void pipecore_mem_data(); void singlecore_alu_forward(); void singlecore_alu_forward_data(); void pipecore_alu_forward(); void pipecore_alu_forward_data(); void pipecorestall_alu_forward(); void pipecorestall_alu_forward_data(); void singlecore_memory_tests_data(); void pipecore_nc_memory_tests_data(); void pipecore_wt_na_memory_tests_data(); void pipecore_wt_a_memory_tests_data(); void pipecore_wb_memory_tests_data(); void singlecore_memory_tests(); void pipecore_nc_memory_tests(); void pipecore_wt_na_memory_tests(); void pipecore_wt_a_memory_tests(); void pipecore_wb_memory_tests(); // Extensions: // ============================================================================================= // RV32M void singlecore_extension_m_data(); void pipecore_extension_m_data(); void singlecore_extension_m(); void pipecore_extension_m(); }; #endif // CORE_TEST_H ================================================ FILE: src/machine/csr/address.h ================================================ #ifndef QTRVSIM_CSR_ADDRESS_H #define QTRVSIM_CSR_ADDRESS_H #include "common/math/bit_ops.h" #include "simulator_exception.h" #include namespace machine { namespace CSR { /** * Spec vol. 2: Table 2.1 */ enum class PrivilegeLevel { UNPRIVILEGED = 0b00, //> Unprivileged and User-Level CSRs, unimplemented SUPERVISOR = 0b01, //> Supervisor-Level CSRs, unimplemented HYPERVISOR = 0b10, //> Hypervisor and VS CSRs, unimplemented MACHINE = 0b11, //> Machine-Level CSRs }; struct Address { constexpr explicit Address(uint16_t address) : data(address) { SANITY_ASSERT( address < (1 << 12), "CSR register address is out of the ISA " "specified range (12bits)"); } constexpr Address(const Address &other) = default; constexpr Address &operator=(const Address &other) = default; uint16_t data; /* * By convention, the upper 4 bits of the CSR address (csr[11:8]) are used to encode the * read and write accessibility of the CSRs according to privilege level as shown in Table * 2.1. */ /** The top two bits (csr[11:10]) indicate whether the register is read/write * (00, 01, or 10) or read-only (11). */ [[nodiscard]] constexpr bool is_writable() const { return get_bits(data, 11, 10) != 0b11; } /** * The next two bits (csr[9:8]) encode the lowest privilege level that can access the CSR. */ [[nodiscard]] constexpr PrivilegeLevel get_privilege_level() const { return static_cast(get_bits(data, 9, 8)); } bool operator<(const Address &rhs) const { return data < rhs.data; } bool operator>(const Address &rhs) const { return rhs < *this; } bool operator<=(const Address &rhs) const { return !(rhs < *this); } bool operator>=(const Address &rhs) const { return !(*this < rhs); } bool operator==(const Address &rhs) const { return data == rhs.data; } bool operator!=(const Address &rhs) const { return data != rhs.data; } }; constexpr Address operator""_csr(unsigned long long literal) { return Address(literal); } }} // namespace machine::CSR template<> struct std::hash { std::size_t operator()(machine::CSR::Address const &addr) const noexcept { return std::hash {}(addr.data); } }; #endif // QTRVSIM_CSR_ADDRESS_H ================================================ FILE: src/machine/csr/controlstate.cpp ================================================ #include "controlstate.h" #include "common/logging.h" #include "machinedefs.h" #include "simulator_exception.h" #include #include LOG_CATEGORY("machine.csr.control_state"); namespace machine { namespace CSR { ControlState::ControlState(Xlen xlen, ConfigIsaWord isa_word) : xlen(xlen) { reset(); uint64_t misa = read_internal(CSR::Id::MISA).as_u64(); misa |= isa_word.toUnsigned(); register_data[CSR::Id::MISA] = misa; } ControlState::ControlState(const ControlState &other) : QObject(this->parent()) , xlen(other.xlen) , register_data(other.register_data) {} void ControlState::reset() { std::transform( REGISTERS.begin(), REGISTERS.end(), register_data.begin(), [](const RegisterDesc &desc) { return desc.initial_value; }); uint64_t misa = read_internal(CSR::Id::MISA).as_u64(); misa &= 0x3fffffff; if (xlen == Xlen::_32) { misa |= (uint64_t)1 << 30; } else if (xlen == Xlen::_64) { misa |= (uint64_t)2 << 62; } register_data[CSR::Id::MISA] = misa; if (xlen == Xlen::_64) { write_field_raw(Field::mstatus::UXL, 2); write_field_raw(Field::mstatus::SXL, 2); } } size_t ControlState::get_register_internal_id(Address address) { // if (address.get_privilege_level() != PrivilegeLevel::MACHINE) try { return CSR::REGISTER_MAP.at(address); } catch (std::out_of_range &e) { throw SIMULATOR_EXCEPTION( UnsupportedInstruction, QString("Accessed nonexistent CSR register %1").arg(address.data), ""); } } RegisterValue ControlState::read(Address address, PrivilegeLevel current_priv) const { size_t reg_id = get_register_internal_id(address); PrivilegeLevel required = address.get_privilege_level(); if (current_priv < required) { throw SIMULATOR_EXCEPTION( UnsupportedInstruction, QString("CSR address %1 not accessible at current privilege level.") .arg(address.data), ""); } RegisterValue value = register_data[reg_id]; DEBUG("Read CSR[%u] == 0x%" PRIx64, address.data, value.as_u64()); emit read_signal(reg_id, value); return value; } void ControlState::write(Address address, RegisterValue value, PrivilegeLevel current_priv) { DEBUG( "Write CSR[%u/%zu] <== 0x%zu", address.data, get_register_internal_id(address), value.as_u64()); // Attempts to write a read-only register also raise illegal instruction exceptions. if (!address.is_writable()) { throw SIMULATOR_EXCEPTION( UnsupportedInstruction, QString("CSR address %1 is not writable.").arg(address.data), ""); } PrivilegeLevel required = address.get_privilege_level(); if (current_priv < required) { throw SIMULATOR_EXCEPTION( UnsupportedInstruction, QString("CSR address %1 not writable at current privilege level.").arg(address.data), ""); } write_internal(get_register_internal_id(address), value); } void ControlState::default_wlrl_write_handler( const RegisterDesc &desc, RegisterValue ®, RegisterValue val) { uint64_t u; u = val.as_u64() & desc.write_mask.as_u64(); u |= reg.as_u64() & ~desc.write_mask.as_u64(); if (xlen == Xlen::_32) u &= 0xffffffff; reg = u; } void ControlState::mstatus_wlrl_write_handler( const RegisterDesc &desc, RegisterValue ®, RegisterValue val) { default_wlrl_write_handler(desc, reg, val); } void ControlState::mcycle_wlrl_write_handler( const RegisterDesc &desc, RegisterValue ®, RegisterValue val) { Q_UNUSED(desc) reg = val; register_data[Id::CYCLE] = val; write_signal(Id::CYCLE, register_data[Id::CYCLE]); } void ControlState::sstatus_wlrl_write_handler( const RegisterDesc &desc, RegisterValue ®, RegisterValue val) { uint64_t s_mask = Field::mstatus::SIE.mask() | Field::mstatus::SPIE.mask() | Field::mstatus::SPP.mask(); if (xlen == Xlen::_64) { s_mask |= Field::mstatus::UXL.mask(); s_mask |= Field::mstatus::SXL.mask(); } uint64_t write_val = val.as_u64() & desc.write_mask.as_u64(); uint64_t mstatus_val = register_data[Id::MSTATUS].as_u64(); mstatus_val = (mstatus_val & ~s_mask) | (write_val & s_mask); register_data[Id::MSTATUS] = mstatus_val; uint64_t new_sstatus = mstatus_val & s_mask; if (xlen == Xlen::_32) new_sstatus &= 0xffffffff; reg = new_sstatus; emit write_signal(Id::MSTATUS, register_data[Id::MSTATUS]); } bool ControlState::operator==(const ControlState &other) const { return register_data == other.register_data; } bool ControlState::operator!=(const ControlState &c) const { return !this->operator==(c); } void ControlState::update_exception_cause(enum ExceptionCause excause) { RegisterValue &value = register_data[Id::MCAUSE]; if (excause != EXCAUSE_INT) { value = static_cast(excause); } else { RegisterValue mie = register_data[Id::MIE]; RegisterValue mip = register_data[Id::MIP]; int irq_to_signal = 0; quint64 irqs = mie.as_u64() & mip.as_u64() & 0xffffffff; if (irqs != 0) { // Find the first (leas significant) set bit irq_to_signal = 63 - qCountLeadingZeroBits(irqs & (~irqs + 1)); } value = (uint64_t)(irq_to_signal | ((uint64_t)1 << ((xlen == Xlen::_32) ? 31 : 63))); } emit write_signal(Id::MCAUSE, value); } void ControlState::set_interrupt_signal(uint irq_num, bool active) { if (irq_num >= 32) { return; } uint64_t mask = 1 << irq_num; size_t reg_id = Id::MIP; RegisterValue &value = register_data[reg_id]; if (active) { value = value.as_xlen(xlen) | mask; } else { value = value.as_xlen(xlen) & ~mask; } emit write_signal(reg_id, value); } bool ControlState::core_interrupt_request() { RegisterValue mie = register_data[Id::MIE]; RegisterValue mip = register_data[Id::MIP]; uint64_t irqs = mie.as_u64() & mip.as_u64() & 0xffffffff; return irqs && read_field(Field::mstatus::MIE).as_u64(); } void ControlState::exception_initiate(PrivilegeLevel act_privlev, PrivilegeLevel to_privlev) { size_t reg_id = Id::MSTATUS; RegisterValue ® = register_data[reg_id]; Q_UNUSED(act_privlev) Q_UNUSED(to_privlev) write_field(Field::mstatus::MPIE, read_field(Field::mstatus::MIE).as_u32()); write_field(Field::mstatus::MIE, (uint64_t)0); write_field(Field::mstatus::MPP, static_cast(act_privlev)); emit write_signal(reg_id, reg); } PrivilegeLevel ControlState::exception_return( enum PrivilegeLevel act_privlev, enum PrivilegeLevel xret_privlev) { size_t reg_id = Id::MSTATUS; RegisterValue ® = register_data[reg_id]; PrivilegeLevel restored_privlev = PrivilegeLevel::MACHINE; if (xret_privlev == PrivilegeLevel::MACHINE) { // MRET semantics: // MIE <- MPIE // MPIE <- 1 // restored_privlev <- MPP // MPP <- 0 write_field(Field::mstatus::MIE, read_field(Field::mstatus::MPIE).as_u32()); write_field(Field::mstatus::MPIE, (uint64_t)1); uint32_t raw_mpp = static_cast(read_field(Field::mstatus::MPP).as_u32()) & 0x3; switch (raw_mpp) { case 0: restored_privlev = PrivilegeLevel::UNPRIVILEGED; break; case 1: restored_privlev = PrivilegeLevel::SUPERVISOR; break; case 2: restored_privlev = PrivilegeLevel::HYPERVISOR; break; case 3: restored_privlev = PrivilegeLevel::MACHINE; break; default: restored_privlev = PrivilegeLevel::UNPRIVILEGED; break; } write_field(Field::mstatus::MPP, (uint64_t)0); // clear MPP per spec } else if (xret_privlev == PrivilegeLevel::SUPERVISOR) { // SRET semantics: // SIE <- SPIE // SPIE <- 1 // restored_privlev <- SPP // SPP <- 0 write_field(Field::mstatus::SIE, read_field(Field::mstatus::SPIE).as_u32()); write_field(Field::mstatus::SPIE, (uint64_t)1); uint32_t raw_spp = static_cast(read_field(Field::mstatus::SPP).as_u32()) & 0x1; restored_privlev = (raw_spp == 1) ? PrivilegeLevel::SUPERVISOR : PrivilegeLevel::UNPRIVILEGED; write_field(Field::mstatus::SPP, (uint64_t)0); } else { restored_privlev = PrivilegeLevel::UNPRIVILEGED; } // If the instruction was executed in M-mode and the restored privilege is less-privileged // than M, clear MPRV per the privileged spec. if (act_privlev == PrivilegeLevel::MACHINE && restored_privlev != PrivilegeLevel::MACHINE) { write_field(Field::mstatus::MPRV, (uint64_t)0); } emit write_signal(reg_id, reg); return restored_privlev; } machine::Address ControlState::exception_pc_address() { return machine::Address(register_data[Id::MTVEC].as_u64()); } RegisterValue ControlState::read_internal(size_t internal_id) const { return register_data[internal_id]; } void ControlState::write_internal(size_t internal_id, RegisterValue value) { RegisterDesc desc = REGISTERS[internal_id]; RegisterValue ® = register_data[internal_id]; (this->*desc.write_handler)(desc, reg, value); write_signal(internal_id, reg); } void ControlState::increment_internal(size_t internal_id, uint64_t amount) { auto value = register_data[internal_id]; write_internal(internal_id, value.as_u64() + amount); } }} // namespace machine::CSR ================================================ FILE: src/machine/csr/controlstate.h ================================================ #ifndef CONTROLSTATE_H #define CONTROLSTATE_H #include "bitfield.h" #include "common/math/bit_ops.h" #include "config_isa.h" #include "csr/address.h" #include "machinedefs.h" #include "register_value.h" #include "simulator_exception.h" #include #include #include #include namespace machine { namespace CSR { /** CSR register names mapping the registers to continuous locations in internal buffer */ struct Id { enum IdxType { // Unprivileged Counter/Timers CYCLE, // Machine Information Registers MVENDORID, MARCHID, MIMPID, MHARTID, // MCONFIGPTR, // Machine Trap Setup MSTATUS, MISA, // MEDELEG, // MIDELET, MIE, MTVEC, // MCOUNTERN, // MSTATUSH, // Machine Trap Handling MSCRATCH, MEPC, MCAUSE, MTVAL, MIP, MTINST, MTVAL2, // ... MCYCLE, MINSTRET, // Supervisor Trap Setup SSTATUS, // ... STVEC, // ... // Supervisor Trap Handling SSCRATCH, SEPC, SCAUSE, STVAL, // ... // Supervisor Protection and Translation SATP, _COUNT }; }; struct RegisterDesc; struct RegisterFieldDesc { uint64_t decode(uint64_t val) const { return field.decode(val); } uint64_t encode(uint64_t val) const { return field.encode(val); } uint64_t mask() const { return field.mask(); } uint64_t update(uint64_t orig, uint64_t val) const { return field.encode(val) | (orig & ~mask()); } const char *name = "unknown"; const Id::IdxType regId; const BitField field; const char *description = ""; }; /** * This class provides access to state of CSR registers. * * Registers are externally addressed by 12bit address. To simplify simulation, all existing * registers are stored in continuous array and indexed by "internal id" (not stable). */ class ControlState : public QObject { Q_OBJECT public: ControlState(Xlen xlen = Xlen::_32, ConfigIsaWord isa_word = 0); ControlState(const ControlState &); /** Read CSR register with ISA specified address. */ [[nodiscard]] RegisterValue read(Address address, PrivilegeLevel current_priv) const; /** * Read CSR register with an internal id. * * Internal id can be obtained from enum Id and works as an index to compacted table * of existing CSR registers. */ [[nodiscard]] RegisterValue read_internal(size_t internal_id) const; /** Write value to CSR register by ISA specified address and receive the previous value. */ void write(Address address, RegisterValue value, PrivilegeLevel current_priv); /** Used for writes occurring as a side-effect (instruction count update...) and * internally by the write method. */ void write_internal(size_t internal_id, RegisterValue value); /** Shorthand for counter incrementing. Counters like time might have different increment * amount. */ void increment_internal(size_t internal_id, uint64_t amount); /** Reset data to initial values */ void reset(); /** Read CSR register field */ RegisterValue read_field(const RegisterFieldDesc &field_desc) const { return field_desc.decode(read_internal(field_desc.regId).as_u64()); } /** Write CSR register field */ void write_field(const RegisterFieldDesc &field_desc, uint64_t value) { uint64_t u = read_internal(field_desc.regId).as_u64(); u = field_desc.update(u, value); write_internal(field_desc.regId, u); } void update_exception_cause(enum ExceptionCause excause); bool operator==(const ControlState &other) const; bool operator!=(const ControlState &c) const; bool core_interrupt_request(); machine::Address exception_pc_address(); signals: void write_signal(size_t internal_reg_id, RegisterValue val); void read_signal(size_t internal_reg_id, RegisterValue val) const; public slots: void set_interrupt_signal(uint irq_num, bool active); void exception_initiate(PrivilegeLevel act_privlev, PrivilegeLevel to_privlev); PrivilegeLevel exception_return(enum PrivilegeLevel act_privlev, enum PrivilegeLevel xret_privlev); private: static size_t get_register_internal_id(Address address); /** Write CSR register field without write handler, read-only masking and signal */ void write_field_raw(const RegisterFieldDesc &field_desc, uint64_t value) { uint64_t u = register_data[field_desc.regId].as_u64(); u = field_desc.update(u, value); register_data[field_desc.regId] = u; } Xlen xlen = Xlen::_32; // TODO /** * Compacted table of existing CSR registers data. Each item is described by table * REGISTERS at corresponding indexes. */ std::array register_data; public: void default_wlrl_write_handler(const RegisterDesc &desc, RegisterValue ®, RegisterValue val); void mstatus_wlrl_write_handler(const RegisterDesc &desc, RegisterValue ®, RegisterValue val); void mcycle_wlrl_write_handler(const RegisterDesc &desc, RegisterValue ®, RegisterValue val); void sstatus_wlrl_write_handler(const RegisterDesc &desc, RegisterValue ®, RegisterValue val); }; struct RegisterDesc { using WriteHandlerFn = void ( ControlState::*)(const RegisterDesc &desc, RegisterValue ®, RegisterValue val); const char *name = "unknown"; Address address = Address(0); const char *description = ""; RegisterValue initial_value = 0; RegisterValue write_mask = (register_storage_t)0xffffffffffffffff; WriteHandlerFn write_handler = &ControlState::default_wlrl_write_handler; struct { const RegisterFieldDesc *const *array; const unsigned count; } fields = { nullptr, 0 }; }; namespace Field { namespace mstatus { static constexpr RegisterFieldDesc SIE = { "SIE", Id::MSTATUS, { 1, 1 }, "System global interrupt-enable" }; static constexpr RegisterFieldDesc MIE = { "MIE", Id::MSTATUS, { 1, 3 }, "Machine global interrupt-enable" }; static constexpr RegisterFieldDesc SPIE = { "SPIE", Id::MSTATUS, { 1, 5 }, "Previous SIE before the trap" }; static constexpr RegisterFieldDesc MPIE = { "MPIE", Id::MSTATUS, { 1, 7 }, "Previous MIE before the trap" }; static constexpr RegisterFieldDesc MPRV = { "MPRV", Id::MSTATUS, { 1, 17 }, "Modify privilege for loads/stores/fetches" }; static constexpr RegisterFieldDesc SPP = { "SPP", Id::MSTATUS, { 1, 8 }, "System previous privilege mode" }; static constexpr RegisterFieldDesc MPP = { "MPP", Id::MSTATUS, { 2, 11 }, "Machine previous privilege mode" }; static constexpr RegisterFieldDesc UXL = { "UXL", Id::MSTATUS, { 2, 32 }, "User mode XLEN (RV64 only)" }; static constexpr RegisterFieldDesc SXL = { "SXL", Id::MSTATUS, { 2, 34 }, "Supervisor mode XLEN (RV64 only)" }; static constexpr const RegisterFieldDesc *fields[] = { &SIE, &MIE, &SPIE, &MPIE, &SPP, &MPP, &UXL, &SXL }; static constexpr unsigned count = sizeof(fields) / sizeof(fields[0]); } // namespace mstatus namespace satp { static constexpr RegisterFieldDesc MODE = { "MODE", Id::SATP, { 1, 31 }, "Address translation mode" }; static constexpr RegisterFieldDesc ASID = { "ASID", Id::SATP, { 9, 22 }, "Address-space ID" }; static constexpr RegisterFieldDesc PPN = { "PPN", Id::SATP, { 22, 0 }, "Root page-table physical page number" }; static constexpr const RegisterFieldDesc *fields[] = { &MODE, &ASID, &PPN }; static constexpr unsigned count = sizeof(fields) / sizeof(fields[0]); } // namespace satp } // namespace Field /** Definitions of supported CSR registers */ inline constexpr std::array REGISTERS { { // Unprivileged Counter/Timers [Id::CYCLE] = { "cycle", 0xC00_csr, "Cycle counter for RDCYCLE instruction.", 0, 0 }, // Priviledged Machine Mode Registers [Id::MVENDORID] = { "mvendorid", 0xF11_csr, "Vendor ID.", 0, 0 }, [Id::MARCHID] = { "marchid", 0xF12_csr, "Architecture ID.", 0, 0 }, [Id::MIMPID] = { "mimpid", 0xF13_csr, "Implementation ID.", 0, 0 }, [Id::MHARTID] = { "mhardid", 0xF14_csr, "Hardware thread ID." }, [Id::MSTATUS] = { "mstatus", 0x300_csr, "Machine status register.", 0, 0x007FFFEA, &ControlState::mstatus_wlrl_write_handler, { Field::mstatus::fields, Field::mstatus::count } }, [Id::MISA] = { "misa", 0x301_csr, "Machine ISA Register.", 0, 0 }, [Id::MIE] = { "mie", 0x304_csr, "Machine interrupt-enable register.", 0, 0x00ff0AAA }, [Id::MTVEC] = { "mtvec", 0x305_csr, "Machine trap-handler base address." }, [Id::MSCRATCH] = { "mscratch", 0x340_csr, "Scratch register for machine trap handlers." }, [Id::MEPC] = { "mepc", 0x341_csr, "Machine exception program counter." }, [Id::MCAUSE] = { "mcause", 0x342_csr, "Machine trap cause." }, [Id::MTVAL] = { "mtval", 0x343_csr, "Machine bad address or instruction." }, [Id::MIP] = { "mip", 0x344_csr, "Machine interrupt pending.", 0, 0x00000222 }, [Id::MTINST] = { "mtinst", 0x34A_csr, "Machine trap instruction (transformed)." }, [Id::MTVAL2] = { "mtval2", 0x34B_csr, "Machine bad guest physical address." }, // Machine Counter/Timers [Id::MCYCLE] = { "mcycle", 0xB00_csr, "Machine cycle counter.", 0, (register_storage_t)0xffffffffffffffff, &ControlState::mcycle_wlrl_write_handler }, [Id::MINSTRET] = { "minstret", 0xB02_csr, "Machine instructions-retired counter." }, // Supervisor-level CSRs [Id::SSTATUS] = { "sstatus", 0x100_csr, "Supervisor status register.", 0, 0xffffffff, &ControlState::sstatus_wlrl_write_handler }, [Id::STVEC] = { "stvec", 0x105_csr, "Supervisor trap-handler base address." }, [Id::SSCRATCH] = { "sscratch", 0x140_csr, "Scratch register for supervisor trap handlers." }, [Id::SEPC] = { "sepc", 0x141_csr, "Supervisor exception program counter." }, [Id::SCAUSE] = { "scause", 0x142_csr, "Supervisor trap cause." }, [Id::STVAL] = { "stval", 0x143_csr, "Supervisor bad address or instruction." }, [Id::SATP] = { "satp", 0x180_csr, "Supervisor address translation and protection", 0, 0xffffffff, &ControlState::default_wlrl_write_handler, { Field::satp::fields, Field::satp::count } } } }; /** Lookup from CSR address (value used in instruction) to internal id (index in continuous * memory) */ class RegisterMap { bool initialized = false; std::unordered_map map; void init() { for (size_t i = 0; i < REGISTERS.size(); i++) { map.emplace(REGISTERS[i].address, i); } initialized = true; } public: size_t at(Address address) { if (!initialized) init(); return map.at(address); } }; class RegisterMapByName { bool initialized = false; std::unordered_map map; void init() { for (size_t i = 0; i < REGISTERS.size(); i++) { map.emplace(std::string(REGISTERS[i].name), i); } initialized = true; } public: size_t at(std::string name) { if (!initialized) init(); return map.at(name); } }; static RegisterMap REGISTER_MAP; static RegisterMapByName REGISTER_MAP_BY_NAME; }} // namespace machine::CSR Q_DECLARE_METATYPE(machine::CSR::ControlState) #endif // CONTROLSTATE_H ================================================ FILE: src/machine/execute/alu.cpp ================================================ #include "alu.h" #include "common/polyfills/mulh64.h" namespace machine { RegisterValue alu_combined_operate( AluCombinedOp op, AluComponent component, bool w_operation, bool modified, RegisterValue a, RegisterValue b) { switch (component) { case AluComponent::ALU: return (w_operation) ? alu32_operate(op.alu_op, modified, a, b) : alu64_operate(op.alu_op, modified, a, b); case AluComponent::MUL: return (w_operation) ? mul32_operate(op.mul_op, a, b) : mul64_operate(op.mul_op, a, b); case AluComponent::PASS: return a; default: qDebug("ERROR, unknown alu component: %hhx", uint8_t(component)); return 0; } } /** * Shift operations are limited to shift by 31 bits. * Other bits of the operand may be used as flags and need to be masked out * before any ALU operation is performed. */ constexpr uint64_t SHIFT_MASK32 = 0b011111; // == 31 constexpr uint64_t SHIFT_MASK64 = 0b111111; // == 63 int64_t alu64_operate(AluOp op, bool modified, RegisterValue a, RegisterValue b) { uint64_t _a = a.as_u64(); uint64_t _b = b.as_u64(); switch (op) { case AluOp::ADD: return _a + ((modified) ? -_b : _b); case AluOp::SLL: return _a << (_b & SHIFT_MASK64); case AluOp::SLT: return a.as_i64() < b.as_i64(); case AluOp::SLTU: return _a < _b; case AluOp::XOR: return _a ^ _b; // Most compilers should calculate SRA correctly, but it is UB. case AluOp::SR: return (modified) ? (a.as_i64() >> (_b & SHIFT_MASK64)) : (_a >> (_b & SHIFT_MASK64)); case AluOp::OR: return _a | _b; case AluOp::AND: return ((modified) ? ~_a : _a) & _b; // Modified: clear bits of b using mask // in a default: qDebug("ERROR, unknown alu operation: %hhx", uint8_t(op)); return 0; } } int32_t alu32_operate(AluOp op, bool modified, RegisterValue a, RegisterValue b) { uint32_t _a = a.as_u32(); uint32_t _b = b.as_u32(); switch (op) { case AluOp::ADD: return _a + ((modified) ? -_b : _b); case AluOp::SLL: return _a << (_b & SHIFT_MASK32); case AluOp::SLT: return a.as_i32() < b.as_i32(); case AluOp::SLTU: return _a < _b; case AluOp::XOR: return _a ^ _b; // Most compilers should calculate SRA correctly, but it is UB. case AluOp::SR: return (modified) ? (a.as_i32() >> (_b & SHIFT_MASK32)) : (_a >> (_b & SHIFT_MASK32)); case AluOp::OR: return _a | _b; case AluOp::AND: return ((modified) ? ~_a : _a) & _b; // Modified: clear bits of b using mask in a default: qDebug("ERROR, unknown alu operation: %hhx", uint8_t(op)); return 0; } } int64_t mul64_operate(MulOp op, RegisterValue a, RegisterValue b) { switch (op) { case MulOp::MUL: return a.as_u64() * b.as_u64(); case MulOp::MULH: return mulh64(a.as_i64(), b.as_i64()); case MulOp::MULHSU: return mulhsu64(a.as_i64(), b.as_u64()); case MulOp::MULHU: return mulhu64(a.as_u64(), b.as_u64()); case MulOp::DIV: if (b.as_i64() == 0) { return -1; // Division by zero is defined. } else if (a.as_i64() == INT64_MIN && b.as_i64() == -1) { return INT64_MIN; // Overflow. } else { return a.as_i64() / b.as_i64(); } case MulOp::DIVU: return (b.as_u64() == 0) ? UINT64_MAX // Division by zero is defined. : a.as_u64() / b.as_u64(); case MulOp::REM: if (b.as_i64() == 0) { return a.as_i64(); // Division by zero is defined. } else if (a.as_i64() == INT64_MIN && b.as_i64() == -1) { return 0; // Overflow. } else { return a.as_i64() % b.as_i64(); } case MulOp::REMU: return (b.as_u64() == 0) ? a.as_u64() // Division by zero reminder // is defined. : a.as_u64() % b.as_u64(); default: qDebug("ERROR, unknown multiplication operation: %hhx", uint8_t(op)); return 0; } } int32_t mul32_operate(MulOp op, RegisterValue a, RegisterValue b) { switch (op) { case MulOp::MUL: return a.as_u32() * b.as_u32(); case MulOp::MULH: return ((uint64_t)a.as_i32() * (uint64_t)b.as_i32()) >> 32; case MulOp::MULHSU: return ((uint64_t)a.as_i32() * (uint64_t)b.as_u32()) >> 32; case MulOp::MULHU: return ((uint64_t)a.as_u32() * (uint64_t)b.as_u32()) >> 32; case MulOp::DIV: if (b.as_i32() == 0) { return -1; // Division by zero is defined. } else if (a.as_i32() == INT32_MIN && b.as_i32() == -1) { return INT32_MIN; // Overflow. } else { return a.as_i32() / b.as_i32(); } case MulOp::DIVU: return (b.as_u32() == 0) ? UINT32_MAX // Division by zero is defined. : a.as_u32() / b.as_u32(); case MulOp::REM: if (b.as_i32() == 0) { return a.as_i32(); // Division by zero is defined. } else if (a.as_i32() == INT32_MIN && b.as_i32() == -1) { return 0; // Overflow. } else { return a.as_i32() % b.as_i32(); } case MulOp::REMU: return (b.as_u32() == 0) ? a.as_u32() // Division by zero reminder // is defined. : a.as_u32() % b.as_u32(); default: qDebug("ERROR, unknown multiplication operation: %hhx", uint8_t(op)); return 0; } } } // namespace machine ================================================ FILE: src/machine/execute/alu.h ================================================ #ifndef ALU_H #define ALU_H #include "execute/alu_op.h" #include "execute/mul_op.h" #include "register_value.h" #include namespace machine { /** * Components available in combined ALU. */ enum class AluComponent { ALU, //> RV32/64I MUL, //> RV32/64M PASS, //> Pass operand A without change (used for AMO) }; union AluCombinedOp { AluOp alu_op; MulOp mul_op; }; /** * Dispatcher for specialised ALUs * * @param op alu and mul operands are isomorphic * @param component specifies which specialization to use * @param w_operation word operation false=64b, true=32b * @param modified see alu64/32 * @param a operand 1 * @param b operand 2 * @return result of specified ALU operation (always, no traps) */ [[gnu::const]] RegisterValue alu_combined_operate( AluCombinedOp op, AluComponent component, bool w_operation, bool modified, RegisterValue a, RegisterValue b); /** * RV64I for OP and OP-IMM instructions * * ALU conforming to Base Integer Instruction Set, Version 2.0. * * @param op operation specifier (funct3 in instruction) * @param modified modifies behavior of ADD (to SUB) and SRL (to SRA) * encoded by bit 30 if applicable * @param a operand 1 * @param b operand 2 * @return result of specified ALU operation (always, no traps) * integer type is returned to ensure correct signe extension * to arbitrary implementation of RegisterValue */ [[gnu::const]] int64_t alu64_operate(AluOp op, bool modified, RegisterValue a, RegisterValue b); /** * RV32I for OP and OP-IMM instructions and RV64I OP-32 and OP-IMM-32 * * ALU conforming to Base Integer Instruction Set, Version 2.0. * * @param op operation specifier (funct3 in instruction) * @param modified modifies behavior of ADD (to SUB) and SRL (to SRA) * encoded by bit 30 if applicable * @param a operand 1 * @param b operand 2 * @return result of specified ALU operation (always, no traps) * integer type is returned to ensure correct signe extension * to arbitrary implementation of RegisterValue */ [[gnu::const]] int32_t alu32_operate(AluOp op, bool modified, RegisterValue a, RegisterValue b); /** * RV64 "M" for OP instructions * * Multiplier conforming to Standard Extension for Integer Multiplication and * Division, Version 2.0. * * Implements operation for instructions: MUL, MUL[[S]H], DIV[U], REM[U]. * Division by zero is defined §7.2 table 7.1 (or see implementation). * * @param op operation specifier (funct3 in the instruction) * @param a operand 1 * @param b operand 2 * @return result of specified operation (always, no traps) * integer type is returned to ensure correct signe extension * to arbitrary implementation of RegisterValue */ [[gnu::const]] int64_t mul64_operate(MulOp op, RegisterValue a, RegisterValue b); /** * RV32 "M" for OP instructions and RV64 "M" for OP-32 instructions * * Multiplier conforming to Standard Extension for Integer Multiplication and * Division, Version 2.0. * * Implements operation for instructions: * RV32: MUL, MUL[[S]H]W, DIV[U]W, REM[U]W. * RV64: MULW, MUL[[S]H]W, DIV[U], REM[U]. * * Division by zero is defined §7.2 table 7.1 (or see implementation). * * @param op operation specifier (funct3 in the instruction) * @param a operand 1 * @param b operand 2 * @return result of specified operation (always, no traps) * integer type is returned to ensure correct signe extension * to arbitrary implementation of RegisterValue */ [[gnu::const]] int32_t mul32_operate(MulOp op, RegisterValue a, RegisterValue b); } // namespace machine #endif // ALU_H ================================================ FILE: src/machine/execute/alu.test.cpp ================================================ #include "alu.h" #include "alu.test.h" #include "common/polyfills/mulh64.h" #include #include using namespace machine; void TestAlu::test_alu64_operate_data() { QTest::addColumn("op"); QTest::addColumn("modified"); QTest::addColumn("operand_a"); QTest::addColumn("operand_b"); QTest::addColumn("result"); QTest::addRow("ADD") << AluOp::ADD << false << int64_t(0xFFFFFFFFFFFFFFFFULL) << int64_t(1) << int64_t(0); QTest::addRow("ADD") << AluOp::ADD << false << int64_t(123) << int64_t(123000) << int64_t(123123); QTest::addRow("SUB") << AluOp::ADD << true << int64_t(123123) << int64_t(123000) << int64_t(123); QTest::addRow("SLT") << AluOp::SLT << false << int64_t(123123) << int64_t(123000) << int64_t(0); QTest::addRow("SLT") << AluOp::SLT << false << int64_t(-123123) << int64_t(123000) << int64_t(1); QTest::addRow("SLTU") << AluOp::SLTU << false << int64_t(-123123) << int64_t(123000) << int64_t(0); QTest::addRow("SLTU") << AluOp::SLTU << false << int64_t(123123) << int64_t(123000000) << int64_t(1); QTest::addRow("XOR") << AluOp::XOR << false << int64_t(0xFFFFFFFF00000000ULL) << int64_t(0xFFFFFFFFFFFFFFFFULL) << int64_t(0x00000000FFFFFFFFULL); QTest::addRow("OR") << AluOp::OR << false << int64_t(0xFFFFFFFF00000000ULL) << int64_t(0xFFFFFFFFFFFFFFFFULL) << int64_t(0xFFFFFFFFFFFFFFFFULL); QTest::addRow("AND") << AluOp::AND << false << int64_t(0xFFFFFFFF00000000ULL) << int64_t(0xFFFFFFFFFFFFFFFFULL) << int64_t(0xFFFFFFFF00000000ULL); QTest::addRow("SRL") << AluOp::SR << false << int64_t(0xFFFFFFFF00000000ULL) << int64_t(0xFFFFFFFF00000010ULL) << int64_t(0x0000FFFFFFFF0000ULL); QTest::addRow("SRA") << AluOp::SR << true << int64_t(0xFFFFFFFF00000000ULL) << int64_t(0xFFFFFFFF00000010ULL) << int64_t(0xFFFFFFFFFFFF0000ULL); } void TestAlu::test_alu64_operate() { QFETCH(AluOp, op); QFETCH(bool, modified); QFETCH(int64_t, operand_a); QFETCH(int64_t, operand_b); QFETCH(int64_t, result); // Test unit itself. QCOMPARE(alu64_operate(op, modified, operand_a, operand_b), result); // Test that combined wrapper does not break anything. QCOMPARE( alu_combined_operate( { .alu_op = op }, AluComponent::ALU, false, modified, operand_a, operand_b), RegisterValue(result)); } void TestAlu::test_alu32_operate_data() { QTest::addColumn("op"); QTest::addColumn("modified"); QTest::addColumn("operand_a"); QTest::addColumn("operand_b"); QTest::addColumn("result"); QTest::addRow("ADD") << AluOp::ADD << false << int32_t(0xFFFFFFFF) << int32_t(1) << int32_t(0); QTest::addRow("ADD") << AluOp::ADD << false << int32_t(123) << int32_t(123000) << int32_t(123123); QTest::addRow("SUB") << AluOp::ADD << true << int32_t(123123) << int32_t(123000) << int32_t(123); QTest::addRow("SLT") << AluOp::SLT << false << int32_t(123123) << int32_t(123000) << int32_t(0); QTest::addRow("SLT") << AluOp::SLT << false << int32_t(-123123) << int32_t(123000) << int32_t(1); QTest::addRow("SLTU") << AluOp::SLTU << false << int32_t(-123123) << int32_t(123000) << int32_t(0); QTest::addRow("SLTU") << AluOp::SLTU << false << int32_t(123123) << int32_t(123000000) << int32_t(1); QTest::addRow("XOR") << AluOp::XOR << false << int32_t(0xFFFF0000) << int32_t(0xFFFFFFFF) << int32_t(0x0000FFFF); QTest::addRow("OR") << AluOp::OR << false << int32_t(0xFFFF0000) << int32_t(0xFFFFFFFF) << int32_t(0xFFFFFFFF); QTest::addRow("AND") << AluOp::AND << false << int32_t(0xFFFF0000) << int32_t(0xFFFFFFFF) << int32_t(0xFFFF0000); QTest::addRow("SRL") << AluOp::SR << false << int32_t(0xFFFF0000) << int32_t(0xFFFF0010) << int32_t(0x0000FFFF); QTest::addRow("SRA") << AluOp::SR << true << int32_t(0xFFFF0000) << int32_t(0xFFFF0010) << int32_t(0xFFFFFFFF); } void TestAlu::test_alu32_operate() { QFETCH(AluOp, op); QFETCH(bool, modified); QFETCH(int32_t, operand_a); QFETCH(int32_t, operand_b); QFETCH(int32_t, result); // Test unit itself. QCOMPARE(alu32_operate(op, modified, operand_a, operand_b), result); // Test that combined wrapper does not break anything. QCOMPARE( alu_combined_operate( { .alu_op = op }, AluComponent::ALU, true, modified, operand_a, operand_b), RegisterValue(result)); } // TODO evaluate the results and inline as literals. constexpr std::array, 6> inputs = { { { 1, 1 }, { 2, 1 }, { 2, 2 }, { 123456789, 666 }, { 123456789, -7777 }, { -1, 8888 }, } }; void TestAlu::test_mul64_operate_data() { QTest::addColumn("op"); QTest::addColumn("operand_a"); QTest::addColumn("operand_b"); QTest::addColumn("result"); for (auto input : inputs) { int64_t a = std::get<0>(input); int64_t b = std::get<1>(input); QTest::addRow("MUL") << MulOp::MUL << a << b << a * b; QTest::addRow("MULH") << MulOp::MULH << a << b << int64_t(mulh64(a, b)); QTest::addRow("MULHU") << MulOp::MULHU << a << b << int64_t(mulhu64(a, b)); QTest::addRow("MULHSU") << MulOp::MULHSU << a << b << int64_t(mulhsu64(a, b)); QTest::addRow("DIV") << MulOp::DIV << a << b << a / b; QTest::addRow("DIVU") << MulOp::DIVU << a << b << int64_t(uint64_t(a) / uint64_t(b)); QTest::addRow("REM") << MulOp::REM << a << b << (a % b); QTest::addRow("REMU") << MulOp::REMU << a << b << int64_t(uint64_t(a) % uint64_t(b)); } // Defined edge cases for division QTest::addRow("division by zero signed") << MulOp::DIV << int64_t(42) << int64_t(0) << int64_t(-1); QTest::addRow("division by zero unsigned") << MulOp::DIVU << int64_t(128) << int64_t(0) << int64_t(UINT64_MAX); QTest::addRow("division by zero reminder signed") << MulOp::REM << int64_t(666) << int64_t(0) << int64_t(666); QTest::addRow("division by zero reminder unsigned") << MulOp::REMU << int64_t(777) << int64_t(0) << int64_t(777); QTest::addRow("zero division by zero signed") << MulOp::DIV << int64_t(0) << int64_t(0) << int64_t(-1); QTest::addRow("zero division by zero unsigned") << MulOp::DIVU << int64_t(0) << int64_t(0) << int64_t(UINT64_MAX); QTest::addRow("zero division by zero reminder signed") << MulOp::REM << int64_t(0) << int64_t(0) << (int64_t)0; QTest::addRow("zero division by zero reminder unsigned") << MulOp::REMU << int64_t(0) << int64_t(0) << (int64_t)0; QTest::addRow("division overflow") << MulOp::DIV << int64_t(INT64_MIN) << int64_t(-1) << INT64_MIN; QTest::addRow("division reminder overflow") << MulOp::REM << int64_t(INT64_MAX) << int64_t(-1) << int64_t(0); } void TestAlu::test_mul64_operate() { QFETCH(MulOp, op); QFETCH(int64_t, operand_a); QFETCH(int64_t, operand_b); QFETCH(int64_t, result); // Test unit itself. QCOMPARE(mul64_operate(op, operand_a, operand_b), result); // Test that combined wrapper does not break anything. QCOMPARE( alu_combined_operate( (AluCombinedOp) { .mul_op = op }, AluComponent::MUL, false, false, operand_a, operand_b), RegisterValue(result)); } /** * Helper function for upper bits of 32 bit multiplication. * Sign extension is handled on caller side. */ constexpr int32_t mulh32(uint64_t a, uint64_t b) { return (a * b) >> 32; } void TestAlu::test_mul32_operate_data() { QTest::addColumn("op"); QTest::addColumn("operand_a"); QTest::addColumn("operand_b"); QTest::addColumn("result"); for (auto input : inputs) { int32_t a = std::get<0>(input); int32_t b = std::get<1>(input); QTest::addRow("MUL") << MulOp::MUL << a << b << a * b; QTest::addRow("MULH") << MulOp::MULH << a << b << int32_t(mulh32(a, b)); QTest::addRow("MULHU") << MulOp::MULHU << a << b << int32_t(mulh32(uint32_t(a), uint32_t(b))); QTest::addRow("MULHSU") << MulOp::MULHSU << a << b << int32_t(mulh32(a, uint32_t(b))); QTest::addRow("DIV") << MulOp::DIV << a << b << a / b; QTest::addRow("DIVU") << MulOp::DIVU << a << b << int32_t(uint32_t(a) / uint32_t(b)); QTest::addRow("REM") << MulOp::REM << a << b << (a % b); QTest::addRow("REMU") << MulOp::REMU << a << b << int32_t(uint32_t(a) % uint32_t(b)); } // Defined edge cases for division QTest::addRow("division by zero signed") << MulOp::DIV << int32_t(42) << int32_t(0) << int32_t(-1); QTest::addRow("division by zero unsigned") << MulOp::DIVU << int32_t(128) << int32_t(0) << int32_t(UINT32_MAX); QTest::addRow("division by zero reminder signed") << MulOp::REM << int32_t(666) << int32_t(0) << int32_t(666); QTest::addRow("division by zero reminder unsigned") << MulOp::REMU << int32_t(777) << int32_t(0) << int32_t(777); QTest::addRow("zero division by zero signed") << MulOp::DIV << int32_t(0) << int32_t(0) << int32_t(-1); QTest::addRow("zero division by zero unsigned") << MulOp::DIVU << int32_t(0) << int32_t(0) << int32_t(UINT32_MAX); QTest::addRow("zero division by zero reminder signed") << MulOp::REM << int32_t(0) << int32_t(0) << (int32_t)0; QTest::addRow("zero division by zero reminder unsigned") << MulOp::REMU << int32_t(0) << int32_t(0) << (int32_t)0; QTest::addRow("division overflow") << MulOp::DIV << int32_t(INT32_MIN) << int32_t(-1) << INT32_MIN; QTest::addRow("division reminder overflow") << MulOp::REM << int32_t(INT32_MAX) << int32_t(-1) << int32_t(0); } void TestAlu::test_mul32_operate() { QFETCH(MulOp, op); QFETCH(int32_t, operand_a); QFETCH(int32_t, operand_b); QFETCH(int32_t, result); // Test unit itself. QCOMPARE(mul32_operate(op, operand_a, operand_b), result); // Test that combined wrapper does not break anything. QCOMPARE( alu_combined_operate( (AluCombinedOp) { .mul_op = op }, AluComponent::MUL, true, false, operand_a, operand_b), RegisterValue(result)); } QTEST_APPLESS_MAIN(TestAlu) ================================================ FILE: src/machine/execute/alu.test.h ================================================ #ifndef ALU_TEST_H #define ALU_TEST_H #include class TestAlu : public QObject { Q_OBJECT private slots: static void test_alu64_operate_data(); static void test_alu64_operate(); static void test_alu32_operate_data(); static void test_alu32_operate(); static void test_mul64_operate_data(); static void test_mul64_operate(); static void test_mul32_operate_data(); static void test_mul32_operate(); }; #endif // ALU_TEST_H ================================================ FILE: src/machine/execute/alu_op.h ================================================ #ifndef ALU_OP_H #define ALU_OP_H #include #include using std::uint8_t; namespace machine { enum class AluOp : uint8_t { ADD = 0b000, SLL = 0b001, SLT = 0b010, SLTU = 0b011, XOR = 0b100, SR = 0b101, OR = 0b110, AND = 0b111, }; } Q_DECLARE_METATYPE(machine::AluOp) #endif // ALU_OP_H ================================================ FILE: src/machine/execute/mul_op.h ================================================ #ifndef MUL_OP_H #define MUL_OP_H #include namespace machine { enum class MulOp : uint8_t { MUL = 0b000, MULH = 0b001, MULHSU = 0b010, MULHU = 0b011, DIV = 0b100, DIVU = 0b101, REM = 0b110, REMU = 0b111, }; } Q_DECLARE_METATYPE(machine::MulOp) #endif // MUL_OP_H ================================================ FILE: src/machine/instruction.cpp ================================================ #include "instruction.h" #include "common/logging.h" #include "common/math/bit_ops.h" #include "common/string_utils.h" #include "csr/controlstate.h" #include "simulator_exception.h" #include "utils.h" #include #include #include #include #include #include #include #include LOG_CATEGORY("machine.instruction"); using namespace machine; using std::underlying_type; namespace machine { } struct ArgumentDesc { char name; /** * Possible values: * @val g: gp register id * @val n: numeric immediate * @val a: pc relative address offset * @val b: pc relative address offset * @val o: offset immediate */ char kind; int64_t min; int64_t max; BitArg arg; inline ArgumentDesc(char name, char kind, int64_t min, int64_t max, BitArg arg) : name(name) , kind(kind) , min(min) , max(max) , arg(arg) {} /** Check whether given value fits into this instruction field. */ [[nodiscard]] constexpr bool is_value_in_field_range(RegisterValue val) const { if (min < 0) { return val.as_i64() <= max && val.as_i64() >= min; } else { return val.as_u64() <= static_cast(max) && val.as_u64() >= static_cast(min); } } [[nodiscard]] constexpr bool is_imm() const { return kind != 'g'; } }; static const ArgumentDesc arg_desc_list[] = { // Destination register (rd) ArgumentDesc('d', 'g', 0, 0x1f, { { { 5, 7 } }, 0 }), // Source register 1 (rs1/rs) ArgumentDesc('s', 'g', 0, 0x1f, { { { 5, 15 } }, 0 }), // Source register 2 (rs2/rt) ArgumentDesc('t', 'g', 0, 0x1f, { { { 5, 20 } }, 0 }), // I-type immediate for arithmetic instructions (12bits) ArgumentDesc('j', 'n', -0x800, 0x7ff, { { { 12, 20 } }, 0 }), // Shift for bit shift instructions (5bits) ArgumentDesc('>', 'n', 0, 0x1f, { { { 5, 20 } }, 0 }), // Address offset immediate (20bits), encoded in multiples of 2 bytes ArgumentDesc('a', 'a', -0x80000, 0x7ffff, { { { 10, 21 }, { 1, 20 }, { 8, 12 }, { 1, 31 } }, 1 }), // U-type immediate for LUI and AUIPC (20bits) ArgumentDesc('u', 'n', 0, 0xfffff000, { { { 20, 12 } }, 0 }), // B-type immediate for branches (12 bits) ArgumentDesc('p', 'p', -0x1000, 0x0fff, { { { 4, 8 }, { 6, 25 }, { 1, 7 }, { 1, 31 } }, 1 }), // Offset immediate for load instructions (12 bits) ArgumentDesc('o', 'o', -0x800, 0x7ff, { { { 12, 20 } }, 0 }), // Offset immediate for store instructions (12 bits) ArgumentDesc('q', 'o', -0x800, 0x7ff, { { { 5, 7 }, { 7, 25 } }, 0 }), // 5-bit CSR value immediate // (https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=opcodes/riscv-opc.c;h=7e95f645c5c5fe0a7c93c64c2f1719efaec67972;hb=HEAD#l928) ArgumentDesc('Z', 'n', 0, 0x1f, { { { 5, 15 } }, 0 }), // 12-bit CSR address // (https://sourceware.org/git/?p=binutils-gdb.git;a=blob;f=opcodes/riscv-opc.c;h=7e95f645c5c5fe0a7c93c64c2f1719efaec67972;hb=HEAD#l928) ArgumentDesc('E', 'E', 0, 0xfff, { { { 12, 20 } }, 0 }), }; static const ArgumentDesc *arg_desc_by_code[(int)('z' + 1)]; static bool fill_argdesbycode() { for (const auto &desc : arg_desc_list) { arg_desc_by_code[(uint)(unsigned char)desc.name] = &desc; } return true; } bool argdesbycode_filled = fill_argdesbycode(); #define FLAGS_ALU_I (IMF_SUPPORTED | IMF_ALUSRC | IMF_REGWRITE | IMF_ALU_REQ_RS) #define FLAGS_ALU_I_LOAD \ (IMF_SUPPORTED | IMF_ALUSRC | IMF_REGWRITE | IMF_MEMREAD | IMF_MEM | IMF_ALU_REQ_RS) #define FLAGS_ALU_I_STORE \ (IMF_SUPPORTED | IMF_ALUSRC | IMF_MEMWRITE | IMF_MEM | IMF_ALU_REQ_RS | IMF_ALU_REQ_RT) #define FLAGS_ALU_T_R_D (IMF_SUPPORTED | IMF_REGWRITE) #define FLAGS_ALU_T_R_STD (FLAGS_ALU_T_R_D | IMF_ALU_REQ_RS | IMF_ALU_REQ_RT) #define FLAGS_AMO_LOAD (FLAGS_ALU_I_LOAD | IMF_AMO) // FLAGS_AMO_STORE for store conditional requires IMF_MEMREAD to ensure stalling because // forwarding is not possible from memory stage after memory read, TODO to solve better way #define FLAGS_AMO_STORE (FLAGS_ALU_I_STORE | FLAGS_ALU_T_R_D | IMF_AMO | IMF_MEMREAD) #define FLAGS_AMO_MODIFY (FLAGS_ALU_I_LOAD | FLAGS_AMO_STORE | IMF_AMO) #define NOALU { .alu_op = AluOp::ADD } #define NOMEM .mem_ctl = AC_NONE // TODO NOTE: if unknown is defined as all 0, instruction map can be significantly simplified // using zero initialization. #define IM_UNKNOWN \ { "unknown", Instruction::UNKNOWN, NOALU, NOMEM, nullptr, {}, 0, 0, { 0 }, nullptr } struct InstructionMap { const char *name; Instruction::Type type = Instruction::UNKNOWN; AluCombinedOp alu = { .alu_op = AluOp::ADD }; AccessControl mem_ctl = AC_NONE; const struct InstructionMap *subclass = nullptr; // when subclass is used then flags // has special meaning const cvector args; uint32_t code; uint32_t mask; union { decltype(underlying_type::type()) flags; BitField subfield; }; const InstructionMap *aliases = nullptr; }; #define IT_R Instruction::R #define IT_I Instruction::I #define IT_S Instruction::S #define IT_B Instruction::B #define IT_U Instruction::U #define IT_J Instruction::J #define IT_AMO Instruction::AMO #define IT_ZICSR Instruction::ZICSR #define IT_UNKNOWN Instruction::UNKNOWN // clang-format off // alliases for instructions for internal assembler and possibly // disassembler if simplified format is requested. // They are not used during decoding and execution #define INST_ALIAS_LIST_END {.name = nullptr, .args = {}, .code = 0 , .mask = 0, .flags = 0 } static const struct InstructionMap inst_aliases_addi[] = { { .name = "mv", .args = {"d", "s"}, .code = 0x13, .mask = 0x707f | (0xffful << 20), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_andi[] = { { .name = "zext.b", .args = {"d", "s"}, .code = 0x7013 | (0xfful << 20), .mask = 0x707f | (0xffful << 20), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_addiw[] = { { .name = "sext.w", .args = {"d", "s"}, .code = 0x1b, .mask = 0x707f | (0xffful << 20), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_xori[] = { { .name = "not", .args = {"d", "s"}, .code = 0x4013 | (0xffful << 20), .mask = 0x707f | (0xffful << 20), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_sub[] = { { .name = "neg", .args = {"d", "t"}, .code = 0x40000033 | (0 << 15), .mask = 0xfe00707f | (31 << 15), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_subw[] = { { .name = "negw", .args = {"d", "t"}, .code = 0x4000003b | (0 << 15), .mask = 0xfe00707f | (31 << 15), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_sltiu[] = { { .name = "seqz", .args = {"d", "s"}, .code = 0x3013| (1 << 20), .mask = 0x0000707f | (0xffful << 20), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_sltu[] = { { .name = "snez", .args = {"d", "t"}, .code = 0x3033 | (0 << 15), .mask = 0xfe00707f | (31 << 15), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_slt[] = { { .name = "sltz", .args = {"d", "s"}, .code = 0x2033 | (0 << 20), .mask = 0xfe00707f | (31 << 20), .flags = IMF_SUPPORTED }, { .name = "sgtz", .args = {"d", "t"}, .code = 0x2033 | (0 << 15), .mask = 0xfe00707f | (31 << 15), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_beq[] = { //0x00000063,0x0000707f { .name = "beqz", .args = {"s", "p"}, .code = 0x0063 | (0 << 20), .mask = 0x707f | (31 << 20), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_bne[] = { //0x00001063, 0x0000707f { .name = "bnez", .args = {"s", "p"}, .code = 0x1063 | (0 << 20), .mask = 0x707f | (31 << 20), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_blt[] = { //0x00004063, 0x0000707f { .name = "bltz", .args = {"s", "p"}, .code = 0x4063 | (0 << 20), .mask = 0x707f | (31 << 20), .flags = IMF_SUPPORTED }, { .name = "bgtz", .args = {"t", "p"}, .code = 0x4063 | (0 << 15), .mask = 0x707f | (31 << 15), .flags = IMF_SUPPORTED }, { .name = "bgt", .args = {"t", "s", "p"}, .code = 0x4063, .mask = 0x707f, .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_bge[] = { //0x00005063,0x0000707f { .name = "bgez", .args = {"s", "p"}, .code = 0x5063 | (0 << 20), .mask = 0x707f | (31 << 20), .flags = IMF_SUPPORTED }, { .name = "ble", .args = {"t", "s", "p"}, .code = 0x5063, .mask = 0x707f, .flags = IMF_SUPPORTED }, { .name = "blez", .args = {"t", "p"}, .code = 0x5063 | (0 << 15), .mask = 0x707f | (31 << 15), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_bltu[] = { //0x00006063, 0x0000707f { .name = "bgtu", .args = {"t", "s", "p"}, .code = 0x6063, .mask = 0x707f, .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_bgeu[] = { //0x00007063,0x0000707f { .name = "bleu", .args = {"t", "s", "p"}, .code = 0x7063, .mask = 0x707f, .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_jal[] = { { .name = "j", .args = {"a"}, .code = 0x6f | (0 << 7) , .mask = 0x7f | (31 << 7), .flags = IMF_SUPPORTED }, { .name = "jal", .args = {"a"}, .code = 0x6f | (1 << 7) , .mask = 0x7f | (31 << 7), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_jalr[] = { { .name = "ret", .args = {}, .code = 0x67 | (0 << 7) | (1 << 15) , .mask = 0xfffffffful, .flags = IMF_SUPPORTED }, { .name = "jr", .args = {"s"}, .code = 0x67 | (0 << 7) , .mask = 0x7f | (31 << 7) | (0xffful << 20), .flags = IMF_SUPPORTED }, { .name = "jr", .args = {"o(s)"}, .code = 0x67 | (0 << 7) , .mask = 0x7f | (31 << 7), .flags = IMF_SUPPORTED }, { .name = "jalr", .args = {"s"}, .code = 0x67 | (1 << 7) , .mask = 0x7f | (31 << 7) | (0xffful << 20), .flags = IMF_SUPPORTED }, { .name = "jalr", .args = {"o(s)"}, .code = 0x67 | (1 << 7) , .mask = 0x7f | (31 << 7), .flags = IMF_SUPPORTED }, { .name = "jalr", .args = {"d", "s", "o"}, .code = 0x67, .mask = 0x7f, .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_csrrw[] = { { .name = "csrw", .args = {"E", "s"}, .code = 0x1073 | (0 << 7) | (0 << 15) , .mask = 0x707f | (31 << 7), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; static const struct InstructionMap inst_aliases_csrrs[] = { { .name = "csrr", .args = {"d", "E"}, .code = 0x2073 | (0 << 15) , .mask = 0x707f | (31 << 15), .flags = IMF_SUPPORTED }, INST_ALIAS_LIST_END, }; // RV32/64A - Atomi Memory Operations #define AMO_ARGS_LOAD {"d", "(s)"} #define AMO_ARGS_STORE {"d", "t", "(s)"} #define AMO_ARGS_MODIFY {"d", "t", "(s)"} #define AMO_MAP_4ITEMS(NAME_BASE, CODE_BASE, MASK, MEM_CTL, FLAGS, ARGS) \ { NAME_BASE, IT_AMO, NOALU, MEM_CTL, nullptr, ARGS , ((CODE_BASE) | 0x00000000), 0xfe00707f, { .flags = FLAGS}, nullptr}, \ { NAME_BASE ".rl", IT_AMO, NOALU, MEM_CTL, nullptr, ARGS , ((CODE_BASE) | 0x02000000), 0xfe00707f, { .flags = FLAGS}, nullptr}, \ { NAME_BASE ".aq", IT_AMO, NOALU, MEM_CTL, nullptr, ARGS , ((CODE_BASE) | 0x04000000), 0xfe00707f, { .flags = FLAGS}, nullptr}, \ { NAME_BASE ".aqrl", IT_AMO, NOALU, MEM_CTL, nullptr, ARGS , ((CODE_BASE) | 0x06000000), 0xfe00707f, { .flags = FLAGS}, nullptr} static const struct InstructionMap AMO_32_map[] = { AMO_MAP_4ITEMS("amoadd.w", 0x0000202f, 0xfe00707f, AC_AMOADD32, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), AMO_MAP_4ITEMS("amoswap.w", 0x0800202f, 0xfe00707f, AC_AMOSWAP32, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), AMO_MAP_4ITEMS("lr.w", 0x1000202f, 0xfff0707f, AC_LR32, FLAGS_AMO_LOAD, AMO_ARGS_LOAD), AMO_MAP_4ITEMS("sc.w", 0x1800202f, 0xfe00707f, AC_SC32, FLAGS_AMO_STORE, AMO_ARGS_STORE), AMO_MAP_4ITEMS("amoxor.w", 0x2000202f, 0xfe00707f, AC_AMOXOR32, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amoor.w", 0x4000202f, 0xfe00707f, AC_AMOOR32, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amoand.w", 0x6000202f, 0xfe00707f, AC_AMOAND32, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amomin.w", 0x8000202f, 0xfe00707f, AC_AMOMIN32, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amomax.w", 0xa000202f, 0xfe00707f, AC_AMOMAX32, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amominu.w", 0xc000202f, 0xfe00707f, AC_AMOMINU32, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amomaxu.w", 0xe000202f, 0xfe00707f, AC_AMOMAXU32, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, }; static const struct InstructionMap AMO_64_map[] = { AMO_MAP_4ITEMS("amoadd.d", 0x0000302f, 0xfe00707f, AC_AMOADD64, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), AMO_MAP_4ITEMS("amoswap.d", 0x0800302f, 0xfe00707f, AC_AMOSWAP64, FLAGS_AMO_MODIFY, AMO_ARGS_MODIFY), AMO_MAP_4ITEMS("lr.d", 0x1000302f, 0xfff0707f, AC_LR64, FLAGS_AMO_LOAD, AMO_ARGS_LOAD), AMO_MAP_4ITEMS("sc.d", 0x1800302f, 0xfe00707f, AC_SC64, FLAGS_AMO_STORE, AMO_ARGS_STORE), AMO_MAP_4ITEMS("amoxor.d", 0x2000302f, 0xfe00707f, AC_AMOXOR64, FLAGS_AMO_MODIFY | IMF_RV64, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amoor.d", 0x4000302f, 0xfe00707f, AC_AMOOR64, FLAGS_AMO_MODIFY | IMF_RV64, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amoand.d", 0x6000302f, 0xfe00707f, AC_AMOAND64, FLAGS_AMO_MODIFY | IMF_RV64, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amomin.d", 0x8000302f, 0xfe00707f, AC_AMOMIN64, FLAGS_AMO_MODIFY | IMF_RV64, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amomax.d", 0xa000302f, 0xfe00707f, AC_AMOMAX64, FLAGS_AMO_MODIFY | IMF_RV64, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amominu.d", 0xc000302f, 0xfe00707f, AC_AMOMINU64, FLAGS_AMO_MODIFY | IMF_RV64, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, AMO_MAP_4ITEMS("amomaxu.d", 0xe000302f, 0xfe00707f, AC_AMOMAXU64, FLAGS_AMO_MODIFY | IMF_RV64, AMO_ARGS_MODIFY), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, }; static const struct InstructionMap AMO_map[] = { IM_UNKNOWN, IM_UNKNOWN, {"amo-32", IT_R, NOALU, NOMEM, AMO_32_map, {}, 0x0002027, 0x0000707f, { .subfield = {7, 25} }, nullptr}, // OP-32 {"amo-64", IT_R, NOALU, NOMEM, AMO_64_map, {}, 0x0003027, 0x0000707f, { .subfield = {7, 25} }, nullptr}, // OP-32 IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, }; #undef AMO_MAP_4ITEMS static const struct InstructionMap LOAD_map[] = { {"lb", IT_I, { .alu_op=AluOp::ADD }, AC_I8, nullptr, {"d", "o(s)"}, 0x00000003,0x0000707f, { .flags = FLAGS_ALU_I_LOAD }, nullptr}, // LB {"lh", IT_I, { .alu_op=AluOp::ADD }, AC_I16, nullptr, {"d", "o(s)"}, 0x00001003,0x0000707f, { .flags = FLAGS_ALU_I_LOAD }, nullptr}, // LH {"lw", IT_I, { .alu_op=AluOp::ADD }, AC_I32, nullptr, {"d", "o(s)"}, 0x00002003,0x0000707f, { .flags = FLAGS_ALU_I_LOAD }, nullptr}, // LW {"ld", IT_I, { .alu_op=AluOp::ADD }, AC_I64, nullptr, {"d", "o(s)"}, 0x00003003,0x0000707f, { .flags = FLAGS_ALU_I_LOAD | IMF_RV64}, nullptr}, // LD {"lbu", IT_I, { .alu_op=AluOp::ADD }, AC_U8, nullptr, {"d", "o(s)"}, 0x00004003,0x0000707f, { .flags = FLAGS_ALU_I_LOAD }, nullptr}, // LBU {"lhu", IT_I, { .alu_op=AluOp::ADD }, AC_U16, nullptr, {"d", "o(s)"}, 0x00005003,0x0000707f, { .flags = FLAGS_ALU_I_LOAD }, nullptr}, // LHU {"lwu", IT_I, { .alu_op=AluOp::ADD }, AC_U32, nullptr, {"d", "o(s)"}, 0x00006003,0x0000707f, { .flags = FLAGS_ALU_I_LOAD | IMF_RV64}, nullptr}, // LWU IM_UNKNOWN, }; static const struct InstructionMap SRI_map[] = { // 0xfe00707f mask changed to 0xfc00707f to support RV64I {"srli", IT_I, { .alu_op=AluOp::SR }, NOMEM, nullptr, {"d", "s", ">"}, 0x00005013,0xfc00707f, { .flags = FLAGS_ALU_I }, nullptr}, // SRLI {"srai", IT_I, { .alu_op=AluOp::SR }, NOMEM, nullptr, {"d", "s", ">"}, 0x40005013,0xfc00707f, { .flags = (FLAGS_ALU_I | IMF_ALU_MOD) }, nullptr}, // SRAI }; static const struct InstructionMap OP_IMM_map[] = { {"addi", IT_I, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"d", "s", "j"}, 0x00000013,0x0000707f, { .flags = FLAGS_ALU_I }, inst_aliases_addi}, // ADDI {"slli", IT_I, { .alu_op=AluOp::SLL }, NOMEM, nullptr, {"d", "s", ">"}, 0x00001013,0xfc00707f, { .flags = FLAGS_ALU_I }, nullptr}, // SLLI {"slti", IT_I, { .alu_op=AluOp::SLT }, NOMEM, nullptr, {"d", "s", "j"}, 0x00002013,0x0000707f, { .flags = FLAGS_ALU_I }, nullptr}, // SLTI {"sltiu", IT_I, { .alu_op=AluOp::SLTU }, NOMEM, nullptr, {"d", "s", "j"}, 0x00003013,0x0000707f, { .flags = FLAGS_ALU_I }, inst_aliases_sltiu}, // SLTIU {"xori", IT_I, { .alu_op=AluOp::XOR }, NOMEM, nullptr, {"d", "s", "j"}, 0x00004013,0x0000707f, { .flags = FLAGS_ALU_I }, inst_aliases_xori}, // XORI {"sri", IT_I, NOALU, NOMEM, SRI_map, {}, 0x00005013, 0xbe00707f, { .subfield = {1, 30} }, nullptr}, // SRLI, SRAI {"ori", IT_I, { .alu_op=AluOp::OR }, NOMEM, nullptr, {"d", "s", "j"}, 0x00006013,0x0000707f, { .flags = FLAGS_ALU_I }, nullptr}, // ORI {"andi", IT_I, { .alu_op=AluOp::AND }, NOMEM, nullptr, {"d", "s", "j"}, 0x00007013,0x0000707f, { .flags = FLAGS_ALU_I }, inst_aliases_andi}, // ANDI }; static const struct InstructionMap STORE_map[] = { {"sb", IT_S, { .alu_op=AluOp::ADD }, AC_U8, nullptr, {"t", "q(s)"}, 0x00000023, 0x0000707f, { .flags = FLAGS_ALU_I_STORE }, nullptr}, // SB {"sh", IT_S, { .alu_op=AluOp::ADD }, AC_U16, nullptr, {"t", "q(s)"}, 0x00001023, 0x0000707f, { .flags = FLAGS_ALU_I_STORE }, nullptr}, // SH {"sw", IT_S, { .alu_op=AluOp::ADD }, AC_U32, nullptr, {"t", "q(s)"}, 0x00002023, 0x0000707f, { .flags = FLAGS_ALU_I_STORE }, nullptr}, // SW {"sd", IT_S, { .alu_op=AluOp::ADD }, AC_U64, nullptr, {"t", "q(s)"}, 0x00003023, 0x0000707f, { .flags = FLAGS_ALU_I_STORE | IMF_RV64}, nullptr}, // SD IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, }; static const struct InstructionMap ADD_map[] = { {"add", IT_R, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"d", "s", "t"}, 0x00000033, 0xfe00707f, { .flags = FLAGS_ALU_T_R_STD }, nullptr}, {"sub", IT_R, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"d", "s", "t"}, 0x40000033, 0xfe00707f, { .flags = FLAGS_ALU_T_R_STD | IMF_ALU_MOD }, inst_aliases_sub}, }; static const struct InstructionMap SR_map[] = { {"srl", IT_R, { .alu_op=AluOp::SR }, NOMEM, nullptr, {"d", "s", "t"}, 0x00005033,0xfe00707f, { .flags = FLAGS_ALU_T_R_STD }, nullptr}, // SRL {"sra", IT_R, { .alu_op=AluOp::SR }, NOMEM, nullptr, {"d", "s", "t"}, 0x40005033,0xfe00707f, { .flags = FLAGS_ALU_T_R_STD | IMF_ALU_MOD }, nullptr}, // SRA }; static const struct InstructionMap OP_ALU_map[] = { {"add/sub", IT_R, NOALU, NOMEM, ADD_map, {}, 0x00000033, 0xbe00707f, { .subfield = {1, 30} }, nullptr}, {"sll", IT_R, { .alu_op=AluOp::SLL }, NOMEM, nullptr, {"d", "s", "t"}, 0x00001033, 0xfe00707f, { .flags = FLAGS_ALU_T_R_STD }, nullptr}, // SLL {"slt", IT_R, { .alu_op=AluOp::SLT }, NOMEM, nullptr, {"d", "s", "t"}, 0x00002033, 0xfe00707f, { .flags = FLAGS_ALU_T_R_STD }, inst_aliases_slt}, // SLT {"sltu", IT_R, { .alu_op=AluOp::SLTU }, NOMEM, nullptr, {"d", "s", "t"}, 0x00003033,0xfe00707f, { .flags = FLAGS_ALU_T_R_STD }, inst_aliases_sltu}, // SLTU {"xor", IT_R, { .alu_op=AluOp::XOR }, NOMEM, nullptr, {"d", "s", "t"}, 0x00004033,0xfe00707f, { .flags = FLAGS_ALU_T_R_STD }, nullptr}, // XOR {"sr", IT_R, NOALU, NOMEM, SR_map, {}, 0x00005033, 0xbe00707f, { .subfield = {1, 30} }, nullptr}, // SRL, SRA {"or", IT_R, { .alu_op=AluOp::OR }, NOMEM, nullptr, {"d", "s", "t"}, 0x00006033,0xfe00707f, { .flags = FLAGS_ALU_T_R_STD }, nullptr}, // OR {"and", IT_R, { .alu_op=AluOp::AND }, NOMEM, nullptr, {"d", "s", "t"}, 0x00007033,0xfe00707f, { .flags = FLAGS_ALU_T_R_STD }, nullptr}, // AND }; // RV32M #define MUL_MAP_ITEM(NAME, OP, CODE) \ { NAME, IT_R, { .mul_op = (OP) }, NOMEM, nullptr, {"d", "s", "t"}, (0x02000033 | (CODE)), 0xfe00707f, { .flags = (FLAGS_ALU_T_R_STD | IMF_MUL) }, nullptr} static const struct InstructionMap OP_MUL_map[] = { MUL_MAP_ITEM("mul", MulOp::MUL, 0x0000), MUL_MAP_ITEM("mulh", MulOp::MULH, 0x1000), MUL_MAP_ITEM("mulhsu", MulOp::MULHSU, 0x2000), MUL_MAP_ITEM("mulhu", MulOp::MULHU, 0x3000), MUL_MAP_ITEM("div", MulOp::DIV, 0x4000), MUL_MAP_ITEM("divu", MulOp::DIVU, 0x5000), MUL_MAP_ITEM("rem", MulOp::REM, 0x6000), MUL_MAP_ITEM("remu", MulOp::REMU, 0x7000), }; static const struct InstructionMap OP_map[] = { {"alu", IT_R, NOALU, NOMEM, OP_ALU_map, {}, 0x00000033, 0x0000707f, { .subfield = {3, 12} }, nullptr}, {"mul", IT_R, NOALU, NOMEM, OP_MUL_map, {}, 0x02000033, 0xfc00707f, { .subfield = {3, 12} }, nullptr}, }; static const struct InstructionMap BRANCH_map[] = { {"beq", IT_B, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"s", "t", "p"}, 0x00000063,0x0000707f, { .flags = IMF_SUPPORTED | IMF_BRANCH | IMF_ALU_REQ_RS | IMF_ALU_REQ_RT | IMF_ALU_MOD }, inst_aliases_beq}, // BEQ {"bne", IT_B, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"s", "t", "p"}, 0x00001063, 0x0000707f, { .flags = IMF_SUPPORTED | IMF_BRANCH | IMF_ALU_REQ_RS | IMF_ALU_REQ_RT | IMF_ALU_MOD | IMF_BJ_NOT }, inst_aliases_bne}, // BNE IM_UNKNOWN, IM_UNKNOWN, {"blt", IT_B, { .alu_op=AluOp::SLT }, NOMEM, nullptr, {"s", "t", "p"}, 0x00004063, 0x0000707f, { .flags = IMF_SUPPORTED | IMF_BRANCH | IMF_ALU_REQ_RS | IMF_ALU_REQ_RT | IMF_BJ_NOT }, inst_aliases_blt}, // BLT {"bge", IT_B, { .alu_op=AluOp::SLT }, NOMEM, nullptr, {"s", "t", "p"}, 0x00005063,0x0000707f, { .flags = IMF_SUPPORTED | IMF_BRANCH | IMF_ALU_REQ_RS | IMF_ALU_REQ_RT }, inst_aliases_bge}, // BGE {"bltu", IT_B, { .alu_op=AluOp::SLTU }, NOMEM, nullptr, {"s", "t", "p"}, 0x00006063, 0x0000707f, { .flags = IMF_SUPPORTED | IMF_BRANCH | IMF_ALU_REQ_RS | IMF_ALU_REQ_RT | IMF_BJ_NOT }, inst_aliases_bltu}, // BLTU {"bgeu", IT_B, { .alu_op=AluOp::SLTU }, NOMEM, nullptr, {"s", "t", "p"}, 0x00007063,0x0000707f, { .flags = IMF_SUPPORTED | IMF_BRANCH | IMF_ALU_REQ_RS | IMF_ALU_REQ_RT }, inst_aliases_bgeu}, // BGEU }; // Spec vol. 1: 2.8 static const struct InstructionMap ENVIRONMENT_AND_BREAKPOINTS_map[] = { {"ecall", IT_I, NOALU, NOMEM, nullptr, {}, 0x00000073, 0xffffffff, { .flags = IMF_SUPPORTED | IMF_EXCEPTION | IMF_ECALL }, nullptr}, {"ebreak", IT_I, NOALU, NOMEM, nullptr, {}, 0x00100073, 0xffffffff, { .flags = IMF_SUPPORTED | IMF_EXCEPTION | IMF_EBREAK }, nullptr}, }; // Priviledged system isntructions, only 5-bits (29:25) are decoded for now. // Full decode is should cover 128 entries (31:25) but we radly support hypervisor even in future static const struct InstructionMap SYSTEM_PRIV_map[] = { {"environment_and_breakpoints", IT_I, NOALU, NOMEM, ENVIRONMENT_AND_BREAKPOINTS_map, {}, 0x00000073, 0xffffffff, { .subfield = {1, 20} }, nullptr}, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, {"sret", IT_I, NOALU, NOMEM, nullptr, {}, 0x10200073, 0xffffffff, { .flags = IMF_SUPPORTED | IMF_XRET | IMF_PRIV_S }, nullptr}, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, {"mret", IT_I, NOALU, NOMEM, nullptr, {}, 0x30200073, 0xffffffff, { .flags = IMF_SUPPORTED | IMF_XRET | IMF_PRIV_M }, nullptr}, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, }; #define CSR_MAP_ITEM(NAME, SOURCE, CODE, ALU_OP, EXTRA_FLAGS, ALIASES) \ { NAME, Instruction::ZICSR, { .alu_op=AluOp::ALU_OP }, NOMEM, nullptr, {"d", "E", SOURCE}, 0x00000073 | (CODE), 0x0000707f, { .flags = IMF_SUPPORTED | IMF_CSR | IMF_REGWRITE | IMF_ALU_REQ_RS | (EXTRA_FLAGS) }, ALIASES} static const struct InstructionMap SYSTEM_map[] = { {"system_priviledged", IT_I, NOALU, NOMEM, SYSTEM_PRIV_map, {}, 0x00000073, 0xffffffff, { .subfield = {5, 25} }, nullptr}, CSR_MAP_ITEM("csrrw", "s", 0x1000, ADD, 0, inst_aliases_csrrw), CSR_MAP_ITEM("csrrs", "s", 0x2000, OR, IMF_CSR_TO_ALU, inst_aliases_csrrs), CSR_MAP_ITEM("csrrc", "s", 0x3000, AND, IMF_CSR_TO_ALU | IMF_ALU_MOD, nullptr), IM_UNKNOWN, CSR_MAP_ITEM("csrrwi", "Z", 0x5000, ADD, IMF_ALU_RS_ID, nullptr), CSR_MAP_ITEM("csrrsi", "Z", 0x6000, OR, IMF_ALU_RS_ID | IMF_CSR_TO_ALU, nullptr), CSR_MAP_ITEM("csrrci", "Z", 0x7000, AND, IMF_ALU_RS_ID | IMF_CSR_TO_ALU | IMF_ALU_MOD, nullptr), }; #undef CSR_MAP_ITEM static const struct InstructionMap MISC_MEM_map[] = { {"fence", IT_I, NOALU, AC_CACHE_OP, nullptr, {}, 0x0000000f, 0x0000707f, { .flags = IMF_SUPPORTED | IMF_MEM }, nullptr}, {"fence.i", IT_I, NOALU, AC_CACHE_OP, nullptr, {}, 0x000100f, 0x0000707f, { .flags = IMF_SUPPORTED | IMF_MEM }, nullptr}, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, }; // RV64I specific operations static const struct InstructionMap SRI_32_map[] = { {"srliw", IT_I, { .alu_op=AluOp::SR }, NOMEM, nullptr, {"d", "s", ">"}, 0x0000501b,0xfe00707f, { .flags = FLAGS_ALU_I | IMF_FORCE_W_OP | IMF_RV64 }, nullptr}, // SRLIW {"sraiw", IT_I, { .alu_op=AluOp::SR }, NOMEM, nullptr, {"d", "s", ">"}, 0x4000501b,0xfe00707f, { .flags = FLAGS_ALU_I | IMF_ALU_MOD | IMF_FORCE_W_OP | IMF_RV64 }, nullptr}, // SRAIW }; static const struct InstructionMap OP_IMM_32_map[] = { {"addiw", IT_I, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"d", "s", "j"}, 0x0000001b,0x0000707f, { .flags = FLAGS_ALU_I | IMF_FORCE_W_OP | IMF_RV64 }, inst_aliases_addiw}, // ADDIW {"slliw", IT_I, { .alu_op=AluOp::SLL }, NOMEM, nullptr, {"d", "s", ">"}, 0x0000101b,0xfe00707f, { .flags = FLAGS_ALU_I | IMF_FORCE_W_OP | IMF_RV64 }, nullptr}, // SLLIW IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, {"sriw", IT_I, NOALU, NOMEM, SRI_32_map, {}, 0x0000501b, 0xbe00707f, { .subfield = {1, 30} }, nullptr}, // SRLIW, SRAIW IM_UNKNOWN, IM_UNKNOWN, }; static const struct InstructionMap ADD_32_map[] = { {"addw", IT_R, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"d", "s", "t"}, 0x0000003b, 0xfe00707f, { .flags = FLAGS_ALU_T_R_STD | IMF_FORCE_W_OP | IMF_RV64 }, nullptr}, {"subw", IT_R, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"d", "s", "t"}, 0x4000003b, 0xfe00707f, { .flags = FLAGS_ALU_T_R_STD | IMF_ALU_MOD | IMF_FORCE_W_OP | IMF_RV64 }, inst_aliases_subw}, }; static const struct InstructionMap SR_32_map[] = { {"srlw", IT_R, { .alu_op=AluOp::SR }, NOMEM, nullptr, {"d", "s", "t"}, 0x0000503b,0xfe00707f, { .flags = FLAGS_ALU_T_R_STD | IMF_FORCE_W_OP | IMF_RV64 }, nullptr}, // SRL {"sraw", IT_R, { .alu_op=AluOp::SR }, NOMEM, nullptr, {"d", "s", "t"}, 0x4000503b,0xfe00707f, { .flags = FLAGS_ALU_T_R_STD | IMF_ALU_MOD | IMF_FORCE_W_OP | IMF_RV64 }, nullptr}, // SRA }; static const struct InstructionMap OP_ALU_32_map[] = { {"addw/subw", IT_R, NOALU, NOMEM, ADD_32_map, {}, 0x0000003b, 0xbe00707f, { .subfield = {1, 30} }, nullptr}, {"sllw", IT_R, { .alu_op=AluOp::SLL }, NOMEM, nullptr, {"d", "s", "t"}, 0x0000103b, 0xfe00707f, { .flags = FLAGS_ALU_T_R_STD | IMF_FORCE_W_OP | IMF_RV64 }, nullptr}, // SLL IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, {"srw", IT_R, NOALU, NOMEM, SR_32_map, {}, 0x0000503b, 0xbe00707f, { .subfield = {1, 30} }, nullptr}, // SRL, SRA IM_UNKNOWN, IM_UNKNOWN, }; // RV64M #define MUL_32_MAP_ITEM(NAME, OP, CODE) \ { NAME, IT_R, { .mul_op = (OP) }, NOMEM, nullptr, {"d", "s", "t"}, (0x0200003b | (CODE)), 0xfe00707f, { .flags = (FLAGS_ALU_T_R_STD | IMF_MUL | IMF_FORCE_W_OP | IMF_RV64 ) }, nullptr} static const struct InstructionMap OP_MUL_32_map[] = { MUL_32_MAP_ITEM("mulw", MulOp::MUL, 0x0000), IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, MUL_32_MAP_ITEM("divw", MulOp::DIV, 0x4000), MUL_32_MAP_ITEM("divuw", MulOp::DIVU, 0x5000), MUL_32_MAP_ITEM("remw", MulOp::REM, 0x6000), MUL_32_MAP_ITEM("remuw", MulOp::REMU, 0x7000), }; static const struct InstructionMap OP_32_map[] = { {"aluw", IT_R, NOALU, NOMEM, OP_ALU_32_map, {}, 0x00000033, 0x0000707f, { .subfield = {3, 12} }, nullptr}, {"mulw", IT_R, NOALU, NOMEM, OP_MUL_32_map, {}, 0x02000033, 0xfc00707f, { .subfield = {3, 12} }, nullptr}, }; // Full, uncomprese, instructions top level map static const struct InstructionMap I_inst_map[] = { {"load", IT_I, NOALU, NOMEM, LOAD_map, {}, 0x03, 0x7f, { .subfield = {3, 12} }, nullptr}, // LOAD IM_UNKNOWN, // LOAD-FP IM_UNKNOWN, // custom-0 {"misc-mem", IT_I, NOALU, NOMEM, MISC_MEM_map, {}, 0x0f, 0x7f, { .subfield = {3, 12} }, nullptr}, // MISC-MEM {"op-imm", IT_I, NOALU, NOMEM, OP_IMM_map, {}, 0x13, 0x7f, { .subfield = {3, 12} }, nullptr}, // OP-IMM {"auipc", IT_U, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"d", "u"}, 0x17, 0x7f, { .flags = IMF_SUPPORTED | IMF_ALUSRC | IMF_REGWRITE | IMF_PC_TO_ALU }, nullptr}, // AUIPC {"op-imm-32", IT_I, NOALU, NOMEM, OP_IMM_32_map, {}, 0x1b, 0x7f, { .subfield = {3, 12} }, nullptr}, // OP-IMM-32 IM_UNKNOWN, // OP-IMM-32 IM_UNKNOWN, // 48b {"store", IT_I, NOALU, NOMEM, STORE_map, {}, 0x23, 0x7f, { .subfield = {3, 12} }, nullptr}, // STORE IM_UNKNOWN, // STORE-FP IM_UNKNOWN, // custom-1 {"amo", IT_R, NOALU, NOMEM, AMO_map, {}, 0x2f, 0x7f, { .subfield = {3, 12} }, nullptr}, // OP-32 {"op", IT_R, NOALU, NOMEM, OP_map, {}, 0x33, 0x7f, { .subfield = {1, 25} }, nullptr}, // OP {"lui", IT_U, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"d", "u"}, 0x37, 0x7f, { .flags = IMF_SUPPORTED | IMF_ALUSRC | IMF_REGWRITE }, nullptr}, // LUI {"op-32", IT_R, NOALU, NOMEM, OP_32_map, {}, 0x3b, 0x7f, { .subfield = {1, 25} }, nullptr}, // OP-32 IM_UNKNOWN, // 64b IM_UNKNOWN, // MADD IM_UNKNOWN, // MSUB IM_UNKNOWN, // NMSUB IM_UNKNOWN, // NMADD IM_UNKNOWN, // OP-FP IM_UNKNOWN, // reserved IM_UNKNOWN, // custom-2/rv128 IM_UNKNOWN, // 48b {"branch", IT_B, NOALU, NOMEM, BRANCH_map, {}, 0x63, 0x7f, { .subfield = {3, 12} }, nullptr}, // BRANCH {"jalr", IT_I, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"d", "o(s)"}, 0x67, 0x7f, { .flags = IMF_SUPPORTED | IMF_REGWRITE | IMF_BRANCH_JALR | IMF_ALUSRC | IMF_ALU_REQ_RS }, inst_aliases_jalr}, // JALR IM_UNKNOWN, // reserved {"jal", IT_J, { .alu_op=AluOp::ADD }, NOMEM, nullptr, {"d", "a"}, 0x6f, 0x7f, { .flags = IMF_SUPPORTED | IMF_REGWRITE | IMF_JUMP | IMF_PC_TO_ALU | IMF_ALUSRC }, inst_aliases_jal}, // JAL {"system", IT_I, NOALU, NOMEM, SYSTEM_map, {}, 0x73, 0x7f, { .subfield = {3, 12} }, nullptr}, // SYSTEM IM_UNKNOWN, // reserved IM_UNKNOWN, // custom-3/rv128 IM_UNKNOWN, // >= 80b }; static const struct InstructionMap C_inst_map[] = { IM_UNKNOWN, IM_UNKNOWN, IM_UNKNOWN, {"i", IT_UNKNOWN, NOALU, NOMEM, I_inst_map, {}, 0x3, 0x3, { .subfield = {5, 2} }, nullptr}, }; static const struct InstructionMap C_inst_unknown = IM_UNKNOWN; // clang-format on const BitField instruction_map_opcode_field = { 2, 0 }; static inline const struct InstructionMap &InstructionMapFind(uint32_t code) { const struct InstructionMap *im = &C_inst_map[instruction_map_opcode_field.decode(code)]; while (im->subclass != nullptr) { im = &im->subclass[im->subfield.decode(code)]; } if ((code ^ im->code) & im->mask) { return C_inst_unknown; } return *im; } const std::array RECOGNIZED_PSEUDOINSTRUCTIONS { "nop", "la", "li", "sext.b", "sext.h", "zext.h", "zext.w", "call", "tail" }; bool Instruction::symbolic_registers_enabled = false; const Instruction Instruction::NOP = Instruction(0x00000013); const Instruction Instruction::UNKNOWN_INST = Instruction(0x0); Instruction::Instruction() { this->dt = 0; } Instruction::Instruction(uint32_t inst) { this->dt = inst; } Instruction::Instruction(const Instruction &i) { this->dt = i.data(); } #define MASK(LEN, OFF) ((this->dt >> (OFF)) & ((1 << (LEN)) - 1)) uint8_t Instruction::opcode() const { return (uint8_t)MASK(7, 0); // Does include the 2 bits marking it's not a // 16b instruction } uint8_t Instruction::rs() const { return (uint8_t)MASK(5, 15); } uint8_t Instruction::rt() const { return (uint8_t)MASK(5, 20); } uint8_t Instruction::rd() const { return (uint8_t)MASK(5, 7); } uint8_t Instruction::shamt() const { return this->rt(); } uint16_t Instruction::funct() const { return uint16_t(MASK(7, 25) << 3 | MASK(3, 12)); } CSR::Address Instruction::csr_address() const { return CSR::Address(MASK(12, 20)); } int32_t Instruction::immediate() const { int32_t ret = 0; switch (this->type()) { case R: break; case I: ret = extend(MASK(12, 20), 12); break; case S: ret = extend(MASK(7, 25) << 5 | MASK(5, 7), 12); break; case B: ret = extend(MASK(4, 8) << 1 | MASK(6, 25) << 5 | MASK(1, 7) << 11 | MASK(1, 31) << 12, 13); break; case U: ret = extend(MASK(20, 12) << 12, 32); break; case J: ret = extend( MASK(10, 21) << 1 | MASK(1, 20) << 11 | MASK(8, 12) << 12 | MASK(1, 31) << 20, 21); break; case ZICSR: case AMO: case UNKNOWN: break; } return ret; } Address Instruction::address() const { return Address(MASK(26, 0)); } uint32_t Instruction::data() const { return this->dt; } bool Instruction::imm_sign() const { return this->dt >> 31; } enum Instruction::Type Instruction::type() const { const struct InstructionMap &im = InstructionMapFind(dt); return im.type; } enum InstructionFlags Instruction::flags() const { const struct InstructionMap &im = InstructionMapFind(dt); return (enum InstructionFlags)im.flags; } AluCombinedOp Instruction::alu_op() const { const struct InstructionMap &im = InstructionMapFind(dt); return im.alu; } enum AccessControl Instruction::mem_ctl() const { const struct InstructionMap &im = InstructionMapFind(dt); return im.mem_ctl; } void Instruction::flags_alu_op_mem_ctl( InstructionFlags &flags, AluCombinedOp &alu_op, AccessControl &mem_ctl) const { const struct InstructionMap &im = InstructionMapFind(dt); flags = (enum InstructionFlags)im.flags; alu_op = im.alu; mem_ctl = im.mem_ctl; } bool Instruction::operator==(const Instruction &c) const { return (this->data() == c.data()); } bool Instruction::operator!=(const Instruction &c) const { return !this->operator==(c); } Instruction &Instruction::operator=(const Instruction &c) { if (this != &c) { this->dt = c.data(); } return *this; } QString Instruction::to_str(Address inst_addr) const { const InstructionMap &im = InstructionMapFind(dt); // TODO there are exception where some fields are zero and such so we should // not print them in such case SANITY_ASSERT(argdesbycode_filled, QString("argdesbycode_filled not initialized")); QString res; QString next_delim = " "; if (im.type == UNKNOWN) { return { "unknown" }; } if (this->dt == NOP.dt) { return { "nop" }; } res += im.name; for (const QString &arg_string : im.args) { res += next_delim; next_delim = ", "; for (int pos = 0; pos < arg_string.size(); pos += 1) { char arg_letter = arg_string[pos].toLatin1(); const ArgumentDesc *arg_desc = arg_desc_by_code[(unsigned char)arg_letter]; if (arg_desc == nullptr) { res += arg_letter; continue; } auto field = (int32_t)arg_desc->arg.decode(this->dt); if (arg_desc->min < 0) { field = extend(field, [&]() { int sum = (int)arg_desc->arg.shift; for (auto chunk : arg_desc->arg) { sum += chunk.count; } return sum; }()); } switch (arg_desc->kind) { case 'g': { if (symbolic_registers_enabled) { res += QString(Rv_regnames[field]); } else { res += "x" + QString::number(field); } break; } case 'p': case 'a': { field += (int32_t)inst_addr.get_raw(); res.append(str::asHex(uint32_t(field))); break; } case 'o': case 'n': { if (arg_desc->min < 0) { res += QString::number((int32_t)field, 10); } else { res.append(str::asHex(uint32_t(field))); } break; } case 'E': { if (symbolic_registers_enabled) { try { res += CSR::REGISTERS[CSR::REGISTER_MAP.at(CSR::Address(field))].name; } catch (std::out_of_range &e) { res.append(str::asHex(field)); } } else { res.append(str::asHex(field)); } break; } } } } return res; } QMultiMap str_to_instruction_code_map; static void instruction_from_string_build_base_aliases( uint32_t base_code, uint32_t base_mask, const InstructionMap *ia) { for (; ia->name != nullptr; ia++) { if ((ia->code ^ base_code) & base_mask) { ERROR( "alias code mismatch %s computed 0x%08" PRIx32 " (mask 0x%08" PRIx32 ") found 0x%08" PRIx32, ia->name, base_code, base_mask, ia->code); continue; } if (~ia->mask & base_mask) { ERROR( "aliase code mismatch %s computed 0x%08" PRIx32 " (mask 0x%08" PRIx32 ") found 0x%08" PRIx32 " with too wide mask 0x%08" PRIx32, ia->name, base_code, base_mask, ia->code, ia->mask); continue; } bool found = false; auto iter_range = str_to_instruction_code_map.equal_range(ia->name); for (auto i = iter_range.first; i != iter_range.second; i += 1) { if (i.value() == base_code) { found = true; break; } } if (found) continue; // store base code, the iteration over alliases is required anyway str_to_instruction_code_map.insert(ia->name, base_code); } } void instruction_from_string_build_base( const InstructionMap *im, BitField field, uint32_t base_code, uint32_t base_mask) { uint32_t code; uint8_t bits = field.count; uint8_t shift = field.offset; base_mask |= (((uint32_t)1 << bits) - 1) << shift; for (unsigned int i = 0; i < 1U << bits; i++, im++) { code = base_code | (i << shift); if (im->subclass) { instruction_from_string_build_base(im->subclass, im->subfield, code, base_mask); continue; } if (!(im->flags & IMF_SUPPORTED)) { continue; } if ((im->code ^ code) & base_mask) { ERROR( "code mismatch %s computed 0x%08" PRIx32 " (mask 0x%08" PRIx32 ") found 0x%08" PRIx32, im->name, code, base_mask, im->code); continue; } if (~im->mask & base_mask) { ERROR( "code mismatch %s computed 0x%08" PRIx32 " (mask 0x%08" PRIx32 ") found 0x%08" PRIx32 " with too wide mask 0x%08" PRIx32, im->name, code, base_mask, im->code, im->mask); continue; } str_to_instruction_code_map.insert(im->name, im->code); if (im->aliases != nullptr) instruction_from_string_build_base_aliases(im->code, im->mask, im->aliases); } #if 0 for (auto i = str_to_instruction_code_map.begin(); i != str_to_instruction_code_map.end(); i++) std::cout << i.key().toStdString() << ' '; #endif } void instruction_from_string_build_base() { return instruction_from_string_build_base(C_inst_map, instruction_map_opcode_field, 0, 0); } static int parse_reg_from_string(const QString &str, uint *chars_taken = nullptr) { if (str.size() < 2) { return -1; } if (str.at(0) == 'x') { int res = 0; int ctk = 1; for (; ctk < str.size(); ctk += 1) { auto c = str.at(ctk); if (c >= '0' && c <= '9') { res *= 10; res += c.unicode() - '0'; } else { break; } } if (ctk == 0) { return -1; } else { *chars_taken = ctk; return res; } } else { auto data = str.toLocal8Bit(); int regnum = -1; for (size_t i = 0; i < Rv_regnames.size(); i++) { size_t len = std::strlen(Rv_regnames[i]); if (size_t(data.size()) < len) continue; if (std::strncmp(data.data(), Rv_regnames[i], len) == 0) { *chars_taken = len; regnum = (int)i; } } return regnum; } } const QString reloc_operators = QStringLiteral("+-/*|&^~"); const QString reloc_special_chars = QStringLiteral("()%_"); /** Takes largest sequence of valid relocation expression chars and removes whitespaces */ static std::pair read_reloc_expression(const QString &input) { QString expression; uint32_t chars_taken = 0; bool prev_was_operator = false; bool is_modifier = false; for (QChar ch : input) { bool is_operator = reloc_operators.contains(ch); if (ch == '(' && !prev_was_operator) { if (is_modifier) { is_modifier = false; } else { // This is a start of a new field. break; } } if (ch.isLetterOrNumber() || is_operator || reloc_special_chars.contains(ch)) { expression.append(ch); if (ch == '%') { is_modifier = true; } } else if (!ch.isSpace()) { break; } chars_taken += 1; prev_was_operator = is_operator; } return { expression, chars_taken }; } static void reloc_append( RelocExpressionList *reloc, const QString &fl, Address inst_addr, int64_t offset, const ArgumentDesc *adesc, uint *chars_taken = nullptr, const QString &filename = "", int line = 0, Instruction::Modifier pseudo_mod = machine::Instruction::Modifier::NONE) { auto [expression, chars_taken_] = read_reloc_expression(fl); if (expression.size() > 0) { // Do not append empty relocation expressions reloc->append(new RelocExpression( inst_addr, expression, offset, adesc->min, adesc->max, &adesc->arg, filename, line, pseudo_mod)); } if (chars_taken != nullptr) { *chars_taken = chars_taken_; } } size_t Instruction::code_from_tokens( uint32_t *code, size_t buffsize, TokenizedInstruction &inst, RelocExpressionList *reloc, bool pseudoinst_enabled) { if (str_to_instruction_code_map.isEmpty()) { instruction_from_string_build_base(); } Instruction result = base_from_tokens(inst, reloc); if (result.data() != 0) { if (result.size() > buffsize) { // NOTE: this is bug, not user error. throw ParseError("insufficient buffer size to write parsed instruction"); } *code = result.data(); return result.size(); } if (pseudoinst_enabled) { size_t pseudo_result = pseudo_from_tokens(code, buffsize, inst, reloc); if (pseudo_result != 0) { return pseudo_result; } } throw ParseError("unknown instruction"); } size_t Instruction::pseudo_from_tokens( uint32_t *code, size_t buffsize, TokenizedInstruction &inst, RelocExpressionList *reloc) { constexpr Modifier UPPER = Modifier::COMPOSED_IMM_UPPER; constexpr Modifier LOWER = Modifier::COMPOSED_IMM_LOWER; if (inst.base == QLatin1String("nop")) { Instruction result; if (!inst.fields.empty()) { throw ParseError("`nop` does not allow any arguments"); } result = Instruction::NOP; *code = result.data(); return result.size(); } if ((inst.base == QLatin1String("la")) && (buffsize >= 8)) { if (inst.fields.size() != 2) { throw ParseError("number of arguments does not match"); } *code = base_from_tokens( { "auipc", inst.fields, inst.address, inst.filename, inst.line }, reloc, UPPER, -inst.address.get_raw()) .data(); code += 1; inst.fields.insert(0, inst.fields.at(0)); *code = base_from_tokens( { "addi", inst.fields, inst.address + 4, inst.filename, inst.line }, reloc, LOWER, -inst.address.get_raw()) .data(); return 8; } if ((inst.base == QLatin1String("li")) && (buffsize >= 8)) { if (inst.fields.size() != 2) { throw ParseError("number of arguments does not match"); } *code = base_from_tokens( { "lui", inst.fields, inst.address, inst.filename, inst.line }, reloc, UPPER) .data(); code += 1; inst.fields.insert(0, inst.fields.at(0)); *code = base_from_tokens( { "addi", inst.fields, inst.address + 4, inst.filename, inst.line }, reloc, LOWER) .data(); return 8; } if ((inst.base == QLatin1String("call")) && (buffsize >= 8)) { if (inst.fields.size() != 1) { throw ParseError("number of arguments does not match"); } inst.fields.insert(0, "x6"); *code = base_from_tokens( { "auipc", inst.fields, inst.address, inst.filename, inst.line }, reloc, UPPER, -inst.address.get_raw()) .data(); code += 1; inst.fields[0] = QString("x1"); inst.fields[1] = QString("%0(x6)").arg(inst.fields[1]); *code = base_from_tokens( { "jalr", inst.fields, inst.address + 4, inst.filename, inst.line }, reloc, LOWER, -inst.address.get_raw()) .data(); return 8; } if ((inst.base == QLatin1String("tail")) && (buffsize >= 8)) { if (inst.fields.size() != 1) { throw ParseError("number of arguments does not match"); } inst.fields.insert(0, "x6"); *code = base_from_tokens( { "auipc", inst.fields, inst.address, inst.filename, inst.line }, reloc, UPPER, -inst.address.get_raw()) .data(); code += 1; inst.fields[0] = QString("x0"); inst.fields[1] = QString("%0(x6)").arg(inst.fields[1]); *code = base_from_tokens( { "jalr", inst.fields, inst.address + 4, inst.filename, inst.line }, reloc, LOWER, -inst.address.get_raw()) .data(); return 8; } if (inst.base[0] == 's') { if ((inst.base == QLatin1String("sext.b")) && (buffsize >= 8)) { if (inst.fields.size() != 2) { throw ParseError("number of arguments does not match"); } inst.base = "slli"; inst.fields.append("XLEN-8"); *code = base_from_tokens(inst, reloc).data(); code += 1; inst.base = "srai"; inst.fields[1] = inst.fields[0]; *code = base_from_tokens(inst, reloc).data(); return 8; } if ((inst.base == QLatin1String("sext.h")) && (buffsize >= 8)) { if (inst.fields.size() != 2) { throw ParseError("number of arguments does not match"); } inst.base = "slli"; inst.fields.append("XLEN-16"); *code = base_from_tokens(inst, reloc).data(); code += 1; inst.base = "srai"; inst.fields[1] = inst.fields[0]; *code = base_from_tokens(inst, reloc).data(); return 8; } } if (inst.base[0] == 'z') { if ((inst.base == QLatin1String("zext.h")) && (buffsize >= 8)) { if (inst.fields.size() != 2) { throw ParseError("number of arguments does not match"); } inst.base = "slli"; inst.fields.append("XLEN-16"); *code = base_from_tokens(inst, reloc).data(); code += 1; inst.base = "srli"; inst.fields[1] = inst.fields[0]; *code = base_from_tokens(inst, reloc).data(); return 8; } if ((inst.base == QLatin1String("zext.w")) && (buffsize >= 8)) { if (inst.fields.size() != 2) { throw ParseError("number of arguments does not match"); } inst.base = "slli"; inst.fields.append("XLEN-32"); *code = base_from_tokens(inst, reloc).data(); code += 1; inst.base = "srli"; inst.fields[1] = inst.fields[0]; *code = base_from_tokens(inst, reloc).data(); return 8; } } return 0; } size_t Instruction::partially_apply( const char *base, int argument_count, int position, const char *value, uint32_t *code, size_t buffsize, TokenizedInstruction &inst, RelocExpressionList *reloc) { if (inst.fields.size() != argument_count) { throw ParseError("number of arguments does not match"); } inst.base = base; inst.fields.insert(position, value); return code_from_tokens(code, buffsize, inst, reloc, false); } static void instruction_code_map_next_im(const InstructionMap *&im, bool &processing_aliases) { if (!processing_aliases) { processing_aliases = true; im = im->aliases; } else { im++; if (im->name == nullptr) im = nullptr; } } Instruction Instruction::base_from_tokens( const TokenizedInstruction &inst, RelocExpressionList *reloc, Modifier pseudo_mod, uint64_t initial_immediate_value) { int rethrow = false; ParseError parse_error = ParseError("no match for arguments combination found"); auto iter_range = str_to_instruction_code_map.equal_range(inst.base); if (iter_range.first == iter_range.second) { DEBUG("Base instruction of the name %s not found.", qPrintable(inst.base)); return Instruction::UNKNOWN_INST; } // Process all codes associated with given instruction name and try matching the supplied // instruction field tokens to fields. First matching instruction is used. for (auto it = iter_range.first; it != iter_range.second; it++) { uint32_t inst_code = it.value(); bool processing_aliases = false; const InstructionMap *im = &InstructionMapFind(inst_code); for (; im != nullptr; instruction_code_map_next_im(im, processing_aliases)) { if (inst.base != im->name) continue; try { inst_code = im->code; if (inst.fields.count() != (int)im->args.size()) { if (!rethrow) { parse_error = ParseError("number of arguments does not match"); rethrow = true; } continue; } for (int field_index = 0; field_index < (int)im->args.size(); field_index++) { const QString &arg = im->args[field_index]; QString field_token = inst.fields[field_index]; inst_code |= parse_field( field_token, arg, inst.address, reloc, inst.filename, inst.line, pseudo_mod, initial_immediate_value); } return Instruction(inst_code); } catch (ParseError &pe) { rethrow = true; parse_error = pe; } } } if (rethrow) { throw parse_error; } DEBUG( "Base instruction of the name %s not matched to any known base format.", qPrintable(inst.base)); // Another instruction format for this base may be found in pseudoinstructions. return Instruction::UNKNOWN_INST; } uint16_t parse_csr_address(const QString &field_token, uint &chars_taken); bool parse_immediate_value( const QString &field_token, Address &inst_addr, RelocExpressionList *reloc, const QString &filename, unsigned int line, bool need_reloc, const ArgumentDesc *adesc, const Instruction::Modifier &effective_mod, uint64_t &val, uint &chars_taken); uint32_t Instruction::parse_field( QString &field_token, const QString &arg, Address inst_addr, RelocExpressionList *reloc, const QString &filename, unsigned int line, Modifier pseudo_mod, uint64_t initial_immediate_value) { uint32_t inst_code = 0; for (QChar ao : arg) { bool need_reloc = false; uint a = ao.toLatin1(); if (!a) { continue; } field_token = field_token.trimmed(); const ArgumentDesc *adesc = arg_desc_by_code[a]; if (adesc == nullptr) { if (!field_token.count()) { throw ParseError("empty argument encountered"); } if (field_token.at(0) != ao) { throw ParseError("argument does not match instruction template"); } field_token = field_token.mid(1); continue; } // Only apply modifier to immediate fields const Modifier effective_mod = (adesc->is_imm()) ? pseudo_mod : Modifier::NONE; uint64_t val = 0; uint chars_taken = 0; switch (adesc->kind) { case 'g': val += parse_reg_from_string(field_token, &chars_taken); break; case 'p': case 'a': val -= inst_addr.get_raw(); FALLTROUGH case 'o': case 'n': { val += initial_immediate_value; if (!parse_immediate_value( field_token, inst_addr, reloc, filename, line, need_reloc, adesc, effective_mod, val, chars_taken)) { throw ParseError( QString("field_token %1 is not a valid immediate value").arg(field_token)); } break; } case 'E': val = parse_csr_address(field_token, chars_taken); break; } if (chars_taken <= 0) { throw ParseError("argument parse error"); } if (effective_mod != Modifier::NONE) { val = modify_pseudoinst_imm(effective_mod, val); } else if (!adesc->is_value_in_field_range(val)) { throw ParseError("argument range exceed"); } inst_code |= adesc->arg.encode(val); field_token = field_token.mid(chars_taken); } if (field_token.trimmed() != "") { throw ParseError("excessive characters in argument"); } return inst_code; } bool parse_immediate_value( const QString &field_token, Address &inst_addr, RelocExpressionList *reloc, const QString &filename, unsigned int line, bool need_reloc, const ArgumentDesc *adesc, const Instruction::Modifier &effective_mod, uint64_t &val, uint &chars_taken) { if (field_token.at(0).isDigit() || field_token.at(0) == '-' || (reloc == nullptr)) { uint64_t num_val = 0; // Qt functions are limited, toLongLong would be usable // but does not return information how many characters // are processed. Used solution has horrible overhead // but is usable for now int i; char cstr[field_token.count() + 1]; for (i = 0; i < field_token.count(); i++) { cstr[i] = field_token.at(i).toLatin1(); } cstr[i] = 0; const char *p = cstr; char *r; if (adesc->min < 0) { num_val = strtoll(p, &r, 0); } else { num_val = strtoull(p, &r, 0); } while (*r && isspace(*r)) { r++; } chars_taken = r - p; if (*r && strchr("+-/*|&^~%", *r)) { need_reloc = true; } else { // extend signed bits val += num_val; } } else { need_reloc = true; } if (need_reloc && (reloc != nullptr)) { reloc_append( reloc, field_token, inst_addr, val, adesc, &chars_taken, filename, line, effective_mod); val = 0; } return chars_taken != 0; } uint16_t parse_csr_address(const QString &field_token, uint &chars_taken) { if (field_token.at(0).isLetter()) { try { size_t index = CSR::REGISTER_MAP_BY_NAME.at(field_token.toStdString()); auto ® = CSR::REGISTERS[index]; chars_taken = strlen(reg.name); return reg.address.data; } catch (std::out_of_range &e) { chars_taken = 0; return 0; } } else { char *r; uint64_t val; const char *str = field_token.toLocal8Bit().constData(); val = strtoul(str, &r, 0); chars_taken = r - str; return val; } } bool Instruction::update(int64_t val, RelocExpression *relocexp) { // Clear all bit of the updated argument. dt &= ~relocexp->arg->encode(~0); val += relocexp->offset; if (relocexp->pseudo_mod != Modifier::NONE) { val = (int64_t)modify_pseudoinst_imm(relocexp->pseudo_mod, val); } else { if ((val & ((1 << relocexp->arg->shift) - 1))) { return false; } if (relocexp->min < 0) { if (((int64_t)val < relocexp->min) || ((int64_t)val > relocexp->max)) { if (((int64_t)val - 0x100000000 < relocexp->min) || ((int64_t)val - 0x100000000 > relocexp->max)) { return false; } } } else { if (((uint64_t)val < (uint64_t)relocexp->min) || ((uint64_t)val > (uint64_t)relocexp->max)) { return false; } } } dt |= relocexp->arg->encode(val); return true; } constexpr uint64_t Instruction::modify_pseudoinst_imm(Instruction::Modifier mod, uint64_t value) { // Example: la rd, symbol -> auipc rd, symbol[31:12] + symbol[11], addi rd, rd, symbol[11:0] switch (mod) { case Modifier::NONE: return value; case Modifier::COMPOSED_IMM_UPPER: return get_bits(value, 31, 12) + get_bit(value, 11); case Modifier::COMPOSED_IMM_LOWER: return get_bits(value, 11, 0); default: UNREACHABLE } } // highlighter void Instruction::append_recognized_instructions(QStringList &list) { if (str_to_instruction_code_map.isEmpty()) { instruction_from_string_build_base(); } for (auto iter = str_to_instruction_code_map.keyBegin(); iter != str_to_instruction_code_map.keyEnd(); iter++) { list.append(*iter); } for (const auto &str : RECOGNIZED_PSEUDOINSTRUCTIONS) { list.append(str); } } void Instruction::set_symbolic_registers(bool enable) { symbolic_registers_enabled = enable; } inline int32_t Instruction::extend(uint32_t value, uint32_t used_bits) const { return value | ~((value & (1 << (used_bits - 1))) - 1); } void Instruction::append_recognized_registers(QStringList &list) { for (auto name : Rv_regnames) { list.append(name); } } uint8_t Instruction::size() const { return 4; } size_t Instruction::code_from_string( uint32_t *code, size_t buffsize, QString str, Address inst_addr, RelocExpressionList *reloc, const QString &filename, unsigned line, bool pseudoinst_enabled) { auto inst = TokenizedInstruction::from_line(std::move(str), inst_addr, filename, line); return Instruction::code_from_tokens(code, buffsize, inst, reloc, pseudoinst_enabled); } Instruction::ParseError::ParseError(QString message) : message(std::move(message)) {} TokenizedInstruction TokenizedInstruction::from_line( QString line_str, Address inst_addr, const QString &filename, unsigned line) { int start = 0, end; while (start < line_str.size()) { if (!line_str.at(start).isSpace()) { break; } start++; } end = start; while (end < line_str.size()) { if (line_str.at(end).isSpace()) { break; } end++; } QString inst_base = line_str.mid(start, end - start).toLower(); if (!inst_base.size()) { throw Instruction::ParseError("empty instruction field"); } line_str = line_str.mid(end + 1).trimmed(); QStringList inst_fields; if (line_str.size() > 0) { inst_fields = line_str.split(","); } return { inst_base, inst_fields, inst_addr, filename, line }; } TokenizedInstruction::TokenizedInstruction( QString base, QStringList fields, const Address &address, QString filename, unsigned int line) : base(std::move(base)) , fields(std::move(fields)) , address(address) , filename(std::move(filename)) , line(line) {} ================================================ FILE: src/machine/instruction.h ================================================ #ifndef INSTRUCTION_H #define INSTRUCTION_H #include "bitfield.h" #include "common/containers/cvector.h" #include "csr/address.h" #include "execute/alu.h" #include "machinedefs.h" #include #include #include #include #include #include namespace machine { // 4 is max number of parts in currently used instructions. using BitArg = SplitBitField<4>; static constexpr std::array Rv_regnames = { "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", }; enum InstructionFlags : unsigned { IMF_SUPPORTED = 1L << 0, /**< Instruction is supported */ IMF_MEMWRITE = 1L << 1, /**< Write to the memory when memory stage is reached */ IMF_MEMREAD = 1L << 2, /**< Read from the memory when memory stage is reached */ IMF_ALUSRC = 1L << 3, /**< The second ALU source is immediate operand */ IMF_REGWRITE = 1L << 4, /**< Instruction result (ALU or memory) is written to register file */ IMF_MEM = 1L << 5, /**< Instruction is memory access instruction */ IMF_ALU_REQ_RS = 1L << 6, /**< Execution phase/ALU requires RS value */ IMF_ALU_REQ_RT = 1L << 7, /**< Execution phase/ALU/mem requires RT value */ IMF_BRANCH = 1L << 8, /**< Operation is conditional or unconditional branch or branch and link when PC_TO_R31 is set */ IMF_JUMP = 1L << 9, /**< Jump operation - JAL, JALR */ IMF_BJ_NOT = 1L << 10, /**< Negate condition for branch instruction */ IMF_BRANCH_JALR = 1L << 11, /**< Use ALU output as branch/jump target. Used by JALR. */ IMF_EXCEPTION = 1L << 12, /**< Instruction causes synchronous exception */ IMF_ALU_MOD = 1L << 13, /**< ADD and right-shift modifier */ IMF_PC_TO_ALU = 1L << 14, /**< PC is loaded instead of RS to ALU */ IMF_FORCE_W_OP = 1L << 15, /**< Force word (32-bit) operation even for XLEN=64 */ IMF_ECALL = 1L << 16, // seems easiest to encode ecall and ebreak as flags, but they might IMF_EBREAK = 1L << 17, // be moved elsewhere in case we run out of InstructionFlag space. IMF_XRET = 1L << 18, /**< Return from exception, MRET and SRET */ // Extensions: // ============================================================================================= // RV64/32M IMF_MUL = 1L << 19, /**< Enables multiplication component of ALU. */ // Zicsr IMF_CSR = 1L << 20, /**< Implies csr read and write */ IMF_CSR_TO_ALU = 1L << 21, /**< Instruction modifies the current value */ IMF_ALU_RS_ID = 1L << 22, // RV64/32A - Atomic Memory Operations IMF_AMO = 1L << 23, /**< Instruction is AMO */ // TODO do we want to add those signals to the visualization? IMF_RV64 = 1L << 24, /**< Mark instructions which are available in 64-bit mode only. */ /* Privilege requirement flags */ IMF_PRIV_S = 1L << 25, /**< Requires at least Supervisor privilege */ IMF_PRIV_H = 1L << 26, /**< Requires at least Hypervisor privilege */ IMF_PRIV_M = 1L << 27, /**< Requires Machine privilege */ }; /** * Collection of data necessary to parse instruction from tokens. * * @TODO Switch to QStringView */ struct TokenizedInstruction { QString base; QStringList fields; Address address; QString filename; unsigned line; public: TokenizedInstruction( QString base, QStringList fields, const Address &address, QString filename, unsigned int line); /** Tokenize assembler line */ static TokenizedInstruction from_line(QString line_str, Address inst_addr, const QString &filename, unsigned line); }; struct RelocExpression; typedef QVector RelocExpressionList; class Instruction { public: Instruction(); explicit Instruction(uint32_t inst); // Instruction( // uint8_t opcode, // uint8_t rs, // uint8_t rt, // uint8_t rd, // uint8_t shamt, // uint8_t funct); // Type R // Instruction( // uint8_t opcode, // uint8_t rs, // uint8_t rt, // uint16_t immediate); // Type I // Instruction(uint8_t opcode, Address address); // Type J Instruction(const Instruction &); static const Instruction NOP; static const Instruction UNKNOWN_INST; enum Type { R, I, S, B, U, J, ZICSR, AMO, UNKNOWN }; /** Modified encoding to enable pseudoinstructions. */ enum class Modifier { /** Normal processing. All fields are checked for value max and min. */ NONE, /** * Encodes upper part of immediate from a pseudoinstruction. * * `imm = symbol[31:12] + symbol[11]` * NOTE: `symbol[11]` compensates for sign extension from addition of the lower part. */ COMPOSED_IMM_UPPER, /** * Encodes lower part of immediate from a pseudoinstruction. * * `imm = symbol[11:0]` * Upper bits of immediate may are discarded. */ COMPOSED_IMM_LOWER, }; struct ParseError; /** Returns size of instruction in bytes */ uint8_t size() const; uint8_t opcode() const; uint8_t rs() const; uint8_t rt() const; uint8_t rd() const; uint8_t shamt() const; uint16_t funct() const; machine::CSR::Address csr_address() const; int32_t immediate() const; Address address() const; uint32_t data() const; bool imm_sign() const; enum Type type() const; enum InstructionFlags flags() const; AluCombinedOp alu_op() const; enum AccessControl mem_ctl() const; void flags_alu_op_mem_ctl( enum InstructionFlags &flags, AluCombinedOp &alu_op, enum AccessControl &mem_ctl) const; bool operator==(const Instruction &c) const; bool operator!=(const Instruction &c) const; Instruction &operator=(const Instruction &c); QString to_str(Address inst_addr = Address::null()) const; /** * Parses instruction from string containing one assembler line. * * @throws Instruction::ParseError if unable to parse */ static size_t code_from_string( uint32_t *code, size_t buffsize, QString str, Address inst_addr, RelocExpressionList *reloc = nullptr, const QString &filename = "", unsigned line = 0, bool pseudoinst_enabled = true); /** * Parses instruction from prepare tokenized form. * * @throws Instruction::ParseError if unable to parse */ static size_t code_from_tokens( uint32_t *code, size_t buffsize, TokenizedInstruction &inst, RelocExpressionList *reloc = nullptr, bool pseudoinst_enabled = true); bool update(int64_t val, RelocExpression *relocexp); static void append_recognized_instructions(QStringList &list); static void set_symbolic_registers(bool enable); static void append_recognized_registers(QStringList &list); static constexpr uint64_t modify_pseudoinst_imm(Modifier mod, uint64_t value); private: uint32_t dt; static bool symbolic_registers_enabled; static Instruction base_from_tokens( const TokenizedInstruction &inst, RelocExpressionList *reloc, Modifier pseudo_mod = Modifier::NONE, uint64_t initial_immediate_value = 0); inline int32_t extend(uint32_t value, uint32_t used_bits) const; static uint32_t parse_field( QString &field_token, const QString &arg, Address inst_addr, RelocExpressionList *reloc, const QString &filename, unsigned int line, Modifier pseudo_mod, uint64_t initial_immediate_value); static size_t partially_apply( const char *base, int argument_count, int position, const char *value, uint32_t *code, size_t buffsize, TokenizedInstruction &inst, RelocExpressionList *reloc); static size_t pseudo_from_tokens( uint32_t *code, size_t buffsize, TokenizedInstruction &inst, RelocExpressionList *reloc); }; struct Instruction::ParseError : public std::exception { QString message; explicit ParseError(QString message); const char *what() const noexcept override { return message.toUtf8().data(); } }; struct RelocExpression { inline RelocExpression( Address location, QString expression, int64_t offset, int64_t min, int64_t max, const BitArg *arg, QString filename, int line, Instruction::Modifier pseudo_mod = Instruction::Modifier::NONE) { this->location = location; this->expression = std::move(expression); this->offset = offset; this->min = min; this->max = max; this->arg = arg; this->filename = std::move(filename); this->line = line; this->pseudo_mod = pseudo_mod; } Address location; QString expression; int64_t offset; int64_t min; int64_t max; const BitArg *arg; QString filename; int line; Instruction::Modifier pseudo_mod; }; } // namespace machine Q_DECLARE_METATYPE(machine::Instruction) #endif // INSTRUCTION_H ================================================ FILE: src/machine/instruction.test.cpp ================================================ #include "instruction.test.h" #include "instruction.h" using namespace machine; // Test that we are correctly encoding instructions in constructor void TestInstruction::instruction() { QCOMPARE(Instruction(0x0), Instruction()); // QCOMPARE(Instruction(0x4432146), Instruction(1, 2, 3, 4, 5, 6)); // QCOMPARE(Instruction(0x4430004), Instruction(1, 2, 3, 4)); // QCOMPARE(Instruction(0x4000002), Instruction(1, 2_addr)); } // Test that we are correctly decoding instruction fields void TestInstruction::instruction_access() { Instruction i(0xffffffff); QCOMPARE(i.data(), (uint32_t)0xffffffff); QCOMPARE(i.opcode(), (uint8_t)0x3f); QCOMPARE(i.rs(), (uint8_t)0x1f); QCOMPARE(i.rt(), (uint8_t)0x1f); QCOMPARE(i.rd(), (uint8_t)0x1f); QCOMPARE(i.shamt(), (uint8_t)0x1f); QCOMPARE(i.funct(), (uint16_t)0x3f); QCOMPARE(i.immediate(), (int32_t)0xffff); QCOMPARE(i.address().get_raw(), (uint64_t)0x3ffffff); } // TODO test to_str QTEST_APPLESS_MAIN(TestInstruction) ================================================ FILE: src/machine/instruction.test.h ================================================ #ifndef INSTRUCTION_TEST_H #define INSTRUCTION_TEST_H #include class TestInstruction : public QObject { Q_OBJECT public slots: void instruction(); void instruction_access(); }; #endif // INSTRUCTION_TEST_H ================================================ FILE: src/machine/machine.cpp ================================================ #include "machine.h" #include "programloader.h" #include #include #include using namespace machine; Machine::Machine(MachineConfig config, bool load_symtab, bool load_executable) : machine_config(std::move(config)) , stat(ST_READY) { regs.reset(new Registers()); if (load_executable) { ProgramLoader program(machine_config.elf()); this->machine_config.set_simulated_endian(program.get_endian()); mem_program_only.reset(new Memory(machine_config.get_simulated_endian())); program.to_memory(mem_program_only.data()); if (program.get_architecture_type() == ARCH64) this->machine_config.set_simulated_xlen(Xlen::_64); else this->machine_config.set_simulated_xlen(Xlen::_32); if (load_symtab) { symtab.reset(program.get_symbol_table()); } program_end = program.end(); if (program.get_executable_entry() != 0x0_addr) { regs->write_pc(program.get_executable_entry()); } mem.reset(new Memory(*mem_program_only)); } else { mem.reset(new Memory(machine_config.get_simulated_endian())); } data_bus.reset(new MemoryDataBus(machine_config.get_simulated_endian())); data_bus->insert_device_to_range(mem.data(), 0x00000000_addr, 0xefffffff_addr, false); setup_serial_port(); setup_perip_spi_led(); setup_lcd_display(); setup_aclint_mtime(); setup_aclint_mswi(); setup_aclint_sswi(); unsigned access_time_read = machine_config.memory_access_time_read(); unsigned access_time_write = machine_config.memory_access_time_write(); unsigned access_time_burst = machine_config.memory_access_time_burst(); bool access_enable_burst = machine_config.memory_access_enable_burst(); cch_level2.reset(new Cache( data_bus.data(), &machine_config.cache_level2(), access_time_read, access_time_write, access_time_burst, access_enable_burst)); if (machine_config.cache_level2().enabled()) { access_time_read = machine_config.memory_access_time_level2(); access_time_write = machine_config.memory_access_time_level2(); access_time_burst = 0; access_enable_burst = true; } cch_program.reset(new Cache( cch_level2.data(), &machine_config.cache_program(), access_time_read, access_time_write, access_time_burst, access_enable_burst)); cch_data.reset(new Cache( cch_level2.data(), &machine_config.cache_data(), access_time_read, access_time_write, access_time_burst, access_enable_burst)); controlst.reset( new CSR::ControlState(machine_config.get_simulated_xlen(), machine_config.get_isa_word())); tlb_program.reset(new TLB( cch_program.data(), PROGRAM, machine_config.access_tlb_program(), machine_config.get_vm_enabled())); tlb_data.reset(new TLB( cch_data.data(), DATA, machine_config.access_tlb_data(), machine_config.get_vm_enabled())); tlb_program->on_csr_write(CSR::Id::SATP, 0); tlb_data->on_csr_write(CSR::Id::SATP, 0); connect( controlst.data(), &CSR::ControlState::write_signal, tlb_program.data(), &machine::TLB::on_csr_write); connect( controlst.data(), &CSR::ControlState::write_signal, tlb_data.data(), &machine::TLB::on_csr_write); controlst->write_internal(CSR::Id::SATP, 0); predictor.reset(new BranchPredictor( machine_config.get_bp_enabled(), machine_config.get_bp_type(), machine_config.get_bp_init_state(), machine_config.get_bp_btb_bits(), machine_config.get_bp_bhr_bits(), machine_config.get_bp_bht_addr_bits())); if (machine_config.pipelined()) { cr.reset(new CorePipelined( regs.data(), predictor.data(), tlb_program.data(), tlb_data.data(), controlst.data(), machine_config.get_simulated_xlen(), machine_config.get_isa_word(), machine_config.hazard_unit())); } else { cr.reset(new CoreSingle( regs.data(), predictor.data(), tlb_program.data(), tlb_data.data(), controlst.data(), machine_config.get_simulated_xlen(), machine_config.get_isa_word())); } connect( this, &Machine::set_interrupt_signal, controlst.data(), &CSR::ControlState::set_interrupt_signal); run_t.reset(new QTimer(this)); set_speed(0); // In default run as fast as possible connect(run_t.data(), &QTimer::timeout, this, &Machine::step_timer); for (int i = 0; i < EXCAUSE_COUNT; i++) { if (i != EXCAUSE_INT && i != EXCAUSE_BREAK && i != EXCAUSE_HWBREAK) { set_stop_on_exception((enum ExceptionCause)i, machine_config.osemu_exception_stop()); set_step_over_exception((enum ExceptionCause)i, machine_config.osemu_exception_stop()); } } set_stop_on_exception(EXCAUSE_INT, machine_config.osemu_interrupt_stop()); set_step_over_exception(EXCAUSE_INT, false); } void Machine::setup_lcd_display() { perip_lcd_display = new LcdDisplay(machine_config.get_simulated_endian()); memory_bus_insert_range(perip_lcd_display, 0xffe00000_addr, 0xffe4afff_addr, true); if (machine_config.get_simulated_xlen() == Xlen::_64) memory_bus_insert_range( perip_lcd_display, 0xffffffffffe00000_addr, 0xffffffffffe4afff_addr, false); } void Machine::setup_perip_spi_led() { perip_spi_led = new PeripSpiLed(machine_config.get_simulated_endian()); memory_bus_insert_range(perip_spi_led, 0xffffc100_addr, 0xffffc1ff_addr, true); if (machine_config.get_simulated_xlen() == Xlen::_64) memory_bus_insert_range( perip_spi_led, 0xffffffffffffc100_addr, 0xffffffffffffc1ff_addr, false); } void Machine::setup_serial_port() { ser_port = new SerialPort(machine_config.get_simulated_endian()); memory_bus_insert_range(ser_port, 0xffffc000_addr, 0xffffc03f_addr, true); memory_bus_insert_range(ser_port, 0xffff0000_addr, 0xffff003f_addr, false); if (machine_config.get_simulated_xlen() == Xlen::_64) memory_bus_insert_range(ser_port, 0xffffffffffffc000_addr, 0xffffffffffffc03f_addr, false); connect(ser_port, &SerialPort::signal_interrupt, this, &Machine::set_interrupt_signal); } void Machine::setup_aclint_mtime() { aclint_mtimer = new aclint::AclintMtimer(machine_config.get_simulated_endian()); memory_bus_insert_range( aclint_mtimer, 0xfffd0000_addr + aclint::CLINT_MTIMER_OFFSET, 0xfffd0000_addr + aclint::CLINT_MTIMER_OFFSET + aclint::CLINT_MTIMER_SIZE - 1, true); if (machine_config.get_simulated_xlen() == Xlen::_64) memory_bus_insert_range( aclint_mtimer, 0xfffffffffffd0000_addr + aclint::CLINT_MTIMER_OFFSET, 0xfffffffffffd0000_addr + aclint::CLINT_MTIMER_OFFSET + aclint::CLINT_MTIMER_SIZE - 1, false); connect( aclint_mtimer, &aclint::AclintMtimer::signal_interrupt, this, &Machine::set_interrupt_signal); } void Machine::setup_aclint_mswi() { aclint_mswi = new aclint::AclintMswi(machine_config.get_simulated_endian()); memory_bus_insert_range( aclint_mswi, 0xfffd0000_addr + aclint::CLINT_MSWI_OFFSET, 0xfffd0000_addr + aclint::CLINT_MSWI_OFFSET + aclint::CLINT_MSWI_SIZE - 1, true); if (machine_config.get_simulated_xlen() == Xlen::_64) memory_bus_insert_range( aclint_mswi, 0xfffffffffffd0000_addr + aclint::CLINT_MSWI_OFFSET, 0xfffffffffffd0000_addr + aclint::CLINT_MSWI_OFFSET + aclint::CLINT_MSWI_SIZE - 1, false); connect( aclint_mswi, &aclint::AclintMswi::signal_interrupt, this, &Machine::set_interrupt_signal); } void Machine::setup_aclint_sswi() { aclint_sswi = new aclint::AclintSswi(machine_config.get_simulated_endian()); memory_bus_insert_range( aclint_sswi, 0xfffd0000_addr + aclint::CLINT_SSWI_OFFSET, 0xfffd0000_addr + aclint::CLINT_SSWI_OFFSET + aclint::CLINT_SSWI_SIZE - 1, true); if (machine_config.get_simulated_xlen() == Xlen::_64) memory_bus_insert_range( aclint_sswi, 0xfffffffffffd0000_addr + aclint::CLINT_SSWI_OFFSET, 0xfffffffffffd0000_addr + aclint::CLINT_SSWI_OFFSET + aclint::CLINT_SSWI_SIZE - 1, false); connect( aclint_sswi, &aclint::AclintSswi::signal_interrupt, this, &Machine::set_interrupt_signal); } Machine::~Machine() { run_t.reset(); cr.reset(); controlst.reset(); regs.reset(); mem.reset(); cch_program.reset(); cch_data.reset(); cch_level2.reset(); data_bus.reset(); mem_program_only.reset(); symtab.reset(); predictor.reset(); } const MachineConfig &Machine::config() { return machine_config; } void Machine::set_speed(unsigned int ips, unsigned int time_chunk_ms) { this->time_chunk = time_chunk_ms; run_t->setInterval(ips); if (run_t->isActive()) { // Clock settings changed. start_core_clock(); } } const Registers *Machine::registers() { return regs.data(); } const CSR::ControlState *Machine::control_state() { return controlst.data(); } const Memory *Machine::memory() { return mem.data(); } Memory *Machine::memory_rw() { return mem.data(); } const Cache *Machine::cache_program() { return cch_program.data(); } const Cache *Machine::cache_data() { return cch_data.data(); } const Cache *Machine::cache_level2() { return cch_level2.data(); } const BranchPredictor *Machine::branch_predictor() { return predictor.data(); } Cache *Machine::cache_data_rw() { return cch_data.data(); } void Machine::cache_sync() { if (!cch_program.isNull()) { cch_program->sync(); } if (!cch_data.isNull()) { cch_data->sync(); } if (!cch_level2.isNull()) { cch_level2->sync(); } } void Machine::tlb_sync() { if (tlb_program) { tlb_program->sync(); } if (tlb_data) { tlb_data->sync(); } } const TLB *Machine::get_tlb_program() const { return tlb_program ? &*tlb_program : nullptr; } const TLB *Machine::get_tlb_data() const { return tlb_data ? &*tlb_data : nullptr; } TLB *Machine::get_tlb_program_rw() { return tlb_program ? &*tlb_program : nullptr; } TLB *Machine::get_tlb_data_rw() { return tlb_data ? &*tlb_data : nullptr; } const MemoryDataBus *Machine::memory_data_bus() { return data_bus.data(); } MemoryDataBus *Machine::memory_data_bus_rw() { return data_bus.data(); } SerialPort *Machine::serial_port() { return ser_port; } PeripSpiLed *Machine::peripheral_spi_led() { return perip_spi_led; } LcdDisplay *Machine::peripheral_lcd_display() { return perip_lcd_display; } SymbolTable *Machine::symbol_table_rw(bool create) { if (create && (symtab.isNull())) { symtab.reset(new SymbolTable); } return symtab.data(); } const SymbolTable *Machine::symbol_table(bool create) { return symbol_table_rw(create); } void Machine::set_symbol( const QString &name, uint32_t value, uint32_t size, unsigned char info, unsigned char other) { if (symtab.isNull()) { symtab.reset(new SymbolTable); } symtab->set_symbol(name, value, size, info, other); } const Core *Machine::core() { return cr.data(); } const CoreSingle *Machine::core_singe() { return machine_config.pipelined() ? nullptr : (const CoreSingle *)cr.data(); } const CorePipelined *Machine::core_pipelined() { return machine_config.pipelined() ? (const CorePipelined *)cr.data() : nullptr; } bool Machine::executable_loaded() const { return (!mem_program_only.isNull()); } enum Machine::Status Machine::status() { return stat; } bool Machine::exited() { return stat == ST_EXIT || stat == ST_TRAPPED; } // We don't allow to call control methods when machine exited or if it's busy // We rather silently fail. #define CTL_GUARD \ do { \ if (exited() || stat == ST_BUSY) return; \ } while (false) void Machine::play() { CTL_GUARD; set_status(ST_RUNNING); start_core_clock(); step_internal(true); emit play_initiated(); } void Machine::pause() { if (stat != ST_BUSY) { CTL_GUARD; } set_status(ST_READY); stop_core_clock(); emit play_paused(); } void Machine::step_internal(bool skip_break) { CTL_GUARD; enum Status stat_prev = stat; set_status(ST_BUSY); emit tick(); try { QElapsedTimer timer; timer.start(); do { cr->step(skip_break); } while (time_chunk != 0 && stat == ST_BUSY && !skip_break && timer.elapsed() < (int)time_chunk); } catch (SimulatorException &e) { stop_core_clock(); set_status(ST_TRAPPED); emit program_trap(e); return; } if (regs->read_pc() >= program_end) { stop_core_clock(); set_status(ST_EXIT); emit program_exit(); } else { if (stat == ST_BUSY) { set_status(stat_prev); } } emit post_tick(); } void Machine::start_core_clock() { // Handle frequency measurement. last_cycle_count = cr->get_cycle_count(); if (run_t->interval() == 0) { // The clock is not fixed, we need to measure it. frequency_timer.start(); } else { // Clocked is fixed. emit report_core_frequency(1e3 / (double)run_t->interval()); } run_t->start(); } void Machine::stop_core_clock() { run_t->stop(); frequency_timer.invalidate(); emit report_core_frequency(0.0); } void Machine::step() { step_internal(true); } void Machine::step_timer() { if (run_t->interval() == 0 && time_chunk == 0) { // We need to amortize QTimer event loop overhead when running in max speed mode. for (size_t i = 0; i < 32 && stat == ST_RUNNING; i++) { step_internal(); } } else { step_internal(); } // Compute core frequency each 0x100 cycles auto total_cycle_count = cr->get_cycle_count(); auto cycle_count = total_cycle_count - last_cycle_count; if (cycle_count >= 0x2000) { double period_ns = (double)(frequency_timer.nsecsElapsed()) / cycle_count; if (period_ns < 0.01) { return; } last_cycle_count = cr->get_cycle_count(); emit frequency_timer.start(); report_core_frequency(1e9 / period_ns); } } void Machine::restart() { pause(); regs->reset(); if (!mem_program_only.isNull()) { mem->reset(*mem_program_only); } cch_program->reset(); cch_data->reset(); cch_level2->reset(); cr->reset(); set_status(ST_READY); } void Machine::set_status(enum Status st) { bool change = st != stat; stat = st; if (change) { emit status_change(st); } } void Machine::register_exception_handler(ExceptionCause excause, ExceptionHandler *exhandler) { if (!cr.isNull()) { cr->register_exception_handler(excause, exhandler); } } bool Machine::memory_bus_insert_range( BackendMemory *mem_acces, Address start_addr, Address last_addr, bool move_ownership) { if (data_bus.isNull()) { return false; } return data_bus->insert_device_to_range(mem_acces, start_addr, last_addr, move_ownership); } void Machine::insert_hwbreak(Address address) { if (!cr.isNull()) { cr->insert_hwbreak(address); } } void Machine::remove_hwbreak(Address address) { if (!cr.isNull()) { cr->remove_hwbreak(address); } } bool Machine::is_hwbreak(Address address) { if (!cr.isNull()) { return cr->is_hwbreak(address); } return false; } void Machine::set_stop_on_exception(enum ExceptionCause excause, bool value) { if (!cr.isNull()) { cr->set_stop_on_exception(excause, value); } } bool Machine::get_stop_on_exception(enum ExceptionCause excause) const { if (!cr.isNull()) { return cr->get_stop_on_exception(excause); } return false; } void Machine::set_step_over_exception(enum ExceptionCause excause, bool value) { if (!cr.isNull()) { cr->set_step_over_exception(excause, value); } } bool Machine::get_step_over_exception(enum ExceptionCause excause) const { if (!cr.isNull()) { return cr->get_step_over_exception(excause); } return false; } enum ExceptionCause Machine::get_exception_cause() const { uint32_t val; if (controlst.isNull()) { return EXCAUSE_NONE; } val = (controlst->read_internal(CSR::Id::MCAUSE).as_u64()); if (val & 0xffffffff80000000) { return EXCAUSE_INT; } else { return (ExceptionCause)val; } } ================================================ FILE: src/machine/machine.h ================================================ #ifndef MACHINE_H #define MACHINE_H #include "core.h" #include "machineconfig.h" #include "memory/backend/aclintmswi.h" #include "memory/backend/aclintmtimer.h" #include "memory/backend/aclintsswi.h" #include "memory/backend/lcddisplay.h" #include "memory/backend/peripheral.h" #include "memory/backend/peripspiled.h" #include "memory/backend/serialport.h" #include "memory/cache/cache.h" #include "memory/memory_bus.h" #include "memory/tlb/tlb.h" #include "predictor.h" #include "registers.h" #include "simulator_exception.h" #include "symboltable.h" #include #include #include #include namespace machine { class Machine : public QObject { Q_OBJECT public: explicit Machine(MachineConfig config, bool load_symtab = false, bool load_executable = true); ~Machine() override; const MachineConfig &config(); void set_speed(unsigned int ips, unsigned int time_chunk = 0); const Registers *registers(); const CSR::ControlState *control_state(); const Memory *memory(); Memory *memory_rw(); const Cache *cache_program(); const Cache *cache_data(); const Cache *cache_level2(); const BranchPredictor *branch_predictor(); Cache *cache_data_rw(); void cache_sync(); const TLB *get_tlb_program() const; const TLB *get_tlb_data() const; TLB *get_tlb_program_rw(); TLB *get_tlb_data_rw(); void tlb_sync(); const MemoryDataBus *memory_data_bus(); MemoryDataBus *memory_data_bus_rw(); SerialPort *serial_port(); PeripSpiLed *peripheral_spi_led(); LcdDisplay *peripheral_lcd_display(); const SymbolTable *symbol_table(bool create = false); SymbolTable *symbol_table_rw(bool create = false); void set_symbol( const QString &name, uint32_t value, uint32_t size, unsigned char info = 0, unsigned char other = 0); const Core *core(); const CoreSingle *core_singe(); const CorePipelined *core_pipelined(); bool executable_loaded() const; enum Status { ST_READY, // Machine is ready to be started or step to be called ST_RUNNING, // Machine is running ST_BUSY, // Machine is calculating step ST_EXIT, // Machine exited ST_TRAPPED // Machine exited with failure }; enum Status status(); bool exited(); void register_exception_handler(ExceptionCause excause, ExceptionHandler *exhandler); bool memory_bus_insert_range( BackendMemory *mem_acces, Address start_addr, Address last_addr, bool move_ownership); void insert_hwbreak(Address address); void remove_hwbreak(Address address); bool is_hwbreak(Address address); void set_stop_on_exception(enum ExceptionCause excause, bool value); bool get_stop_on_exception(enum ExceptionCause excause) const; void set_step_over_exception(enum ExceptionCause excause, bool value); bool get_step_over_exception(enum ExceptionCause excause) const; enum ExceptionCause get_exception_cause() const; Address virtual_to_physical(AddressWithMode v) { if (tlb_data) { return tlb_data->translate_virtual_to_physical(v); } else { return v; } } public slots: void play(); void pause(); void step(); void restart(); signals: void program_exit(); void program_trap(machine::SimulatorException &e); void status_change(enum machine::Machine::Status st); void tick(); // Time tick void post_tick(); // Emitted after tick to allow updates void set_interrupt_signal(uint irq_num, bool active); void play_initiated(); void play_paused(); void report_core_frequency(double); private slots: void step_timer(); private: void step_internal(bool skip_break = false); void start_core_clock(); void stop_core_clock(); MachineConfig machine_config; Box regs; Box mem; /** * Memory with loaded program only. * It is not used for execution, only for quick * simulation reset without repeated ELF file loading. */ Box mem_program_only; Box data_bus; // Peripherals are owned by data_bus SerialPort *ser_port = nullptr; PeripSpiLed *perip_spi_led = nullptr; LcdDisplay *perip_lcd_display = nullptr; aclint::AclintMtimer *aclint_mtimer = nullptr; aclint::AclintMswi *aclint_mswi = nullptr; aclint::AclintSswi *aclint_sswi = nullptr; Box cch_level2; Box cch_program; Box cch_data; Box tlb_data; Box tlb_program; Box controlst; Box predictor; Box cr; Box run_t; unsigned int time_chunk = { 0 }; // Used to monitor the real CPU frequency QElapsedTimer frequency_timer; uint64_t last_cycle_count = 0; Box symtab; Address program_end = 0xffff0000_addr; enum Status stat = ST_READY; void set_status(enum Status st); void setup_serial_port(); void setup_perip_spi_led(); void setup_lcd_display(); void setup_aclint_mtime(); void setup_aclint_mswi(); void setup_aclint_sswi(); }; } // namespace machine #endif // MACHINE_H ================================================ FILE: src/machine/machineconfig.cpp ================================================ #include "machineconfig.h" #include "common/endian.h" #include #include using namespace machine; ////////////////////////////////////////////////////////////////////////////// /// Default config of MachineConfig #define DF_PIPELINE false #define DF_DELAYSLOT true #define DF_HUNIT HU_STALL_FORWARD #define DF_EXEC_PROTEC false #define DF_WRITE_PROTEC false #define DF_MEM_ACC_READ 10 #define DF_MEM_ACC_WRITE 10 #define DF_MEM_ACC_BURST 0 #define DF_MEM_ACC_LEVEL2 2 #define DF_MEM_ACC_BURST_ENABLE false #define DF_ELF QString("") /// Default config of branch predictor #define DFC_BP_ENABLED false #define DFC_BP_TYPE PredictorType::SMITH_1_BIT #define DFC_BP_INIT_STATE PredictorState::NOT_TAKEN #define DFC_BP_BTB_BITS 2 #define DFC_BP_BHR_BITS 0 #define DFC_BP_BHT_ADDR_BITS 2 /// Default config of Virtual Memory #define DFC_VM_ENABLED false #define DFC_TLB_SETS 16 #define DFC_TLB_ASSOC 1 #define DFC_TLB_REPLAC RP_LRU ////////////////////////////////////////////////////////////////////////////// /// Default config of CacheConfig #define DFC_EN false #define DFC_SETS 1 #define DFC_BLOCKS 1 #define DFC_ASSOC 1 #define DFC_REPLAC RP_RAND #define DFC_WRITE WP_THROUGH_NOALLOC ////////////////////////////////////////////////////////////////////////////// CacheConfig::CacheConfig() { en = DFC_EN; n_sets = DFC_SETS; n_blocks = DFC_BLOCKS; d_associativity = DFC_ASSOC; replac_pol = DFC_REPLAC; write_pol = DFC_WRITE; } CacheConfig::CacheConfig(const CacheConfig *cc) { en = cc->enabled(); n_sets = cc->set_count(); n_blocks = cc->block_size(); d_associativity = cc->associativity(); replac_pol = cc->replacement_policy(); write_pol = cc->write_policy(); } #define N(STR) (prefix + QString(STR)) CacheConfig::CacheConfig(const QSettings *sts, const QString &prefix) { en = sts->value(N("Enabled"), DFC_EN).toBool(); n_sets = sts->value(N("Sets"), DFC_SETS).toUInt(); n_blocks = sts->value(N("Blocks"), DFC_BLOCKS).toUInt(); d_associativity = sts->value(N("Associativity"), DFC_ASSOC).toUInt(); replac_pol = (enum ReplacementPolicy)sts->value(N("Replacement"), DFC_REPLAC).toUInt(); write_pol = (enum WritePolicy)sts->value(N("Write"), DFC_WRITE).toUInt(); } void CacheConfig::store(QSettings *sts, const QString &prefix) const { sts->setValue(N("Enabled"), enabled()); sts->setValue(N("Sets"), set_count()); sts->setValue(N("Blocks"), block_size()); sts->setValue(N("Associativity"), associativity()); sts->setValue(N("Replacement"), (unsigned)replacement_policy()); sts->setValue(N("Write"), (unsigned)write_policy()); } #undef N void CacheConfig::preset(enum ConfigPresets p) { switch (p) { case CP_PIPE: case CP_SINGLE_CACHE: set_enabled(true); set_set_count(4); set_block_size(2); set_associativity(2); set_replacement_policy(RP_RAND); set_write_policy(WP_THROUGH_NOALLOC); break; case CP_SINGLE: case CP_PIPE_NO_HAZARD: set_enabled(false); } } void CacheConfig::set_enabled(bool v) { en = v; } void CacheConfig::set_set_count(unsigned v) { n_sets = v > 0 ? v : 1; } void CacheConfig::set_block_size(unsigned v) { n_blocks = v > 0 ? v : 1; } void CacheConfig::set_associativity(unsigned v) { d_associativity = v > 0 ? v : 1; } void CacheConfig::set_replacement_policy(enum ReplacementPolicy v) { replac_pol = v; } void CacheConfig::set_write_policy(enum WritePolicy v) { write_pol = v; } bool CacheConfig::enabled() const { return en; } unsigned CacheConfig::set_count() const { return n_sets; } unsigned CacheConfig::block_size() const { return n_blocks; } unsigned CacheConfig::associativity() const { return d_associativity; } enum CacheConfig::ReplacementPolicy CacheConfig::replacement_policy() const { return replac_pol; } enum CacheConfig::WritePolicy CacheConfig::write_policy() const { return write_pol; } bool CacheConfig::operator==(const CacheConfig &c) const { #define CMP(GETTER) (GETTER)() == (c.GETTER)() return CMP(enabled) && CMP(set_count) && CMP(block_size) && CMP(associativity) && CMP(replacement_policy) && CMP(write_policy); #undef CMP } bool CacheConfig::operator!=(const CacheConfig &c) const { return !operator==(c); } ////////////////////////////////////////////////////////////////////////////// TLBConfig::TLBConfig() { vm_asid = 0; n_sets = DFC_TLB_SETS; d_associativity = DFC_TLB_ASSOC; replac_pol = (enum ReplacementPolicy)DFC_TLB_REPLAC; } TLBConfig::TLBConfig(const TLBConfig *tc) { if (tc == nullptr) { vm_asid = 0; n_sets = DFC_TLB_SETS; d_associativity = DFC_TLB_ASSOC; replac_pol = (enum ReplacementPolicy)DFC_TLB_REPLAC; return; } vm_asid = tc->get_vm_asid(); n_sets = tc->get_tlb_num_sets(); d_associativity = tc->get_tlb_associativity(); replac_pol = tc->get_tlb_replacement_policy(); } #define N(STR) (prefix + QString(STR)) TLBConfig::TLBConfig(const QSettings *sts, const QString &prefix) { vm_asid = sts->value(N("VM_ASID"), 0u).toUInt(); n_sets = sts->value(N("NumSets"), DFC_TLB_SETS).toUInt(); d_associativity = sts->value(N("Associativity"), DFC_TLB_ASSOC).toUInt(); replac_pol = (enum ReplacementPolicy)sts->value(N("Policy"), DFC_TLB_REPLAC).toUInt(); } void TLBConfig::store(QSettings *sts, const QString &prefix) const { sts->setValue(N("VM_ASID"), get_vm_asid()); sts->setValue(N("NumSets"), get_tlb_num_sets()); sts->setValue(N("Associativity"), get_tlb_associativity()); sts->setValue(N("Policy"), (unsigned)get_tlb_replacement_policy()); } #undef N void TLBConfig::preset(enum ConfigPresets p) { switch (p) { case CP_PIPE: case CP_SINGLE_CACHE: case CP_SINGLE: case CP_PIPE_NO_HAZARD: vm_asid = 0; n_sets = DFC_TLB_SETS; d_associativity = DFC_TLB_ASSOC; replac_pol = (enum ReplacementPolicy)DFC_TLB_REPLAC; } } void TLBConfig::set_vm_asid(uint32_t a) { vm_asid = a; } uint32_t TLBConfig::get_vm_asid() const { return vm_asid; } void TLBConfig::set_tlb_num_sets(unsigned v) { n_sets = v > 0 ? v : 1; } void TLBConfig::set_tlb_associativity(unsigned v) { d_associativity = v > 0 ? v : 1; } void TLBConfig::set_tlb_replacement_policy(TLBConfig::ReplacementPolicy p) { replac_pol = p; } unsigned TLBConfig::get_tlb_num_sets() const { return n_sets; } unsigned TLBConfig::get_tlb_associativity() const { return d_associativity; } TLBConfig::ReplacementPolicy TLBConfig::get_tlb_replacement_policy() const { return replac_pol; } bool TLBConfig::operator==(const TLBConfig &c) const { #define CMP(GETTER) (GETTER)() == (c.GETTER)() return CMP(get_vm_asid) && CMP(get_tlb_num_sets) && CMP(get_tlb_associativity) && CMP(get_tlb_replacement_policy); #undef CMP } bool TLBConfig::operator!=(const TLBConfig &c) const { return !operator==(c); } ////////////////////////////////////////////////////////////////////////////// MachineConfig::MachineConfig() { simulated_endian = LITTLE; simulated_xlen = Xlen::_32; isa_word = config_isa_word_default; pipeline = DF_PIPELINE; delayslot = DF_DELAYSLOT; hunit = DF_HUNIT; exec_protect = DF_EXEC_PROTEC; write_protect = DF_WRITE_PROTEC; mem_acc_read = DF_MEM_ACC_READ; mem_acc_write = DF_MEM_ACC_WRITE; mem_acc_burst = DF_MEM_ACC_BURST; mem_acc_level2 = DF_MEM_ACC_LEVEL2; mem_acc_enable_burst = DF_MEM_ACC_BURST_ENABLE; osem_enable = true; osem_known_syscall_stop = true; osem_unknown_syscall_stop = true; osem_interrupt_stop = true; osem_exception_stop = true; osem_fs_root = ""; res_at_compile = true; elf_path = DF_ELF; cch_program = CacheConfig(); cch_data = CacheConfig(); cch_level2 = CacheConfig(); // Branch predictor bp_enabled = DFC_BP_ENABLED; bp_type = DFC_BP_TYPE; bp_init_state = DFC_BP_INIT_STATE; bp_btb_bits = DFC_BP_BTB_BITS; bp_bhr_bits = DFC_BP_BHR_BITS; bp_bht_addr_bits = DFC_BP_BHT_ADDR_BITS; bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; // Virtual memory vm_enabled = DFC_VM_ENABLED; tlb_program = TLBConfig(); tlb_data = TLBConfig(); } MachineConfig::MachineConfig(const MachineConfig *config) { simulated_endian = config->get_simulated_endian(); simulated_xlen = config->get_simulated_xlen(); isa_word = config->get_isa_word(); pipeline = config->pipelined(); delayslot = config->delay_slot(); hunit = config->hazard_unit(); exec_protect = config->memory_execute_protection(); write_protect = config->memory_write_protection(); mem_acc_read = config->memory_access_time_read(); mem_acc_write = config->memory_access_time_write(); mem_acc_burst = config->memory_access_time_burst(); mem_acc_level2 = config->memory_access_time_level2(); mem_acc_enable_burst = config->memory_access_enable_burst(); osem_enable = config->osemu_enable(); osem_known_syscall_stop = config->osemu_known_syscall_stop(); osem_unknown_syscall_stop = config->osemu_unknown_syscall_stop(); osem_interrupt_stop = config->osemu_interrupt_stop(); osem_exception_stop = config->osemu_exception_stop(); osem_fs_root = config->osemu_fs_root(); res_at_compile = config->reset_at_compile(); elf_path = config->elf(); cch_program = config->cache_program(); cch_data = config->cache_data(); cch_level2 = config->cache_level2(); // Branch predictor bp_enabled = config->get_bp_enabled(); bp_type = config->get_bp_type(); bp_init_state = config->get_bp_init_state(); bp_btb_bits = config->get_bp_btb_bits(); bp_bhr_bits = config->get_bp_bhr_bits(); bp_bht_addr_bits = config->get_bp_bht_addr_bits(); bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; // Virtual memory vm_enabled = config->get_vm_enabled(); tlb_program = config->tlbc_program(); tlb_data = config->tlbc_data(); } #define N(STR) (prefix + QString(STR)) MachineConfig::MachineConfig(const QSettings *sts, const QString &prefix) { simulated_endian = LITTLE; unsigned int xlen_num_bits = sts->value(N("XlenBits"), 32).toUInt(); simulated_xlen = xlen_num_bits == 64 ? Xlen::_64 : Xlen::_32; isa_word = ConfigIsaWord(sts->value(N("IsaWord"), config_isa_word_default.toUnsigned()).toUInt()); isa_word |= config_isa_word_default & config_isa_word_fixed; isa_word &= config_isa_word_default | ~config_isa_word_fixed; pipeline = sts->value(N("Pipelined"), DF_PIPELINE).toBool(); delayslot = sts->value(N("DelaySlot"), DF_DELAYSLOT).toBool(); hunit = (enum HazardUnit)sts->value(N("HazardUnit"), DF_HUNIT).toUInt(); exec_protect = sts->value(N("MemoryExecuteProtection"), DF_EXEC_PROTEC).toBool(); write_protect = sts->value(N("MemoryWriteProtection"), DF_WRITE_PROTEC).toBool(); mem_acc_read = sts->value(N("MemoryRead"), DF_MEM_ACC_READ).toUInt(); mem_acc_write = sts->value(N("MemoryWrite"), DF_MEM_ACC_WRITE).toUInt(); mem_acc_burst = sts->value(N("MemoryBurst"), DF_MEM_ACC_BURST).toUInt(); mem_acc_level2 = sts->value(N("MemoryLevel2"), DF_MEM_ACC_LEVEL2).toUInt(); mem_acc_enable_burst = sts->value(N("MemoryBurstEnable"), DF_MEM_ACC_BURST_ENABLE).toBool(); osem_enable = sts->value(N("OsemuEnable"), true).toBool(); osem_known_syscall_stop = sts->value(N("OsemuKnownSyscallStop"), true).toBool(); osem_unknown_syscall_stop = sts->value(N("OsemuUnknownSyscallStop"), true).toBool(); osem_interrupt_stop = sts->value(N("OsemuInterruptStop"), true).toBool(); osem_exception_stop = sts->value(N("OsemuExceptionStop"), true).toBool(); osem_fs_root = sts->value(N("OsemuFilesystemRoot"), "").toString(); res_at_compile = sts->value(N("ResetAtCompile"), true).toBool(); elf_path = sts->value(N("Elf"), DF_ELF).toString(); cch_program = CacheConfig(sts, N("ProgramCache_")); cch_data = CacheConfig(sts, N("DataCache_")); cch_level2 = CacheConfig(sts, N("Level2Cache_")); // Branch predictor bp_enabled = sts->value(N("BranchPredictor_Enabled"), DFC_BP_ENABLED).toBool(); bp_type = (PredictorType)sts->value(N("BranchPredictor_Type"), (uint8_t)DFC_BP_TYPE).toUInt(); bp_init_state = (PredictorState)sts->value(N("BranchPredictor_InitState"), (uint8_t)DFC_BP_INIT_STATE) .toUInt(); bp_btb_bits = sts->value(N("BranchPredictor_BitsBTB"), DFC_BP_BTB_BITS).toUInt(); bp_bhr_bits = sts->value(N("BranchPredictor_BitsBHR"), DFC_BP_BHR_BITS).toUInt(); bp_bht_addr_bits = sts->value(N("BranchPredictor_BitsBHTAddr"), DFC_BP_BHT_ADDR_BITS).toUInt(); bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; // Virtual memory vm_enabled = sts->value(N("VMEnabled"), DFC_VM_ENABLED).toBool(); tlb_data = TLBConfig(sts, N("DataTLB_")); tlb_program = TLBConfig(sts, N("ProgramTLB_")); } void MachineConfig::store(QSettings *sts, const QString &prefix) { sts->setValue(N("XlenBits"), get_simulated_xlen() == Xlen::_64 ? 64 : 32); sts->setValue(N("IsaWord"), get_isa_word().toUnsigned()); sts->setValue(N("Pipelined"), pipelined()); sts->setValue(N("DelaySlot"), delay_slot()); sts->setValue(N("HazardUnit"), (unsigned)hazard_unit()); sts->setValue(N("MemoryRead"), memory_access_time_read()); sts->setValue(N("MemoryWrite"), memory_access_time_write()); sts->setValue(N("MemoryBurst"), memory_access_time_burst()); sts->setValue(N("MemoryLevel2"), memory_access_time_level2()); sts->setValue(N("MemoryBurstEnable"), memory_access_enable_burst()); sts->setValue(N("OsemuEnable"), osemu_enable()); sts->setValue(N("OsemuKnownSyscallStop"), osemu_known_syscall_stop()); sts->setValue(N("OsemuUnknownSyscallStop"), osemu_unknown_syscall_stop()); sts->setValue(N("OsemuInterruptStop"), osemu_interrupt_stop()); sts->setValue(N("OsemuExceptionStop"), osemu_exception_stop()); sts->setValue(N("OsemuFilesystemRoot"), osemu_fs_root()); sts->setValue(N("ResetAtCompile"), reset_at_compile()); sts->setValue(N("Elf"), elf_path); cch_program.store(sts, N("ProgramCache_")); cch_data.store(sts, N("DataCache_")); cch_level2.store(sts, N("Level2Cache_")); // Branch predictor sts->setValue(N("BranchPredictor_Enabled"), get_bp_enabled()); sts->setValue(N("BranchPredictor_Type"), (uint8_t)get_bp_type()); sts->setValue(N("BranchPredictor_InitState"), (uint8_t)get_bp_init_state()); sts->setValue(N("BranchPredictor_BitsBTB"), get_bp_btb_bits()); sts->setValue(N("BranchPredictor_BitsBHR"), get_bp_bhr_bits()); sts->setValue(N("BranchPredictor_BitsBHTAddr"), get_bp_bht_addr_bits()); // Virtual memory sts->setValue(N("VMEnabled"), get_vm_enabled()); tlbc_program().store(sts, N("ProgramTLB_")); tlbc_data().store(sts, N("DataTLB_")); } #undef N void MachineConfig::preset(enum ConfigPresets p) { // Note: we set just a minimal subset to get preset (preserving as much of // hidden configuration as possible) switch (p) { case CP_SINGLE: case CP_SINGLE_CACHE: set_pipelined(false); set_delay_slot(true); break; case CP_PIPE_NO_HAZARD: set_pipelined(true); set_hazard_unit(MachineConfig::HU_NONE); break; case CP_PIPE: set_pipelined(true); set_hazard_unit(MachineConfig::HU_STALL_FORWARD); break; } // Some common configurations set_memory_execute_protection(DF_EXEC_PROTEC); set_memory_write_protection(DF_WRITE_PROTEC); set_memory_access_time_read(DF_MEM_ACC_READ); set_memory_access_time_write(DF_MEM_ACC_WRITE); set_memory_access_time_burst(DF_MEM_ACC_BURST); set_memory_access_time_level2(DF_MEM_ACC_LEVEL2); set_memory_access_enable_burst(DF_MEM_ACC_BURST_ENABLE); // Branch predictor set_bp_enabled(DFC_BP_ENABLED); set_bp_type(DFC_BP_TYPE); set_bp_init_state(DFC_BP_INIT_STATE); set_bp_btb_bits(DFC_BP_BTB_BITS); set_bp_bhr_bits(DFC_BP_BHR_BITS); set_bp_bht_addr_bits(DFC_BP_BHT_ADDR_BITS); access_cache_program()->preset(p); access_cache_data()->preset(p); access_cache_level2()->preset(p); set_vm_enabled(DFC_VM_ENABLED); access_tlb_program()->preset(p); access_tlb_data()->preset(p); set_simulated_xlen(Xlen::_32); set_isa_word(config_isa_word_default); switch (p) { case CP_SINGLE: case CP_SINGLE_CACHE: case CP_PIPE_NO_HAZARD: case CP_PIPE: access_cache_level2()->set_enabled(false); break; } } void MachineConfig::set_pipelined(bool v) { pipeline = v; } void MachineConfig::set_delay_slot(bool v) { delayslot = v; } void MachineConfig::set_hazard_unit(enum MachineConfig::HazardUnit hu) { hunit = hu; } bool MachineConfig::set_hazard_unit(const QString &hukind) { static QMap hukind_map = { { "none", HU_NONE }, { "stall", HU_STALL }, { "forward", HU_STALL_FORWARD }, { "stall-forward", HU_STALL_FORWARD }, }; if (!hukind_map.contains(hukind)) { return false; } set_hazard_unit(hukind_map.value(hukind)); return true; } void MachineConfig::set_memory_execute_protection(bool v) { exec_protect = v; } void MachineConfig::set_memory_write_protection(bool v) { write_protect = v; } void MachineConfig::set_memory_access_time_read(unsigned v) { mem_acc_read = v; } void MachineConfig::set_memory_access_time_write(unsigned v) { mem_acc_write = v; } void MachineConfig::set_memory_access_time_burst(unsigned v) { mem_acc_burst = v; } void MachineConfig::set_memory_access_time_level2(unsigned v) { mem_acc_level2 = v; } void MachineConfig::set_memory_access_enable_burst(bool v) { mem_acc_enable_burst = v; } void MachineConfig::set_osemu_enable(bool v) { osem_enable = v; } void MachineConfig::set_osemu_known_syscall_stop(bool v) { osem_known_syscall_stop = v; } void MachineConfig::set_osemu_unknown_syscall_stop(bool v) { osem_unknown_syscall_stop = v; } void MachineConfig::set_osemu_interrupt_stop(bool v) { osem_interrupt_stop = v; } void MachineConfig::set_osemu_exception_stop(bool v) { osem_exception_stop = v; } void MachineConfig::set_osemu_fs_root(QString v) { osem_fs_root = std::move(v); } void MachineConfig::set_reset_at_compile(bool v) { res_at_compile = v; } void MachineConfig::set_elf(QString path) { elf_path = std::move(path); } void MachineConfig::set_cache_program(const CacheConfig &c) { cch_program = c; } void MachineConfig::set_cache_data(const CacheConfig &c) { cch_data = c; } void MachineConfig::set_cache_level2(const CacheConfig &c) { cch_level2 = c; } void MachineConfig::set_simulated_endian(Endian endian) { MachineConfig::simulated_endian = endian; } void MachineConfig::set_simulated_xlen(Xlen xlen) { simulated_xlen = xlen; } void MachineConfig::set_isa_word(ConfigIsaWord bits) { isa_word = bits | config_isa_word_fixed; } void MachineConfig::modify_isa_word(ConfigIsaWord mask, ConfigIsaWord val) { mask &= ~config_isa_word_fixed; isa_word.modify(mask, val); } bool MachineConfig::pipelined() const { return pipeline; } bool MachineConfig::delay_slot() const { // Delay slot is always on when pipeline is enabled return pipeline || delayslot; } enum MachineConfig::HazardUnit MachineConfig::hazard_unit() const { // Hazard unit is always off when there is no pipeline return pipeline ? hunit : machine::MachineConfig::HU_NONE; } bool MachineConfig::memory_execute_protection() const { return exec_protect; } bool MachineConfig::memory_write_protection() const { return write_protect; } unsigned MachineConfig::memory_access_time_read() const { return mem_acc_read > 1 ? mem_acc_read : 1; } unsigned MachineConfig::memory_access_time_write() const { return mem_acc_write > 1 ? mem_acc_write : 1; } unsigned MachineConfig::memory_access_time_burst() const { return mem_acc_burst; } unsigned MachineConfig::memory_access_time_level2() const { return mem_acc_level2; } bool MachineConfig::memory_access_enable_burst() const { return mem_acc_enable_burst; } bool MachineConfig::osemu_enable() const { return osem_enable; } bool MachineConfig::osemu_known_syscall_stop() const { return osem_known_syscall_stop; } bool MachineConfig::osemu_unknown_syscall_stop() const { return osem_unknown_syscall_stop; } bool MachineConfig::osemu_interrupt_stop() const { return osem_interrupt_stop; } bool MachineConfig::osemu_exception_stop() const { return osem_exception_stop; } QString MachineConfig::osemu_fs_root() const { return osem_fs_root; } bool MachineConfig::reset_at_compile() const { return res_at_compile; } QString MachineConfig::elf() const { return elf_path; } const CacheConfig &MachineConfig::cache_program() const { return cch_program; } const CacheConfig &MachineConfig::cache_data() const { return cch_data; } const CacheConfig &MachineConfig::cache_level2() const { return cch_level2; } CacheConfig *MachineConfig::access_cache_program() { return &cch_program; } CacheConfig *MachineConfig::access_cache_data() { return &cch_data; } CacheConfig *MachineConfig::access_cache_level2() { return &cch_level2; } TLBConfig *MachineConfig::access_tlb_program() { return &tlb_program; } TLBConfig *MachineConfig::access_tlb_data() { return &tlb_data; } const TLBConfig &MachineConfig::tlbc_program() const { return tlb_program; } const TLBConfig &MachineConfig::tlbc_data() const { return tlb_data; } Endian MachineConfig::get_simulated_endian() const { return simulated_endian; } Xlen MachineConfig::get_simulated_xlen() const { return simulated_xlen; } ConfigIsaWord MachineConfig::get_isa_word() const { return isa_word; } void MachineConfig::set_bp_enabled(bool e) { bp_enabled = e; } void MachineConfig::set_bp_type(PredictorType t) { bp_type = t; } void MachineConfig::set_bp_init_state(PredictorState i) { bp_init_state = i; } void MachineConfig::set_bp_btb_bits(uint8_t b) { bp_btb_bits = b > BP_MAX_BTB_BITS ? BP_MAX_BTB_BITS : b; } void MachineConfig::set_bp_bhr_bits(uint8_t b) { bp_bhr_bits = b > BP_MAX_BHR_BITS ? BP_MAX_BHR_BITS : b; bp_bht_addr_bits = bp_bht_addr_bits > BP_MAX_BHT_ADDR_BITS ? BP_MAX_BHT_ADDR_BITS : bp_bht_addr_bits; bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; bp_bht_bits = bp_bht_bits > BP_MAX_BHT_BITS ? BP_MAX_BHT_BITS : bp_bht_bits; } void MachineConfig::set_bp_bht_addr_bits(uint8_t b) { bp_bht_addr_bits = b > BP_MAX_BHT_ADDR_BITS ? BP_MAX_BHT_ADDR_BITS : b; bp_bhr_bits = bp_bhr_bits > BP_MAX_BHR_BITS ? BP_MAX_BHR_BITS : bp_bhr_bits; bp_bht_bits = bp_bhr_bits + bp_bht_addr_bits; bp_bht_bits = bp_bht_bits > BP_MAX_BHT_BITS ? BP_MAX_BHT_BITS : bp_bht_bits; } bool MachineConfig::get_bp_enabled() const { return bp_enabled; } PredictorType MachineConfig::get_bp_type() const { return bp_type; } PredictorState MachineConfig::get_bp_init_state() const { return bp_init_state; } uint8_t MachineConfig::get_bp_btb_bits() const { return bp_btb_bits; } uint8_t MachineConfig::get_bp_bhr_bits() const { return bp_bhr_bits; } uint8_t MachineConfig::get_bp_bht_addr_bits() const { return bp_bht_addr_bits; } uint8_t MachineConfig::get_bp_bht_bits() const { return bp_bht_bits; } void MachineConfig::set_vm_enabled(bool v) { vm_enabled = v; } bool MachineConfig::get_vm_enabled() const { return vm_enabled; } bool MachineConfig::operator==(const MachineConfig &c) const { #define CMP(GETTER) (GETTER)() == (c.GETTER)() return CMP(pipelined) && CMP(delay_slot) && CMP(hazard_unit) && CMP(get_simulated_xlen) && CMP(get_isa_word) && CMP(get_bp_enabled) && CMP(get_bp_type) && CMP(get_bp_init_state) && CMP(get_bp_btb_bits) && CMP(get_bp_bhr_bits) && CMP(get_bp_bht_addr_bits) && CMP(memory_execute_protection) && CMP(memory_write_protection) && CMP(memory_access_time_read) && CMP(memory_access_time_write) && CMP(memory_access_time_burst) && CMP(memory_access_time_level2) && CMP(memory_access_enable_burst) && CMP(elf) && CMP(cache_program) && CMP(cache_data) && CMP(cache_level2) && CMP(get_vm_enabled) && CMP(tlbc_data) && CMP(tlbc_program); #undef CMP } bool MachineConfig::operator!=(const MachineConfig &c) const { return !operator==(c); } ================================================ FILE: src/machine/machineconfig.h ================================================ #ifndef MACHINECONFIG_H #define MACHINECONFIG_H #include "common/endian.h" #include "config_isa.h" #include "predictor_types.h" #include #include namespace machine { /** * There are two primary base integer variants, RV32I and RV64I, described in Chapters 2 and 5, * which provide 32-bit or 64-bit address spaces respectively. We use the term XLEN to refer to * the width of an integer register in bits (either 32 or 64). [RISC-V Unprivileged ISA, page 4] * * `RegisterValue` type is used to abstract the size of stored value. It provides methods to get * correct size. The `Core` can extract them with a special method. */ enum class Xlen { _32 = 32, _64 = 64 }; enum ConfigPresets { CP_SINGLE, // No pipeline cpu without cache CP_SINGLE_CACHE, // No pipeline cpu with cache CP_PIPE_NO_HAZARD, // Pipelined cpu without hazard unit and without cache CP_PIPE // Full pipelined cpu }; constexpr ConfigIsaWord config_isa_word_default = ConfigIsaWord::byChar('E') | ConfigIsaWord::byChar('I') | ConfigIsaWord::byChar('A') | ConfigIsaWord::byChar('M'); constexpr ConfigIsaWord config_isa_word_fixed = ConfigIsaWord::byChar('E') | ConfigIsaWord::byChar('I'); class CacheConfig { public: CacheConfig(); explicit CacheConfig(const CacheConfig *cc); explicit CacheConfig(const QSettings *, const QString &prefix = ""); void store(QSettings *, const QString &prefix = "") const; void preset(enum ConfigPresets); enum ReplacementPolicy { RP_RAND, // Random RP_LRU, // Least recently used RP_LFU, // Least frequently used RP_PLRU, // Pseudo Least recently used RP_NMRU // Not most recently used }; enum WritePolicy { WP_THROUGH_NOALLOC, // Write through WP_THROUGH_ALLOC, // Write through WP_BACK // Write back }; // If cache should be used or not void set_enabled(bool); void set_set_count(unsigned); // Number of sets void set_block_size(unsigned); // Number of uint32_t in block void set_associativity(unsigned); // Degree of associativity (number of // ways) void set_replacement_policy(enum ReplacementPolicy); void set_write_policy(enum WritePolicy); bool enabled() const; unsigned set_count() const; unsigned block_size() const; unsigned associativity() const; enum ReplacementPolicy replacement_policy() const; enum WritePolicy write_policy() const; bool operator==(const CacheConfig &c) const; bool operator!=(const CacheConfig &c) const; private: bool en; unsigned n_sets, n_blocks, d_associativity; enum ReplacementPolicy replac_pol; enum WritePolicy write_pol; }; class TLBConfig { public: TLBConfig(); explicit TLBConfig(const TLBConfig *cc); explicit TLBConfig(const QSettings *, const QString &prefix = ""); void store(QSettings *, const QString &prefix = "") const; void preset(enum ConfigPresets); enum VmMode { VM_BARE, VM_SV32 }; enum ReplacementPolicy { RP_RAND, // Random RP_LRU, // Least recently used RP_LFU, // Least frequently used RP_PLRU // Pseudo Least recently used }; // Virtual Memory void set_vm_asid(uint32_t a); uint32_t get_vm_asid() const; void set_tlb_num_sets(unsigned); void set_tlb_associativity(unsigned); void set_tlb_replacement_policy(ReplacementPolicy); unsigned get_tlb_num_sets() const; unsigned get_tlb_associativity() const; ReplacementPolicy get_tlb_replacement_policy() const; bool operator==(const TLBConfig &c) const; bool operator!=(const TLBConfig &c) const; private: bool vm_enabled = false; uint32_t vm_asid = 0; unsigned n_sets = 1; unsigned d_associativity = 1; enum ReplacementPolicy replac_pol = RP_RAND; }; class MachineConfig { public: MachineConfig(); explicit MachineConfig(const MachineConfig *config); explicit MachineConfig(const QSettings *, const QString &prefix = ""); void store(QSettings *, const QString &prefix = ""); void preset(enum ConfigPresets); enum HazardUnit { HU_NONE, HU_STALL, HU_STALL_FORWARD }; // Configure if CPU is pipelined // In default disabled. void set_pipelined(bool); // Configure if cpu should simulate delay slot in non-pipelined core // In default enabled. When disabled it also automatically disables // pipelining. void set_delay_slot(bool); // Hazard unit void set_hazard_unit(enum HazardUnit); bool set_hazard_unit(const QString &hukind); // Protect data memory from execution. Only program sections can be // executed. void set_memory_execute_protection(bool); // Protect program memory from accidental writes. void set_memory_write_protection(bool); // Set memory access times. Passed value is in cycles. void set_memory_access_time_read(unsigned); void set_memory_access_time_write(unsigned); void set_memory_access_time_burst(unsigned); void set_memory_access_time_level2(unsigned); void set_memory_access_enable_burst(bool); // Operating system and exceptions setup void set_osemu_enable(bool); void set_osemu_known_syscall_stop(bool); void set_osemu_unknown_syscall_stop(bool); void set_osemu_interrupt_stop(bool); void set_osemu_exception_stop(bool); void set_osemu_fs_root(QString v); // reset machine befor internal compile/reload after external make void set_reset_at_compile(bool); // Set path to source elf file. This has to be set before core is // initialized. void set_elf(QString path); // Configure cache void set_cache_program(const CacheConfig &); void set_cache_data(const CacheConfig &); void set_cache_level2(const CacheConfig &); void set_simulated_endian(Endian endian); void set_simulated_xlen(Xlen xlen); void set_isa_word(ConfigIsaWord bits); void modify_isa_word(ConfigIsaWord mask, ConfigIsaWord val); bool pipelined() const; bool delay_slot() const; enum HazardUnit hazard_unit() const; bool memory_execute_protection() const; bool memory_write_protection() const; unsigned memory_access_time_read() const; unsigned memory_access_time_write() const; unsigned memory_access_time_burst() const; unsigned memory_access_time_level2() const; bool memory_access_enable_burst() const; bool osemu_enable() const; bool osemu_known_syscall_stop() const; bool osemu_unknown_syscall_stop() const; bool osemu_interrupt_stop() const; bool osemu_exception_stop() const; QString osemu_fs_root() const; bool reset_at_compile() const; QString elf() const; const CacheConfig &cache_program() const; const CacheConfig &cache_data() const; const CacheConfig &cache_level2() const; Endian get_simulated_endian() const; Xlen get_simulated_xlen() const; ConfigIsaWord get_isa_word() const; // Virtual memory void set_vm_enabled(bool v); bool get_vm_enabled() const; const TLBConfig &tlbc_program() const; const TLBConfig &tlbc_data() const; // Branch predictor - Setters void set_bp_enabled(bool e); void set_bp_type(PredictorType t); void set_bp_init_state(PredictorState i); void set_bp_btb_bits(uint8_t b); void set_bp_bhr_bits(uint8_t b); void set_bp_bht_addr_bits(uint8_t b); // Branch predictor - Getters bool get_bp_enabled() const; PredictorType get_bp_type() const; PredictorState get_bp_init_state() const; uint8_t get_bp_btb_bits() const; uint8_t get_bp_bhr_bits() const; uint8_t get_bp_bht_addr_bits() const; uint8_t get_bp_bht_bits() const; CacheConfig *access_cache_program(); CacheConfig *access_cache_data(); CacheConfig *access_cache_level2(); TLBConfig *access_tlb_program(); TLBConfig *access_tlb_data(); bool operator==(const MachineConfig &c) const; bool operator!=(const MachineConfig &c) const; private: bool pipeline, delayslot; enum HazardUnit hunit; bool exec_protect, write_protect; unsigned mem_acc_read, mem_acc_write, mem_acc_burst, mem_acc_level2; bool mem_acc_enable_burst; bool osem_enable, osem_known_syscall_stop, osem_unknown_syscall_stop; bool osem_interrupt_stop, osem_exception_stop; bool res_at_compile; QString osem_fs_root; QString elf_path; CacheConfig cch_program, cch_data, cch_level2; Endian simulated_endian; Xlen simulated_xlen; ConfigIsaWord isa_word; // Branch predictor bool bp_enabled; PredictorType bp_type; PredictorState bp_init_state; uint8_t bp_btb_bits; uint8_t bp_bhr_bits; uint8_t bp_bht_addr_bits; uint8_t bp_bht_bits; // = bp_bhr_bits + bp_bht_addr_bits // Virtual memory bool vm_enabled; TLBConfig tlb_program, tlb_data; }; } // namespace machine Q_DECLARE_METATYPE(machine::CacheConfig) #endif // MACHINECONFIG_H ================================================ FILE: src/machine/machinedefs.h ================================================ #ifndef MACHINEDEFS_H #define MACHINEDEFS_H #include "memory/address.h" #include #include namespace machine { enum AccessControl { AC_NONE, AC_I8, AC_U8, AC_I16, AC_U16, AC_I32, AC_U32, AC_I64, AC_U64, AC_LR32, AC_SC32, AC_AMOSWAP32, AC_AMOADD32, AC_AMOXOR32, AC_AMOAND32, AC_AMOOR32, AC_AMOMIN32, AC_AMOMAX32, AC_AMOMINU32, AC_AMOMAXU32, AC_LR64, AC_SC64, AC_AMOSWAP64, AC_AMOADD64, AC_AMOXOR64, AC_AMOAND64, AC_AMOOR64, AC_AMOMIN64, AC_AMOMAX64, AC_AMOMINU64, AC_AMOMAXU64, AC_CACHE_OP, }; constexpr AccessControl AC_FIRST_REGULAR = AC_I8; constexpr AccessControl AC_LAST_REGULAR = AC_U64; constexpr AccessControl AC_FIRST_SPECIAL = AC_LR32; constexpr AccessControl AC_LAST_SPECIAL = AC_CACHE_OP; constexpr AccessControl AC_FISRT_AMO_MODIFY32 = AC_AMOSWAP32; constexpr AccessControl AC_LAST_AMO_MODIFY32 = AC_AMOMAXU32; constexpr AccessControl AC_FISRT_AMO_MODIFY64 = AC_AMOSWAP64; constexpr AccessControl AC_LAST_AMO_MODIFY64 = AC_AMOMAXU64; constexpr bool is_regular_access(AccessControl type) { return AC_FIRST_REGULAR <= type and type <= AC_LAST_REGULAR; } constexpr bool is_special_access(AccessControl type) { return AC_FIRST_SPECIAL <= type and type <= AC_LAST_SPECIAL; } static_assert(is_special_access(AC_CACHE_OP), ""); static_assert(is_special_access((AccessControl)11), ""); enum ExceptionCause { EXCAUSE_NONE = 0, // Use zero as default value when no exception is // ECAUSE_INSN_MISALIGNED - not defined for now, overlaps with EXCAUSE_NON EXCAUSE_INSN_FAULT = 1, // Instruction access fault EXCAUSE_INSN_ILLEGAL = 2, // Illegal instruction EXCAUSE_BREAK = 3, // Breakpoint EXCAUSE_LOAD_MISALIGNED = 4, // Load address misaligned EXCAUSE_LOAD_FAULT = 5, // Load access fault EXCAUSE_STORE_MISALIGNED = 6, // Store/AMO address misaligned EXCAUSE_STORE_FAULT = 7, // Store/AMO access fault EXCAUSE_ECALL_U = 8, // Environment call from U-mode EXCAUSE_ECALL_S = 9, // Environment call from S-mode EXCAUSE_RESERVED_10 = 10, // Reserved EXCAUSE_ECALL_M = 11, // Environment call from M-mode EXCAUSE_INSN_PAGE_FAULT = 12, // Instruction page fault EXCAUSE_LOAD_PAGE_FAULT = 13, // Load page fault EXCAUSE_RESERVED_14 = 14, // Reserved EXCAUSE_STORE_PAGE_FAULT = 15, // Store/AMO page fault // Simulator specific exception cause codes, alliases EXCAUSE_HWBREAK = 16, EXCAUSE_ECALL_ANY = 17, // sythetic exception to mark ECALL instruction EXCAUSE_INT = 18, // External/asynchronous interrupt, bit 32 or 63 EXCAUSE_COUNT = 19, }; enum LocationStatus { LOCSTAT_NONE = 0, LOCSTAT_CACHED = 1 << 0, LOCSTAT_DIRTY = 1 << 1, LOCSTAT_READ_ONLY = 1 << 2, LOCSTAT_ILLEGAL = 1 << 3, }; const Address STAGEADDR_NONE = 0xffffffff_addr; } // namespace machine Q_DECLARE_METATYPE(machine::AccessControl) #endif // MACHINEDEFS_H ================================================ FILE: src/machine/memory/address.h ================================================ #ifndef ADDRESS_H #define ADDRESS_H #include "utils.h" #include #include using std::uint64_t; namespace machine { /** * Emulated physical memory address. * * It is guaranteed that all instances of this type belong to single address * space and therefore all can be compared fearlessly. *

* OPTIMIZATION NOTE: All methods are implemented in header file to support * inlining (as we want to use it as a primitive type) but too keep declaration * tidy out-of-line definitions are preferred. */ class Address { private: uint64_t data; //> Real wrapped address public: constexpr explicit Address(uint64_t); /** * Default constructor results in null pointer. * Why it exists? To make address default constructable for STL. */ constexpr Address(); constexpr Address(const Address &address) = default; //> Copy constructor constexpr Address &operator=(const Address &address) = default; //> Assign constructor /** * Extracts raw numeric value of the address. * Why it exists? Output, unsafe operations not supported by operators. */ [[nodiscard]] constexpr uint64_t get_raw() const; /** * Explicit cast operator to underlying type. * This is an alternative ti get_raw() method. * * NOTE: This operator is needed for correct functioning of * repeat_access_until_completed function defined in memory_utils.h. */ constexpr explicit operator uint64_t() const; /** * More expressive way to assign null. * Why it exists? Equivalent of nullptr in cpp. Address(0) was ugly and too * common. */ constexpr static Address null(); /** * Test for null address * * Why is exists? Testing for null in very often and without this method a * creation of a new address object set to null would be required. That is * considered too long and badly readable. For other comparison we expect a * comparison with existing value to be the common case. * Alternative: `address.get_raw() == 0` * Alternative: `address == Address::null()` */ [[nodiscard]] constexpr bool is_null() const; /** * Test whether address is aligned to the size of type T. * * Example: * ``` * is_aligned(address) <=> address % 4 == 0 * ``` */ template [[nodiscard]] constexpr bool is_aligned() const; /* Eq */ constexpr inline bool operator==(const Address &other) const; constexpr inline bool operator!=(const Address &other) const; /* Ord */ constexpr inline bool operator<(const Address &other) const; constexpr inline bool operator>(const Address &other) const; constexpr inline bool operator<=(const Address &other) const; constexpr inline bool operator>=(const Address &other) const; /* Offset arithmetic */ constexpr inline Address operator+(const uint64_t &offset) const; constexpr inline Address operator-(const uint64_t &offset) const; inline void operator+=(const uint64_t &offset); inline void operator-=(const uint64_t &offset); /* Bitwise arithmetic */ constexpr inline Address operator&(const uint64_t &mask) const; constexpr inline Address operator|(const uint64_t &mask) const; constexpr inline Address operator^(const uint64_t &mask) const; constexpr inline Address operator>>(const uint64_t &size) const; constexpr inline Address operator<<(const uint64_t &size) const; /* Distance arithmetic */ constexpr inline int64_t operator-(const Address &other) const; }; constexpr Address operator""_addr(unsigned long long literal) { return Address(literal); } constexpr Address::Address(uint64_t address) : data(address) {} constexpr Address::Address() : data(0) {} constexpr uint64_t Address::get_raw() const { return data; } constexpr Address::operator uint64_t() const { return this->get_raw(); } constexpr Address Address::null() { return Address(0x0); } constexpr bool Address::is_null() const { return this->get_raw() == 0; } template constexpr bool Address::is_aligned() const { return is_aligned_genericdata), T>(this->data); } /* * Equality operators */ constexpr bool Address::operator==(const Address &other) const { return this->get_raw() == other.get_raw(); } constexpr bool Address::operator!=(const Address &other) const { return this->get_raw() != other.get_raw(); } /* * Ordering operators */ constexpr bool Address::operator<(const Address &other) const { return this->get_raw() < other.get_raw(); } constexpr bool Address::operator>(const Address &other) const { return this->get_raw() > other.get_raw(); } constexpr bool Address::operator<=(const Address &other) const { return this->get_raw() <= other.get_raw(); } constexpr bool Address::operator>=(const Address &other) const { return this->get_raw() >= other.get_raw(); } /* * Offset arithmetic operators */ constexpr Address Address::operator+(const uint64_t &offset) const { return Address(this->get_raw() + offset); } constexpr Address Address::operator-(const uint64_t &offset) const { return Address(this->get_raw() - offset); } void Address::operator+=(const uint64_t &offset) { data += offset; } void Address::operator-=(const uint64_t &offset) { data -= offset; } /* * Bitwise operators */ constexpr Address Address::operator&(const uint64_t &mask) const { return Address(this->get_raw() & mask); } constexpr Address Address::operator|(const uint64_t &mask) const { return Address(this->get_raw() | mask); } constexpr Address Address::operator^(const uint64_t &mask) const { return Address(get_raw() ^ mask); } constexpr Address Address::operator>>(const uint64_t &size) const { return Address(this->get_raw() >> size); } constexpr Address Address::operator<<(const uint64_t &size) const { return Address(this->get_raw() << size); } /* * Distance arithmetic operators */ constexpr int64_t Address::operator-(const Address &other) const { return this->get_raw() - other.get_raw(); } } // namespace machine Q_DECLARE_METATYPE(machine::Address) #endif // ADDRESS_H ================================================ FILE: src/machine/memory/address_range.h ================================================ #ifndef ADDRESS_RANGE_H #define ADDRESS_RANGE_H #include "memory/address.h" #include "utils.h" #include #include namespace machine { /** * Physical memory range - for nou used for range reservation for AMO operations * * If the first and the last point to the same address then range represnets * exaclly one valid byte. If the last is smaller than the first then range * is empty. */ class AddressRange { public: Address first; //> The first valid location of the range Address last; //> The last valid location of the range /** * Default constructor results for empty range. */ constexpr AddressRange() : first(1), last(0) {}; constexpr AddressRange(const AddressRange &range) = default; //> Copy constructor constexpr AddressRange(const Address &afirst, const Address &alast) : first(afirst) , last(alast) {}; constexpr AddressRange(const Address &asingleAddr) : first(asingleAddr), last(asingleAddr) {}; constexpr AddressRange &operator=(const AddressRange &address) = default; //> Assign constructor /** * Is empty range */ constexpr bool is_empty() const { return first > last; }; /* Eq */ constexpr inline bool operator==(const AddressRange &other) const { return (first == other.first && last == other.last) || (is_empty() && other.is_empty()); }; constexpr inline bool operator!=(const AddressRange &other) const { return !((first == other.first && last == other.last) || (is_empty() && other.is_empty())); }; constexpr bool within(const AddressRange &other) const { return (first >= other.first) && (last <= other.last); }; constexpr bool contains(const AddressRange &other) const { return (first <= other.first) && (last >= other.last); }; constexpr bool overlaps(const AddressRange &other) const { return (first <= other.last) && (last >= other.first) && !is_empty() && !other.is_empty(); }; void reset() { first = Address(1); last = Address(0); } }; } // namespace machine Q_DECLARE_METATYPE(machine::AddressRange) #endif // ADDRESS_RANGE_H ================================================ FILE: src/machine/memory/address_with_mode.h ================================================ #ifndef ADDRESS_WITH_MODE_H #define ADDRESS_WITH_MODE_H #include #include #include #include "address.h" #include "csr/address.h" namespace machine { struct AccessMode { uint32_t token = 0; AccessMode() = default; explicit AccessMode(uint32_t t) : token(t) {} static AccessMode pack(uint16_t asid, CSR::PrivilegeLevel priv, bool uncached = false) { uint32_t t = (static_cast(priv) & 0x3u) | ((static_cast(asid) & 0xFFFFu) << 2) | (uncached ? (1u << 18) : 0u); return AccessMode(t); } uint16_t asid() const { return static_cast((token >> 2) & 0xFFFFu); } CSR::PrivilegeLevel priv() const { return static_cast(token & 0x3u); } bool uncached() const { return ((token >> 18) & 0x1u) != 0; } uint32_t raw() const { return token; } }; class AddressWithMode : public Address { private: AccessMode mode; public: constexpr AddressWithMode() : Address(), mode() {} constexpr explicit AddressWithMode(uint64_t addr, AccessMode m = {}) : Address(addr), mode(m) {} constexpr AddressWithMode(const Address &addr, AccessMode m = {}) : Address(addr), mode(m) {} constexpr AddressWithMode(const AddressWithMode &other) = default; AddressWithMode &operator=(const AddressWithMode &other) = default; constexpr AccessMode access_mode() const noexcept { return mode; } void set_access_mode(AccessMode m) noexcept { mode = m; } uint32_t access_mode_raw() const noexcept { return mode.raw(); } constexpr bool operator==(const AddressWithMode &other) const noexcept { return static_cast(*this) == static_cast(other) && mode.raw() == other.mode.raw(); } constexpr bool operator!=(const AddressWithMode &other) const noexcept { return !(*this == other); } constexpr std::pair unpack() const noexcept { return { Address(get_raw()), mode }; } }; } // namespace machine Q_DECLARE_METATYPE(machine::AddressWithMode) #endif // ADDRESS_WITH_MODE_H ================================================ FILE: src/machine/memory/backend/aclintmswi.cpp ================================================ #include "memory/backend/aclintmswi.h" #include "common/endian.h" #include using ae = machine::AccessEffects; // For enum values, type is obvious from // context. namespace machine::aclint { AclintMswi::AclintMswi(Endian simulated_machine_endian) : BackendMemory(simulated_machine_endian) , mswi_irq_level(3) { mswi_count = 1; for (bool &i : mswi_value) i = false; } AclintMswi::~AclintMswi() = default; bool AclintMswi::update_mswi_irq() { bool active; active = mswi_value[0]; if (active != mswi_irq_active) { mswi_irq_active = active; emit signal_interrupt(mswi_irq_level, active); } return active; } WriteResult AclintMswi::write(Offset destination, const void *source, size_t size, WriteOptions options) { UNUSED(options) return write_by_u32( destination, source, size, [&](Offset src) { return byteswap_if( read_reg32(src, options.type), internal_endian != simulated_machine_endian); }, [&](Offset src, uint32_t value) { return write_reg32( src, byteswap_if(value, internal_endian != simulated_machine_endian)); }); } ReadResult AclintMswi::read(void *destination, Offset source, size_t size, ReadOptions options) const { return read_by_u32(destination, source, size, [&](Offset src) { return byteswap_if( read_reg32(src, options.type), internal_endian != simulated_machine_endian); }); } uint32_t AclintMswi::read_reg32(Offset source, AccessEffects type) const { Q_UNUSED(type) Q_ASSERT((source & 3U) == 0); // uint32_t aligned uint32_t value = 0; if ((source >= ACLINT_MSWI_OFFSET) && (source < ACLINT_MSWI_OFFSET + 4 * mswi_count)) { value = mswi_value[source >> 2] ? 1 : 0; } emit read_notification(source, value); return value; } bool AclintMswi::write_reg32(Offset destination, uint32_t value) { Q_ASSERT((destination & 3U) == 0); // uint32_t aligned bool changed = false; if ((destination >= ACLINT_MSWI_OFFSET) && (destination < ACLINT_MSWI_OFFSET + 4 * mswi_count)) { bool value_bool = value & 1; changed = value_bool != mswi_value[destination >> 2]; mswi_value[destination >> 2] = value_bool; update_mswi_irq(); } else { printf("WARNING: ACLINT MSWI - read out of range (at 0x%zu).\n", destination); } emit write_notification(destination, value); return changed; } LocationStatus AclintMswi::location_status(Offset offset) const { if ((offset >= ACLINT_MSWI_OFFSET) && (offset < ACLINT_MSWI_OFFSET + 4 * mswi_count)) return LOCSTAT_NONE; return LOCSTAT_ILLEGAL; } } // namespace machine::aclint ================================================ FILE: src/machine/memory/backend/aclintmswi.h ================================================ #ifndef ACLINTMSWI_H #define ACLINTMSWI_H #include "common/endian.h" #include "memory/backend/backend_memory.h" #include #include namespace machine::aclint { constexpr Offset CLINT_MSWI_OFFSET = 0x0000u; constexpr Offset CLINT_MSWI_SIZE = 0x4000u; constexpr Offset ACLINT_MSWI_OFFSET = 0; constexpr Offset ACLINT_MSWI_COUNT_MAX = 1; // Timer interrupts // mip.MTIP and mie.MTIE are bit 7 // mip.STIP and mie.STIE are bit 5 // Software interrupts // mip.MSIP and mie.MSIE are bit 3 // mip.SSIP and mie.SSIE are bit 1 class AclintMswi : public BackendMemory { Q_OBJECT public: explicit AclintMswi(Endian simulated_machine_endian); ~AclintMswi() override; signals: void write_notification(Offset address, uint32_t value); void read_notification(Offset address, uint32_t value) const; void signal_interrupt(uint irq_level, bool active) const; public: WriteResult write(Offset destination, const void *source, size_t size, WriteOptions options) override; ReadResult read(void *destination, Offset source, size_t size, ReadOptions options) const override; [[nodiscard]] LocationStatus location_status(Offset offset) const override; private: /** endian of internal registers of the periphery use. */ static constexpr Endian internal_endian = NATIVE_ENDIAN; [[nodiscard]] uint32_t read_reg32(Offset source, AccessEffects type) const; bool write_reg32(Offset destination, uint32_t value); bool update_mswi_irq(); unsigned mswi_count; bool mswi_value[ACLINT_MSWI_COUNT_MAX] {}; const uint8_t mswi_irq_level; bool mswi_irq_active = false; }; } // namespace machine::aclint #endif // ACLINTMSWI_H ================================================ FILE: src/machine/memory/backend/aclintmtimer.cpp ================================================ #include "memory/backend/aclintmtimer.h" #include "common/endian.h" #include #include LOG_CATEGORY("machine.memory.aclintmtimer"); using ae = machine::AccessEffects; // For enum values, type is obvious from // context. namespace machine::aclint { AclintMtimer::AclintMtimer(Endian simulated_machine_endian) : BackendMemory(simulated_machine_endian) , mtimer_irq_level(7) { mtimecmp_count = 1; for (auto &value : mtimecmp_value) { value = 0; } clock.start(); qt_timer_id = -1; } AclintMtimer::~AclintMtimer() { if (qt_timer_id >= 0) killTimer(qt_timer_id); qt_timer_id = -1; } uint64_t AclintMtimer::mtime_fetch_current() const { mtime_last_current_fetch = clock.elapsed() * (uint64_t)10000; return mtime_last_current_fetch; } bool AclintMtimer::update_mtimer_irq() { bool active; active = mtimecmp_value[0] < mtime_last_current_fetch + mtime_user_offset; if (active != mtimer_irq_active) { mtimer_irq_active = active; emit signal_interrupt(mtimer_irq_level, active); } if (active) { if (qt_timer_id >= 0) killTimer(qt_timer_id); qt_timer_id = -1; } return active; } void AclintMtimer::timerEvent(QTimerEvent *event) { if (event->timerId() == qt_timer_id) { if (qt_timer_id >= 0) killTimer(qt_timer_id); qt_timer_id = -1; mtime_fetch_current(); if (!update_mtimer_irq()) { arm_mtimer_event(); } } else { BackendMemory::timerEvent(event); } } void AclintMtimer::arm_mtimer_event() { if (qt_timer_id >= 0) killTimer(qt_timer_id); qt_timer_id = -1; uint64_t ticks_to_wait = mtimecmp_value[0] - (mtime_last_current_fetch + mtime_user_offset); qt_timer_id = startTimer(ticks_to_wait / 10000); } WriteResult AclintMtimer::write(Offset destination, const void *source, size_t size, WriteOptions options) { UNUSED(options) return write_by_u64( destination, source, size, [&](Offset src) { return byteswap_if( read_reg64(src, options.type), internal_endian != simulated_machine_endian); }, [&](Offset src, uint64_t value) { return write_reg64( src, byteswap_if(value, internal_endian != simulated_machine_endian)); }); } ReadResult AclintMtimer::read(void *destination, Offset source, size_t size, ReadOptions options) const { return read_by_u64(destination, source, size, [&](Offset src) { return byteswap_if( read_reg64(src, options.type), internal_endian != simulated_machine_endian); }); } uint64_t AclintMtimer::read_reg64(Offset source, AccessEffects type) const { Q_UNUSED(type) Q_ASSERT((source & 7U) == 0); // uint64_t aligned uint64_t value = 0; if (source == ACLINT_MTIME_OFFSET) { if (type == AccessEffects::REGULAR) mtime_fetch_current(); value = mtime_last_current_fetch + mtime_user_offset; } else if ( (source >= ACLINT_MTIMECMP_OFFSET) && (source < ACLINT_MTIMECMP_OFFSET + 8 * mtimecmp_count)) { value = mtimecmp_value[source >> 3]; } emit read_notification(source, value); return value; } bool AclintMtimer::write_reg64(Offset destination, uint64_t value) { Q_ASSERT((destination & 7U) == 0); // uint64_t aligned bool changed = false; if (destination == ACLINT_MTIME_OFFSET) { mtime_fetch_current(); mtime_user_offset = value - mtime_last_current_fetch; if (!update_mtimer_irq()) arm_mtimer_event(); changed = true; } else if ( (destination >= ACLINT_MTIMECMP_OFFSET) && (destination < ACLINT_MTIMECMP_OFFSET + 8 * mtimecmp_count)) { changed = value != mtimecmp_value[destination >> 3]; mtimecmp_value[destination >> 3] = value; if (!update_mtimer_irq()) arm_mtimer_event(); } else { WARN("ACLINT MTIMER - read out of range (at 0x%zu).\n", destination); } emit write_notification(destination, value); return changed; } LocationStatus AclintMtimer::location_status(Offset offset) const { if ((offset >= ACLINT_MTIMECMP_OFFSET) && (offset < ACLINT_MTIMECMP_OFFSET + 8 * mtimecmp_count)) return LOCSTAT_NONE; if ((offset & ~7U) == ACLINT_MTIME_OFFSET) return LOCSTAT_NONE; // LOCSTAT_NONE / LOCSTAT_READ_ONLY return LOCSTAT_ILLEGAL; } } // namespace machine::aclint ================================================ FILE: src/machine/memory/backend/aclintmtimer.h ================================================ #ifndef ACLINTMTIMER_H #define ACLINTMTIMER_H #include "common/endian.h" #include "memory/backend/backend_memory.h" #include #include #include namespace machine { namespace aclint { constexpr Offset CLINT_MTIMER_OFFSET = 0x4000u; constexpr Offset CLINT_MTIMER_SIZE = 0x8000u; constexpr Offset ACLINT_MTIME_OFFSET = 0x7ff8u; constexpr Offset ACLINT_MTIME_SIZE = 0x8u; constexpr Offset ACLINT_MTIMECMP_OFFSET = 0x0000u; constexpr Offset ACLINT_MTIMECMP_SIZE = 0x7ff8u; constexpr unsigned ACLINT_MTIMECMP_COUNT_MAX = 1; // Timer interrupts // mip.MTIP and mie.MTIE are bit 7 // mip.STIP and mie.STIE are bit 5 // Software interrupts // mip.MSIP and mie.MSIE are bit 3 // mip.SSIP and mie.SSIE are bit 1 class AclintMtimer : public BackendMemory { Q_OBJECT public: explicit AclintMtimer(Endian simulated_machine_endian); ~AclintMtimer() override; signals: void write_notification(Offset address, uint32_t value); void read_notification(Offset address, uint32_t value) const; void signal_interrupt(uint irq_level, bool active) const; public: uint64_t mtime_fetch_current() const; WriteResult write(Offset destination, const void *source, size_t size, WriteOptions options) override; ReadResult read(void *destination, Offset source, size_t size, ReadOptions options) const override; LocationStatus location_status(Offset offset) const override; private: void timerEvent(QTimerEvent *event) override; /** endian of internal registers of the periphery use. */ static constexpr Endian internal_endian = NATIVE_ENDIAN; uint64_t read_reg64(Offset source, AccessEffects type) const; bool write_reg64(Offset destination, uint64_t value); bool update_mtimer_irq(); void arm_mtimer_event(); unsigned mtimecmp_count; uint64_t mtimecmp_value[ACLINT_MTIMECMP_COUNT_MAX] {}; QElapsedTimer clock; const uint8_t mtimer_irq_level; uint64_t mtime_user_offset = 0; mutable uint64_t mtime_last_current_fetch = 0; mutable bool mtimer_irq_active = false; int qt_timer_id = -1; }; }} // namespace machine::aclint #endif // ACLINTMTIMER_H ================================================ FILE: src/machine/memory/backend/aclintsswi.cpp ================================================ #include "memory/backend/aclintsswi.h" #include "common/endian.h" #include #include using ae = machine::AccessEffects; // For enum values, type is obvious from // context. namespace machine { namespace aclint { AclintSswi::AclintSswi(Endian simulated_machine_endian) : BackendMemory(simulated_machine_endian) , sswi_irq_level(1) { sswi_count = 1; } AclintSswi::~AclintSswi() = default; WriteResult AclintSswi::write(Offset destination, const void *source, size_t size, WriteOptions options) { UNUSED(options) return write_by_u32( destination, source, size, [&](Offset src) { return byteswap_if( read_reg32(src, options.type), internal_endian != simulated_machine_endian); }, [&](Offset src, uint32_t value) { return write_reg32( src, byteswap_if(value, internal_endian != simulated_machine_endian)); }); } ReadResult AclintSswi::read(void *destination, Offset source, size_t size, ReadOptions options) const { return read_by_u32(destination, source, size, [&](Offset src) { return byteswap_if( read_reg32(src, options.type), internal_endian != simulated_machine_endian); }); } uint32_t AclintSswi::read_reg32(Offset source, AccessEffects type) const { Q_UNUSED(type) Q_ASSERT((source & 3U) == 0); // uint32_t aligned uint32_t value = 0; if ((source >= ACLINT_SSWI_OFFSET) && (source < ACLINT_SSWI_OFFSET + 4 * sswi_count)) {} emit read_notification(source, value); return value; } bool AclintSswi::write_reg32(Offset destination, uint32_t value) { Q_ASSERT((destination & 3U) == 0); // uint32_t aligned bool changed = false; if ((destination >= ACLINT_SSWI_OFFSET) && (destination < ACLINT_SSWI_OFFSET + 4 * sswi_count)) { bool value_bool = value & 1; if (value_bool) emit signal_interrupt(sswi_irq_level, value_bool); } else { printf("WARNING: ACLINT SSWI - read out of range (at 0x%zu).\n", destination); } emit write_notification(destination, value); return changed; } LocationStatus AclintSswi::location_status(Offset offset) const { if ((offset >= ACLINT_SSWI_OFFSET) && (offset < ACLINT_SSWI_OFFSET + 4 * sswi_count)) return LOCSTAT_NONE; return LOCSTAT_ILLEGAL; } }} // namespace machine::aclint ================================================ FILE: src/machine/memory/backend/aclintsswi.h ================================================ #ifndef ACLINTSSWI_H #define ACLINTSSWI_H #include "common/endian.h" #include "memory/backend/backend_memory.h" #include #include namespace machine::aclint { constexpr Offset CLINT_SSWI_OFFSET = 0xc000u; constexpr Offset CLINT_SSWI_SIZE = 0x4000u; constexpr Offset ACLINT_SSWI_OFFSET = 0; constexpr Offset ACLINT_SSWI_COUNT_MAX = 1; // Timer interrupts // mip.MTIP and mie.MTIE are bit 7 // mip.STIP and mie.STIE are bit 5 // Software interrupts // mip.MSIP and mie.MSIE are bit 3 // mip.SSIP and mie.SSIE are bit 1 class AclintSswi : public BackendMemory { Q_OBJECT public: explicit AclintSswi(Endian simulated_machine_endian); ~AclintSswi() override; signals: void write_notification(Offset address, uint32_t value); void read_notification(Offset address, uint32_t value) const; void signal_interrupt(uint irq_level, bool active) const; public: WriteResult write(Offset destination, const void *source, size_t size, WriteOptions options) override; ReadResult read(void *destination, Offset source, size_t size, ReadOptions options) const override; [[nodiscard]] LocationStatus location_status(Offset offset) const override; private: /** endian of internal registers of the periphery use. */ static constexpr Endian internal_endian = NATIVE_ENDIAN; [[nodiscard]] uint32_t read_reg32(Offset source, AccessEffects type) const; bool write_reg32(Offset destination, uint32_t value); unsigned sswi_count; const uint8_t sswi_irq_level; }; } // namespace machine::aclint #endif // ACLINTSSWI_H ================================================ FILE: src/machine/memory/backend/backend_memory.h ================================================ #ifndef BACKEND_MEMORY_H #define BACKEND_MEMORY_H #include "common/endian.h" #include "machinedefs.h" #include "memory/memory_utils.h" #include // Shortcut for enum class values, type is obvious from context. using ae = machine::AccessEffects; namespace machine { /** * Relative index within an instance of backend memory. */ typedef size_t Offset; /** * Interface for physical memory or periphery. * . * Device implementing this interface is connected to the memory system via * memory data bus (`memory/memory_bus.h`). * * ## ENDIAN * Each device is responsible to return reads and write with the correct endian. * This is because there are different optimal ways to perform the swapping in * different kind of peripheries. For example, peripheries that have only word * (u23) accessible registers are simple to swap. * * All backend memory devices must set the `simulated_machine_endian` in * `BackendMemory` parent. They should also, by convention, have a private * (`const` or `static constexpr`) variable `internal_endian`. It can have * values `LITTLE | BIG | NATIVE_ENDIAN` (note: `NATIVE_ENDIAN` is a compile * time constant with endian of the host machine i.e. `LITTLE | BIG`). Byteswap * is needed, when internal and simulated endian are mismatched. * * ### Examples of internal endian values * - LED diode will have `NATIVE_ENDIAN` as the rgb value needs to be valid for * GUI. * - Memory mapped source will have runtime set endian, based on the file * endian. * - LCD has fixed `BIG` endian, as required by the hardware. */ class BackendMemory : public QObject { Q_OBJECT public: /** * @param simulated_machine_endian endian of the simulated CPU/memory * system */ explicit BackendMemory(Endian simulated_machine_endian); /** * Write byte sequence to memory. * * @param source pointer to array of bytes to be copied * @param destination relative index of destination to write to * @param size number of bytes to be written * @return true when memory before and after write differs */ virtual WriteResult write(Offset destination, const void *source, size_t size, WriteOptions options) = 0; /** * Read sequence of bytes from memory * * @param source relative index of data to be read * @param destination pointer to destination buffer * @param size number of bytes to be read * @param options additional option like debug mode, see type * definition */ virtual ReadResult read(void *destination, Offset source, size_t size, ReadOptions options) const = 0; /** * Determine status of given address. * * Applicable values: * - LOCSTAT_NONE normal RW area * - LOCSTAT_READ_ONLY read only hw register * - LOCSTAT_ILLEGAL address is not occupied, write will result in * NOP, read will return constant zero. */ [[nodiscard]] virtual enum LocationStatus location_status(Offset offset) const = 0; /** * Endian of the simulated CPU/memory system. * @see BackendMemory docs */ const Endian simulated_machine_endian; signals: /** * Notify upper layer about a change in managed physical memory of periphery * * @param mem_access this * @param start_addr affected area start * @param last_addr affected area end * @param type allowed side effects, see type declaration */ void external_backend_change_notify( const BackendMemory *mem_access, uint32_t start_addr, uint32_t last_addr, AccessEffects type) const; }; inline BackendMemory::BackendMemory(Endian simulated_machine_endian) : simulated_machine_endian(simulated_machine_endian) {} } // namespace machine #endif // BACKEND_MEMORY_H ================================================ FILE: src/machine/memory/backend/lcddisplay.cpp ================================================ #include "lcddisplay.h" #include "common/endian.h" #ifdef DEBUG_LCD #undef DEBUG_LCD #define DEBUG_LCD true #else #define DEBUG_LCD false #endif namespace machine { LcdDisplay::LcdDisplay(Endian simulated_machine_endian) : BackendMemory(simulated_machine_endian) , fb_width(480) , fb_height(320) , fb_bits_per_pixel(16) , fb_data(get_fb_size_bytes(), 0) {} LcdDisplay::~LcdDisplay() = default; WriteResult LcdDisplay::write(Offset destination, const void *source, size_t size, WriteOptions options) { UNUSED(options) return write_by_u16( destination, source, size, [&](Offset src) { return byteswap_if(read_raw_pixel(src), internal_endian != simulated_machine_endian); }, [&](Offset src, uint16_t value) { return write_raw_pixel( src, byteswap_if(value, internal_endian != simulated_machine_endian)); }); } ReadResult LcdDisplay::read(void *destination, Offset source, size_t size, ReadOptions options) const { UNUSED(options) return read_by_u16(destination, source, size, [&](Offset src) { return byteswap_if(read_raw_pixel(src), internal_endian != simulated_machine_endian); }); } uint16_t LcdDisplay::read_raw_pixel(Offset source) const { Q_ASSERT((source & 1U) == 0); // uint16_t aligned if (source + 1 >= get_fb_size_bytes()) { return 0; } uint16_t value; memcpy(&value, &fb_data[source], sizeof(value)); // TODO Switch to if constexpr as soon as we have cpp17. if (DEBUG_LCD) { printf( "LcdDisplay::read_reg address 0x%08lx data 0x%08lx\n", (unsigned long)source, (unsigned long)value); } emit read_notification(source, value); return value; } bool LcdDisplay::write_raw_pixel(Offset destination, uint16_t value) { Q_ASSERT((destination & 1U) == 0); // uint16_t aligned if (destination + 1 >= get_fb_size_bytes()) { printf("WARNING: LCD display - read out of range.\n"); return false; } // TODO Switch to if constexpr as soon as we have cpp17. if (DEBUG_LCD) { printf( "LcdDisplay::write_reg address 0x%08lx data 0x%08lx\n", (unsigned long)destination, (unsigned long)value); } if (read_raw_pixel(destination) == value) { return false; } memcpy(&fb_data[destination], &value, sizeof(value)); size_t x, y; std::tie(x, y) = get_pixel_from_address(destination); const uint32_t last_addr = destination + 1; uint32_t pixel_addr; uint16_t pixel_data; uint r, g, b; while ((pixel_addr = get_address_from_pixel(x, y)) <= last_addr) { memcpy(&pixel_data, &fb_data[pixel_addr], sizeof(pixel_data)); r = ((pixel_data >> 11u) & 0x1fu) << 3u; g = ((pixel_data >> 5u) & 0x3fu) << 2u; b = ((pixel_data >> 0u) & 0x1fu) << 3u; emit pixel_update(x, y, r, g, b); if (++x >= fb_width) { x = 0; y++; } } emit write_notification(destination, value); return true; } size_t LcdDisplay::get_address_from_pixel(size_t x, size_t y) const { size_t address = y * get_fb_line_size(); if (fb_bits_per_pixel > 12) { address += x * divide_and_ceil(fb_bits_per_pixel, 8u); } else { address += x * fb_bits_per_pixel / 8; } return address; } std::tuple LcdDisplay::get_pixel_from_address(size_t address) const { size_t y = address / get_fb_line_size(); size_t x = (fb_bits_per_pixel > 12) ? (address - y * get_fb_line_size()) / ((fb_bits_per_pixel + 7) >> 3u) : (address - y * get_fb_line_size()) * 8 / fb_bits_per_pixel; return std::make_tuple(x, y); } size_t LcdDisplay::get_fb_line_size() const { return (fb_bits_per_pixel > 12) ? ((fb_bits_per_pixel + 7) >> 3u) * fb_width : (fb_bits_per_pixel * fb_width + 7) >> 3u; } size_t LcdDisplay::get_fb_size_bytes() const { return get_fb_line_size() * fb_height; } LocationStatus LcdDisplay::location_status(Offset offset) const { if ((offset | ~3u) >= get_fb_size_bytes()) { return LOCSTAT_ILLEGAL; } return LOCSTAT_NONE; } } // namespace machine ================================================ FILE: src/machine/memory/backend/lcddisplay.h ================================================ #ifndef LCDDISPLAY_H #define LCDDISPLAY_H #include "common/endian.h" #include "machinedefs.h" #include "memory/backend/backend_memory.h" #include "simulator_exception.h" #include #include #include namespace machine { class LcdDisplay final : public BackendMemory { Q_OBJECT public: explicit LcdDisplay(Endian simulated_machine_endian); ~LcdDisplay() override; signals: void write_notification(Offset offset, uint32_t value) const; void read_notification(Offset offset, uint32_t value) const; void pixel_update(size_t x, size_t y, uint r, uint g, uint b); public: WriteResult write(Offset destination, const void *source, size_t size, WriteOptions options) override; ReadResult read(void *destination, Offset source, size_t size, ReadOptions options) const override; [[nodiscard]] LocationStatus location_status(Offset offset) const override; /** * @return framebuffer width in pixels */ [[nodiscard]] inline constexpr size_t get_width() const { return fb_width; } /** * @return framebuffer height in pixels */ [[nodiscard]] inline constexpr size_t get_height() const { return fb_height; } private: /** Endian internal registers of the periphery (framebuffer) use. */ static constexpr Endian internal_endian = NATIVE_ENDIAN; /** Read HW register - allows only 32bit aligned access. */ [[nodiscard]] uint16_t read_raw_pixel(Offset source) const; /** Write HW register - allows only 32bit aligned access */ bool write_raw_pixel(Offset destination, uint16_t value); [[nodiscard]] size_t get_fb_line_size() const; [[nodiscard]] size_t get_fb_size_bytes() const; [[nodiscard]] size_t get_address_from_pixel(size_t x, size_t y) const; [[nodiscard]] std::tuple get_pixel_from_address(size_t address) const; const size_t fb_width; //> Width in pixels const size_t fb_height; //> Height in pixels const size_t fb_bits_per_pixel; std::vector fb_data; }; } // namespace machine #endif // LCDDISPLAY_H ================================================ FILE: src/machine/memory/backend/memory.cpp ================================================ #include "memory/backend/memory.h" #include "common/endian.h" #include "simulator_exception.h" #include namespace machine { MemorySection::MemorySection(size_t length_bytes, Endian simulated_machine_endian) : BackendMemory(simulated_machine_endian) , dt(length_bytes, 0) {} MemorySection::MemorySection(const MemorySection &other) : BackendMemory(other.simulated_machine_endian) , dt(other.dt) {} WriteResult MemorySection::write(Offset dst_offset, const void *source, size_t size, WriteOptions options) { UNUSED(options) auto destination = static_cast(dst_offset); if (destination >= length()) { throw SIMULATOR_EXCEPTION( OutOfMemoryAccess, "Trying to write outside of the memory section", QString("Accessing using offset: ") + QString::number(destination)); } // Size the can be read from this section const size_t available_size = std::min(destination + size, length()) - destination; // TODO, make swap conditional for big endian machines bool changed = memcmp(source, &dt[destination], available_size) != 0; if (changed) { memcpy(&dt[destination], source, available_size); } return { .n_bytes = available_size, .changed = changed }; } ReadResult MemorySection::read(void *destination, Offset src_offset, size_t size, ReadOptions options) const { UNUSED(options) auto source = static_cast(src_offset); size = std::min(source + size, length()) - source; if (source >= length()) { throw SIMULATOR_EXCEPTION( OutOfMemoryAccess, "Trying to read outside of the memory section", QString("Accessing using offset: ") + QString::number(source)); } memcpy(destination, &dt[source], size); return { .n_bytes = size }; } LocationStatus MemorySection::location_status(Offset offset) const { UNUSED(offset) return LOCSTAT_NONE; } size_t MemorySection::length() const { return this->dt.size(); } const byte *MemorySection::data() const { return this->dt.data(); } bool MemorySection::operator==(const MemorySection &other) const { return this->dt == other.dt; } bool MemorySection::operator!=(const MemorySection &ms) const { return !this->operator==(ms); } // Settings sanity checks static_assert(MEMORY_SECTION_SIZE != 0, "Nonzero memory section size is required."); static_assert(MEMORY_TREE_ROW_SIZE != 0, "Nonzero memory tree row size is required."); static_assert( ((32 - MEMORY_SECTION_BITS) % MEMORY_TREE_BITS) == 0, "Number of bits in tree row has to be exact division of available number " "of bits."); /** * Generate mask to get memory section index from address. * * Memory section of section_size 2^`section_size` separably addressable units * each of section_size 2^`unit_size` bytes. * * Example: * `MemorySection` of 256x1B index is received as * ```address & generate_mask(8, 0)``` */ constexpr uint64_t generate_mask(size_t section_size, size_t unit_size) { return ((1U << section_size) - 1) << unit_size; } /** * Get index in row for given offset and row number i */ constexpr size_t tree_row_bit_offset(size_t i) { return 32 - MEMORY_TREE_BITS - i * MEMORY_TREE_BITS; } /* * Select branch index from memory tree. */ constexpr size_t get_tree_row(size_t offset, size_t i) { return (offset & generate_mask(MEMORY_TREE_BITS, tree_row_bit_offset(i))) >> tree_row_bit_offset(i); } Memory::Memory() : BackendMemory(BIG) { // This is dummy constructor for qt internal uses only. this->mt_root = nullptr; } Memory::Memory(Endian simulated_machine_endian) : BackendMemory(simulated_machine_endian) { this->mt_root = allocate_section_tree(); } Memory::Memory(const Memory &other) : BackendMemory(other.simulated_machine_endian) { this->mt_root = copy_section_tree(other.get_memory_tree_root(), 0); } Memory::~Memory() { free_section_tree(this->mt_root, 0); delete[] this->mt_root; } void Memory::reset() { free_section_tree(this->mt_root, 0); delete[] this->mt_root; this->mt_root = allocate_section_tree(); } void Memory::reset(const Memory &m) { free_section_tree(this->mt_root, 0); this->mt_root = copy_section_tree(m.get_memory_tree_root(), 0); } MemorySection *Memory::get_section(size_t offset, bool create) const { union MemoryTree *w = this->mt_root; size_t row_num; // Walk memory tree branch from root to leaf and create new nodes when // needed and requested (`create` flag). for (size_t i = 0; i < (MEMORY_TREE_DEPTH - 1); i++) { row_num = get_tree_row(offset, i); if (w[row_num].subtree == nullptr) { // We don't have this tree so allocate it. if (!create) { // If we shouldn't be creating it than just return// null. return nullptr; } w[row_num].subtree = allocate_section_tree(); } w = w[row_num].subtree; } row_num = get_tree_row(offset, MEMORY_TREE_DEPTH - 1); if (w[row_num].sec == nullptr) { if (!create) { return nullptr; } w[row_num].sec = new MemorySection(MEMORY_SECTION_SIZE, simulated_machine_endian); } return w[row_num].sec; } size_t get_section_offset_mask(size_t addr) { return addr & generate_mask(MEMORY_SECTION_BITS, 0); } WriteResult Memory::write(Offset destination, const void *source, size_t size, WriteOptions options) { return repeat_access_until_completed( destination, source, size, options, [this](Offset _destination, const void *_source, size_t _size, WriteOptions) { MemorySection *section = this->get_section(_destination, true); return section->write(get_section_offset_mask(_destination), _source, _size, {}); }); } ReadResult Memory::read(void *destination, Offset source, size_t size, ReadOptions options) const { return repeat_access_until_completed( destination, source, size, options, [this]( void *_destination, Offset _source, size_t _size, ReadOptions _options) -> ReadResult { MemorySection *section = this->get_section(_source, false); if (section == nullptr) { memset(_destination, 0, _size); // TODO Warning read of uninitialized memory return { .n_bytes = _size }; } else { return section->read( _destination, get_section_offset_mask(_source), _size, _options); } }); } uint32_t Memory::get_change_counter() const { return change_counter; } bool Memory::operator==(const Memory &m) const { return compare_section_tree(this->mt_root, m.get_memory_tree_root(), 0); } bool Memory::operator!=(const Memory &m) const { return !this->operator==(m); } const union machine::MemoryTree *Memory::get_memory_tree_root() const { return this->mt_root; } union machine::MemoryTree *Memory::allocate_section_tree() { auto *mt = new union MemoryTree[MEMORY_TREE_ROW_SIZE]; memset(mt, 0, sizeof *mt * MEMORY_TREE_ROW_SIZE); return mt; } void Memory::free_section_tree(union MemoryTree *mt, size_t depth) { if (depth < (MEMORY_TREE_DEPTH - 1)) { // Following level is memory tree for (size_t i = 0; i < MEMORY_TREE_ROW_SIZE; i++) { if (mt[i].subtree != nullptr) { free_section_tree(mt[i].subtree, depth + 1); delete[] mt[i].subtree; } } } else { // Following level is memory section for (size_t i = 0; i < MEMORY_TREE_ROW_SIZE; i++) { delete mt[i].sec; } } } bool Memory::compare_section_tree( const union MemoryTree *mt1, const union MemoryTree *mt2, size_t depth) { if (depth < (MEMORY_TREE_DEPTH - 1)) { // Following level is memory tree for (size_t i = 0; i < MEMORY_TREE_ROW_SIZE; i++) { if (((mt1[i].subtree == nullptr || mt2[i].subtree == nullptr) && mt1[i].subtree != mt2[i].subtree) || (mt1[i].subtree != nullptr && mt2[i].subtree != nullptr && !compare_section_tree(mt1[i].subtree, mt2[i].subtree, depth + 1))) { return false; } } } else { // Following level is memory section for (size_t i = 0; i < MEMORY_TREE_ROW_SIZE; i++) { if (((mt1[i].sec == nullptr || mt2[i].sec == nullptr) && mt1[i].sec != mt2[i].sec) || (mt1[i].sec != nullptr && mt2[i].sec != nullptr && *mt1[i].sec != *mt2[i].sec)) { return false; } } } return true; } union machine::MemoryTree *Memory::copy_section_tree(const union MemoryTree *mt, size_t depth) { union MemoryTree *nmt = allocate_section_tree(); if (depth < (MEMORY_TREE_DEPTH - 1)) { // Following level is memory tree for (size_t i = 0; i < MEMORY_TREE_ROW_SIZE; i++) { if (mt[i].subtree != nullptr) { nmt[i].subtree = copy_section_tree(mt[i].subtree, depth + 1); } } } else { // Following level is memory section for (size_t i = 0; i < MEMORY_TREE_ROW_SIZE; i++) { if (mt[i].sec != nullptr) { nmt[i].sec = new MemorySection(*mt[i].sec); } } } return nmt; } LocationStatus Memory::location_status(Offset offset) const { UNUSED(offset) // Lazy allocation of memory is only internal implementation detail. return LOCSTAT_NONE; } } // namespace machine ================================================ FILE: src/machine/memory/backend/memory.h ================================================ #ifndef MACHINE_MEMORY_H #define MACHINE_MEMORY_H #include "common/endian.h" #include "machinedefs.h" #include "memory/address.h" #include "memory/backend/backend_memory.h" #include "memory/memory_utils.h" #include "simulator_exception.h" #include "utils.h" #include #include namespace machine { /** * NOTE: Internal endian of memory must be the same as endian of the whole * simulated machine. Therefore it does not have internal_endian field. */ class MemorySection final : public BackendMemory { public: explicit MemorySection(size_t length_bytes, Endian simulated_machine_endian); MemorySection(const MemorySection &other); ~MemorySection() override = default; WriteResult write(Offset destination, const void *source, size_t total_size, WriteOptions options) override; ReadResult read(void *destination, Offset source, size_t size, ReadOptions options) const override; [[nodiscard]] LocationStatus location_status(Offset offset) const override; [[nodiscard]] size_t length() const; [[nodiscard]] const byte *data() const; bool operator==(const MemorySection &) const; bool operator!=(const MemorySection &) const; private: std::vector dt; }; ////////////////////////////////////////////////////////////////////////////// /// Some optimisation options // How big memory sections will be in bits (2^8=256 bytes) constexpr size_t MEMORY_SECTION_BITS = 8; // How big one row of lookup tree will be in bits (2^4=16) constexpr size_t MEMORY_TREE_BITS = 4; ////////////////////////////////////////////////////////////////////////////// // Size of one section constexpr size_t MEMORY_SECTION_SIZE = (1u << MEMORY_SECTION_BITS); // Size of one memory row constexpr size_t MEMORY_TREE_ROW_SIZE = (1u << MEMORY_TREE_BITS); // Depth of tree constexpr size_t MEMORY_TREE_DEPTH = ((32 - MEMORY_SECTION_BITS) / MEMORY_TREE_BITS); union MemoryTree { union MemoryTree *subtree; MemorySection *sec; }; /** * NOTE: Internal endian of memory must be the same as endian of the whole * simulated machine. Therefore it does not have internal_endian field. */ class Memory final : public BackendMemory { Q_OBJECT public: // This is dummy constructor for qt internal uses only. Memory(); explicit Memory(Endian simulated_machine_endian); Memory(const Memory &); ~Memory() override; void reset(); // Reset whole content of memory (removes old tree and creates // new one) void reset(const Memory &); // returns section containing given address [[nodiscard]] MemorySection *get_section(size_t offset, bool create) const; WriteResult write(Offset destination, const void *source, size_t size, WriteOptions options) override; ReadResult read(void *destination, Offset source, size_t size, ReadOptions options) const override; [[nodiscard]] LocationStatus location_status(Offset offset) const override; bool operator==(const Memory &) const; bool operator!=(const Memory &) const; [[nodiscard]] const union MemoryTree *get_memory_tree_root() const; private: union MemoryTree *mt_root; uint32_t change_counter = 0; static union MemoryTree *allocate_section_tree(); static void free_section_tree(union MemoryTree *, size_t depth); static bool compare_section_tree(const union MemoryTree *, const union MemoryTree *, size_t depth); static union MemoryTree *copy_section_tree(const union MemoryTree *, size_t depth); [[nodiscard]] uint32_t get_change_counter() const; }; } // namespace machine Q_DECLARE_METATYPE(machine::Memory); #endif // MEMORY_H ================================================ FILE: src/machine/memory/backend/memory.test.cpp ================================================ #include "memory.test.h" #include "common/endian.h" #include "machine/machinedefs.h" #include "machine/memory/backend/memory.h" #include "machine/memory/memory_bus.h" #include "machine/memory/memory_utils.h" #include "tests/utils/integer_decomposition.h" #include using namespace machine; // Default memory testing data. Some tests may use other values, where it // makes better sense. constexpr array default_endians { BIG, LITTLE }; constexpr array default_addresses { 0x0, 0xFFFFFFFC, 0xFFFF0, 0xFFFF1, 0xFFFFFF }; constexpr array default_strides { 0, 1, 2, 3 }; constexpr array default_values { 0x4142434445464748 }; /** * Test the IntegerDecomposition util, that is used for testing later. */ void TestMemory::integer_decomposition() { const uint64_t value = 0x0102030405060708; IntegerDecomposition big_endian(value, BIG); // Expected sub-values done by hand. array be_u32 { 0x01020304, 0x05060708 }; array be_u16 { 0x0102, 0x0304, 0x0506, 0x0708 }; array be_u8 { 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08 }; QCOMPARE(big_endian.u64, (uint64_t)0x0102030405060708); QCOMPARE(big_endian.u32, be_u32); QCOMPARE(big_endian.u16, be_u16); QCOMPARE(big_endian.u8, be_u8); IntegerDecomposition little_endian(value, LITTLE); // Little endian arrays are reversed to big endian ones. array le_u32 = be_u32; std::reverse(le_u32.begin(), le_u32.end()); array le_u16 = be_u16; std::reverse(le_u16.begin(), le_u16.end()); array le_u8 = be_u8; std::reverse(le_u8.begin(), le_u8.end()); // Const cast is just for types to match, this way reverse is simpler. QCOMPARE(little_endian.u64, (uint64_t)0x0102030405060708); QCOMPARE(little_endian.u32, (const array)le_u32); QCOMPARE(little_endian.u16, (const array)le_u16); QCOMPARE(little_endian.u8, (const array)le_u8); } /** * Common testing dataset generator. * * @tparam N1 size of first argument array * @tparam N2 size of second argument array * @tparam N3 size of third argument array * @tparam N4 size of fourth argument array * @param simulated_endians array of endians to test * @param accessed_addresses array of addresses to test at * @param strides array of offsets of reads to the previous write (because * of endian differences), only tested on 64b write * @param values values to use for memory access, for smaller accesses * part of the value is use */ template constexpr void prepare_data( const array &simulated_endians, const array &accessed_addresses, const array &strides, const array &values) { QTest::addColumn("endian"); QTest::addColumn("address"); QTest::addColumn("stride"); QTest::addColumn("value"); QTest::addColumn("result"); for (auto endian : simulated_endians) { for (auto address : accessed_addresses) { for (auto stride : strides) { for (auto value : values) { // Result when read has `stride` bytes offset to the write. auto result_value = (endian == BIG) ? (value << (stride * 8)) : (value >> (stride * 8)); QTest::addRow( "endian=%s, address=0x%lx, stride=%ld, value=0x%" PRIx64, to_string(endian), address, stride, value) << endian << address << stride << IntegerDecomposition(value, endian) << IntegerDecomposition(result_value, endian); } } } } } void TestMemory::memory_data() { prepare_data(default_endians, default_addresses, default_strides, default_values); } void TestMemory::memory() { QFETCH(Endian, endian); QFETCH(Offset, address); QFETCH(Offset, stride); QFETCH(IntegerDecomposition, value); QFETCH(IntegerDecomposition, result); Memory m(endian); // Uninitialized memory should read as zero QCOMPARE(memory_read_u8(&m, address + stride), (uint8_t)0); QCOMPARE(memory_read_u16(&m, address + stride), (uint16_t)0); QCOMPARE(memory_read_u32(&m, address + stride), (uint32_t)0); QCOMPARE(memory_read_u64(&m, address + stride), (uint64_t)0); // Writes value and reads it in all possible ways (by parts). // It is sufficient to test reads with stride to write of largest write. For // smaller writes, correct result is much harder to prepare. if (stride == 0) { memory_write_u8(&m, address, value.u8.at(0)); QCOMPARE(memory_read_u8(&m, address), result.u8.at(0)); memory_write_u16(&m, address, value.u16.at(0)); QCOMPARE(memory_read_u16(&m, address), result.u16.at(0)); for (size_t i = 0; i < 2; ++i) { QCOMPARE(memory_read_u8(&m, address + i), result.u8.at(i)); } memory_write_u32(&m, address, value.u32.at(0)); QCOMPARE(memory_read_u32(&m, address), result.u32.at(0)); for (size_t i = 0; i < 2; ++i) { QCOMPARE(memory_read_u16(&m, address + 2 * i), result.u16.at(i)); } for (size_t i = 0; i < 4; ++i) { QCOMPARE(memory_read_u8(&m, address + i), result.u8.at(i)); } } memory_write_u64(&m, address, value.u64); QCOMPARE(memory_read_u64(&m, address + stride), result.u64); for (size_t i = 0; i < 2; ++i) { QCOMPARE(memory_read_u32(&m, address + stride + 4 * i), result.u32.at(i)); } for (size_t i = 0; i < 4; ++i) { QCOMPARE(memory_read_u16(&m, address + stride + 2 * i), result.u16.at(i)); } for (size_t i = 0; i < 8; ++i) { QCOMPARE(memory_read_u8(&m, address + stride + i), result.u8.at(i)); } } void TestMemory::memory_section_data() { constexpr array addresses { 0x0, 0xFFFFFFF4, 0xFFFF00, 0xFFFFF2 }; prepare_data(default_endians, addresses, default_strides, default_values); } void TestMemory::memory_section() { QFETCH(Endian, endian); QFETCH(Offset, address); QFETCH(Offset, stride); QFETCH(IntegerDecomposition, value); QFETCH(IntegerDecomposition, result); Memory m(endian); // First section shouldn't exists QCOMPARE(m.get_section(address, false), (MemorySection *)nullptr); // Create section MemorySection *s = m.get_section(address, true); QVERIFY(s != nullptr); // Writes value and reads it in all possible ways (by parts). // It is sufficient to test reads with stride to write of largest write. For // smaller writes, correct result is much harder to prepare. if (stride == 0) { Offset section_offset = address % MEMORY_SECTION_SIZE; memory_write_u8(&m, address, value.u8.at(0)); QCOMPARE(memory_read_u8(s, section_offset), result.u8.at(0)); memory_write_u16(&m, address, value.u16.at(0)); QCOMPARE(memory_read_u16(s, section_offset), result.u16.at(0)); for (size_t i = 0; i < 2; ++i) { QCOMPARE(memory_read_u8(s, section_offset + i), result.u8.at(i)); } memory_write_u32(&m, address, value.u32.at(0)); QCOMPARE(memory_read_u32(s, section_offset), result.u32.at(0)); for (size_t i = 0; i < 2; ++i) { QCOMPARE(memory_read_u16(s, section_offset + 2 * i), result.u16.at(i)); } for (size_t i = 0; i < 4; ++i) { QCOMPARE(memory_read_u8(s, section_offset + i), result.u8.at(i)); } } Offset section_offset = (address + stride) % MEMORY_SECTION_SIZE; memory_write_u64(&m, address, value.u64); QCOMPARE(memory_read_u64(s, section_offset), result.u64); for (size_t i = 0; i < 2; ++i) { QCOMPARE(memory_read_u32(s, section_offset + 4 * i), result.u32.at(i)); } for (size_t i = 0; i < 4; ++i) { QCOMPARE(memory_read_u16(s, section_offset + 2 * i), result.u16.at(i)); } for (size_t i = 0; i < 8; ++i) { QCOMPARE(memory_read_u8(s, section_offset + i), result.u8.at(i)); } } void prepare_endian_test() { QTest::addColumn("endian"); for (auto endian : default_endians) { QTest::addRow("endian=%s", to_string(endian)) << endian; } } void TestMemory::memory_compare_data() { prepare_endian_test(); } void TestMemory::memory_compare() { QFETCH(Endian, endian); Memory m1(endian), m2(endian); QCOMPARE(m1, m2); memory_write_u8(&m1, 0x20, 0x0); QVERIFY(m1 != m2); // This should not be equal as this identifies also // memory write (difference between no write and zero // write) memory_write_u8(&m1, 0x20, 0x24); QVERIFY(m1 != m2); memory_write_u8(&m2, 0x20, 0x23); QVERIFY(m1 != m2); memory_write_u8(&m2, 0x20, 0x24); QCOMPARE(m1, m2); // Do the same with some other section memory_write_u8(&m1, 0xFFFF20, 0x24); QVERIFY(m1 != m2); memory_write_u8(&m2, 0xFFFF20, 0x24); QCOMPARE(m1, m2); // And also check memory copy Memory m3(m1); QCOMPARE(m1, m3); memory_write_u8(&m3, 0x18, 0x22); QVERIFY(m1 != m3); } void TestMemory::memory_write_ctl_data() { QTest::addColumn("ctl"); QTest::addColumn("result"); for (auto endian : default_endians) { Memory mem(endian); QTest::addRow("AC_NONE, endian=%s", to_string(endian)) << AC_NONE << mem; memory_write_u8(&mem, 0x20, 0x30); QTest::addRow("AC_I8, endian=%s", to_string(endian)) << AC_I8 << mem; QTest::addRow("AC_U8, endian=%s", to_string(endian)) << AC_U8 << mem; memory_write_u16(&mem, 0x20, 0x2930); QTest::addRow("AC_I16, endian=%s", to_string(endian)) << AC_I16 << mem; QTest::addRow("AC_U16, endian=%s", to_string(endian)) << AC_U16 << mem; memory_write_u32(&mem, 0x20, 0x27282930); QTest::addRow("AC_I32, endian=%s", to_string(endian)) << AC_I32 << mem; QTest::addRow("AC_U32, endian=%s", to_string(endian)) << AC_U32 << mem; memory_write_u64(&mem, 0x20, 0x2324252627282930ULL); QTest::addRow("AC_I64, endian=%s", to_string(endian)) << AC_I64 << mem; QTest::addRow("AC_U64, endian=%s", to_string(endian)) << AC_U64 << mem; } } void TestMemory::memory_write_ctl() { QFETCH(AccessControl, ctl); QFETCH(Memory, result); // Memory is not supposed to be read directly as it does not implement // frontend memory. TrivialBus was introduced to wrap Memory into the most // simple FrontendMemory with no additional functionality. Memory mem(result.simulated_machine_endian); TrivialBus bus(&mem); bus.write_ctl(ctl, 0x20_addr, (uint64_t)0x2324252627282930ULL); QCOMPARE(mem, result); } void TestMemory::memory_read_ctl_data() { prepare_data(default_endians, default_addresses, default_strides, default_values); } void TestMemory::memory_read_ctl() { QFETCH(Endian, endian); QFETCH(Offset, address); QFETCH(Offset, stride); QFETCH(IntegerDecomposition, value); QFETCH(IntegerDecomposition, result); Address frontend_address(address); Memory mem(endian); TrivialBus bus(&mem); bus.write_u64(frontend_address, value.u64); QCOMPARE(bus.read_ctl(AC_U64, frontend_address + stride).as_u64(), result.u64); for (size_t i = 0; i < 2; ++i) { QCOMPARE( bus.read_ctl(AC_U32, frontend_address + stride + 4 * i).as_u32(), result.u32.at(i)); } for (size_t i = 0; i < 4; ++i) { QCOMPARE( bus.read_ctl(AC_U16, frontend_address + stride + 2 * i).as_u16(), result.u16.at(i)); } for (size_t i = 0; i < 4; ++i) { QCOMPARE( bus.read_ctl(AC_I16, frontend_address + stride + 2 * i).as_i16(), (int16_t)result.u16.at(i)); } for (size_t i = 0; i < 8; ++i) { QCOMPARE(bus.read_ctl(AC_U8, frontend_address + stride + i).as_u8(), result.u8.at(i)); } for (size_t i = 0; i < 8; ++i) { QCOMPARE( bus.read_ctl(AC_I8, frontend_address + stride + i).as_i8(), (int8_t)result.u8.at(i)); } } void TestMemory::memory_memtest_data() { QTest::addColumn("endian"); QTest::addColumn("address"); for (auto endian : default_endians) { for (auto address : default_addresses) { QTest::addRow("endian=%s, address=0x%lx", to_string(endian), address) << endian << address; } } } void TestMemory::memory_memtest() { QFETCH(Endian, endian); QFETCH(Offset, address); Address frontend_address(address); Memory mem(endian); TrivialBus bus(&mem); uint64_t range_to_test = 128 * 1024; if (frontend_address.get_raw() < 0x100000000 && 0x100000000 - range_to_test < frontend_address.get_raw()) range_to_test = uint64_t(Address(0x100000000) - frontend_address); for (uint64_t o = 0; o < range_to_test; o += 4) { bus.write_u32(frontend_address + o, uint32_t(o)); } for (uint64_t o = 0; o < range_to_test; o += 4) { QCOMPARE(bus.read_u32(frontend_address + o), uint32_t(o)); bus.write_u32(frontend_address + o, uint32_t(o) ^ 0xaabbccdd); } for (uint64_t o = 0; o < range_to_test; o += 4) { QCOMPARE(bus.read_u32(frontend_address + o), uint32_t(o) ^ 0xaabbccdd); } } QTEST_APPLESS_MAIN(TestMemory) ================================================ FILE: src/machine/memory/backend/memory.test.h ================================================ #ifndef MEMORY_TEST_H #define MEMORY_TEST_H #include class TestMemory : public QObject { Q_OBJECT private: static void integer_decomposition(); private slots: static void memory(); static void memory_data(); static void memory_section(); static void memory_section_data(); void memory_compare(); void memory_compare_data(); static void memory_write_ctl_data(); static void memory_write_ctl(); static void memory_read_ctl_data(); static void memory_read_ctl(); static void memory_memtest_data(); static void memory_memtest(); }; #endif // MEMORY_TEST_H ================================================ FILE: src/machine/memory/backend/peripheral.cpp ================================================ #include "memory/backend/peripheral.h" #include "common/endian.h" using namespace machine; SimplePeripheral::SimplePeripheral(Endian simulated_machine_endian) : BackendMemory(simulated_machine_endian) {}; SimplePeripheral::~SimplePeripheral() = default; WriteResult SimplePeripheral::write(Offset destination, const void *source, size_t size, WriteOptions options) { UNUSED(source) UNUSED(options) // Write to dummy periphery is nop emit write_notification(destination, size); return { size, false }; } ReadResult SimplePeripheral::read(void *destination, Offset source, size_t size, ReadOptions options) const { UNUSED(options) memset(destination, 0x12, size); // Random value emit read_notification(source, size); return { size }; } LocationStatus SimplePeripheral::location_status(Offset offset) const { UNUSED(offset) return LOCSTAT_NONE; } ================================================ FILE: src/machine/memory/backend/peripheral.h ================================================ #ifndef PERIPHERAL_H #define PERIPHERAL_H #include "common/endian.h" #include "machinedefs.h" #include "memory/backend/backend_memory.h" #include "memory/backend/memory.h" #include "memory/memory_utils.h" #include "simulator_exception.h" #include #include #include namespace machine { /** * NOTE: This peripheral is constant, it does not case about endian. Therefore * it does not have internal_endian field. */ class SimplePeripheral final : public BackendMemory { Q_OBJECT public: explicit SimplePeripheral(Endian simulated_machine_endian); ~SimplePeripheral() override; signals: void write_notification(Offset address, size_t size) const; void read_notification(Offset address, size_t size) const; public: WriteResult write(Offset destination, const void *source, size_t size, WriteOptions options) override; ReadResult read(void *destination, Offset source, size_t size, ReadOptions options) const override; [[nodiscard]] LocationStatus location_status(Offset offset) const override; }; } // namespace machine #endif // PERIPHERAL_H ================================================ FILE: src/machine/memory/backend/peripspiled.cpp ================================================ #include "memory/backend/peripspiled.h" #include "common/endian.h" using namespace machine; constexpr size_t SPILED_REG_LED_LINE_o = 0x004; constexpr size_t SPILED_REG_LED_RGB1_o = 0x010; constexpr size_t SPILED_REG_LED_RGB2_o = 0x014; constexpr size_t SPILED_REG_LED_KBDWR_DIRECT_o = 0x018; constexpr size_t SPILED_REG_KBDRD_KNOBS_DIRECT_o = 0x020; constexpr size_t SPILED_REG_KNOBS_8BIT_o = 0x024; PeripSpiLed::PeripSpiLed(Endian simulated_machine_endian) : BackendMemory(simulated_machine_endian) {} PeripSpiLed::~PeripSpiLed() = default; WriteResult PeripSpiLed::write(Offset destination, const void *source, size_t size, WriteOptions options) { UNUSED(options) return write_by_u32( destination, source, size, [&](Offset src) { return byteswap_if(read_reg(src), internal_endian != simulated_machine_endian); }, [&](Offset src, uint32_t value) { return write_reg(src, byteswap_if(value, internal_endian != simulated_machine_endian)); }); } ReadResult PeripSpiLed::read(void *destination, Offset source, size_t size, ReadOptions options) const { UNUSED(options) return read_by_u32(destination, source, size, [&](Offset src) { return byteswap_if(read_reg(src), internal_endian != simulated_machine_endian); }); } uint32_t PeripSpiLed::read_reg(Offset source) const { Q_ASSERT((source & 3U) == 0); // uint32_t aligned uint32_t value = [&]() { switch (source) { case SPILED_REG_LED_LINE_o: return spiled_reg_led_line; case SPILED_REG_LED_RGB1_o: return spiled_reg_led_rgb1; case SPILED_REG_LED_RGB2_o: return spiled_reg_led_rgb2; case SPILED_REG_LED_KBDWR_DIRECT_o: return spiled_reg_led_kbdwr_direct; case SPILED_REG_KBDRD_KNOBS_DIRECT_o: return spiled_reg_kbdrd_knobs_direct; case SPILED_REG_KNOBS_8BIT_o: return spiled_reg_knobs_8bit; default: // Todo show this to user as this is failure of supplied program printf("[WARNING] PeripSpiLed: read to non-readable location.\n"); return 0u; } }(); emit read_notification(source, value); return value; } bool PeripSpiLed::write_reg(Offset destination, uint32_t value) { Q_ASSERT((destination & 3U) == 0); // uint32_t aligned bool changed = [&]() { switch (destination) { case SPILED_REG_LED_LINE_o: { if (spiled_reg_led_line != value) { spiled_reg_led_line = value; emit led_line_changed(spiled_reg_led_line); return true; } return false; } case SPILED_REG_LED_RGB1_o: if (spiled_reg_led_rgb1 != value) { spiled_reg_led_rgb1 = value; emit led_rgb1_changed(spiled_reg_led_rgb1); return true; } return false; case SPILED_REG_LED_RGB2_o: if (spiled_reg_led_rgb2 != value) { spiled_reg_led_rgb2 = value; emit led_rgb2_changed(spiled_reg_led_rgb2); return true; } return false; default: // Todo show this to user as this is failure of supplied program printf("[WARNING] PeripSpiLed: write to non-writable location.\n"); return false; } }(); emit write_notification(destination, value); return changed; } void PeripSpiLed::knob_update_notify(uint32_t val, uint32_t mask, size_t shift) { mask <<= shift; val <<= shift; if (!((spiled_reg_knobs_8bit ^ val) & mask)) { return; } spiled_reg_knobs_8bit &= ~mask; spiled_reg_knobs_8bit |= val; emit external_backend_change_notify( this, SPILED_REG_KNOBS_8BIT_o, SPILED_REG_KNOBS_8BIT_o + 3, ae::EXTERNAL_ASYNC); } void PeripSpiLed::red_knob_update(int val) { knob_update_notify(val, 0xff, 16); } void PeripSpiLed::green_knob_update(int val) { knob_update_notify(val, 0xff, 8); } void PeripSpiLed::blue_knob_update(int val) { knob_update_notify(val, 0xff, 0); } void PeripSpiLed::red_knob_push(bool state) { knob_update_notify(state ? 1 : 0, 1, 26); } void PeripSpiLed::green_knob_push(bool state) { knob_update_notify(state ? 1 : 0, 1, 25); } void PeripSpiLed::blue_knob_push(bool state) { knob_update_notify(state ? 1 : 0, 1, 24); } LocationStatus PeripSpiLed::location_status(Offset offset) const { switch (offset & ~3U) { case SPILED_REG_LED_LINE_o: FALLTROUGH case SPILED_REG_LED_RGB1_o: FALLTROUGH case SPILED_REG_LED_RGB2_o: { return LOCSTAT_NONE; } case SPILED_REG_LED_KBDWR_DIRECT_o: FALLTROUGH case SPILED_REG_KBDRD_KNOBS_DIRECT_o: FALLTROUGH case SPILED_REG_KNOBS_8BIT_o: { return LOCSTAT_READ_ONLY; } default: { return LOCSTAT_ILLEGAL; } } } ================================================ FILE: src/machine/memory/backend/peripspiled.h ================================================ #ifndef PERIPSPILED_H #define PERIPSPILED_H #include "common/endian.h" #include "machinedefs.h" #include "memory/backend/backend_memory.h" #include "memory/memory_utils.h" #include "simulator_exception.h" #include namespace machine { class PeripSpiLed final : public BackendMemory { Q_OBJECT public: explicit PeripSpiLed(Endian simulated_machine_endian); ~PeripSpiLed() override; signals: void write_notification(Offset address, uint32_t value) const; void read_notification(Offset address, uint32_t value) const; void led_line_changed(uint val) const; void led_rgb1_changed(uint val) const; void led_rgb2_changed(uint val) const; public slots: void red_knob_update(int val); void green_knob_update(int val); void blue_knob_update(int val); void red_knob_push(bool state); void green_knob_push(bool state); void blue_knob_push(bool state); public: WriteResult write(Offset destination, const void *source, size_t size, WriteOptions options) override; ReadResult read(void *destination, Offset source, size_t size, ReadOptions options) const override; [[nodiscard]] LocationStatus location_status(Offset offset) const override; private: [[nodiscard]] uint32_t read_reg(Offset source) const; bool write_reg(Offset destination, uint32_t value); void knob_update_notify(uint32_t val, uint32_t mask, size_t shift); /** endian of internal registers of the periphery use. */ static constexpr Endian internal_endian = NATIVE_ENDIAN; uint32_t spiled_reg_led_line = 0; uint32_t spiled_reg_led_rgb1 = 0; uint32_t spiled_reg_led_rgb2 = 0; uint32_t spiled_reg_led_kbdwr_direct = 0; uint32_t spiled_reg_kbdrd_knobs_direct = 0; uint32_t spiled_reg_knobs_8bit = 0; }; } // namespace machine #endif // PERIPSPILED_H ================================================ FILE: src/machine/memory/backend/serialport.cpp ================================================ #include "memory/backend/serialport.h" #include "common/endian.h" #include LOG_CATEGORY("machine.memory.serialport"); using ae = machine::AccessEffects; // For enum values, type is obvious from // context. namespace machine { constexpr Offset SERP_RX_ST_REG_o = 0x00u; constexpr uint32_t SERP_RX_ST_REG_READY_m = 0x1u; constexpr uint32_t SERP_RX_ST_REG_IE_m = 0x2u; constexpr Offset SERP_RX_DATA_REG_o = 0x04u; constexpr Offset SERP_TX_ST_REG_o = 0x08u; constexpr uint32_t SERP_TX_ST_REG_READY_m = 0x1u; constexpr uint32_t SERP_TX_ST_REG_IE_m = 0x2u; constexpr Offset SERP_TX_DATA_REG_o = 0xcu; SerialPort::SerialPort(Endian simulated_machine_endian) : BackendMemory(simulated_machine_endian) , tx_irq_level(17) // The second platform HW interrupt , rx_irq_level(16) // The first platform HW interrupt , tx_st_reg(SERP_TX_ST_REG_READY_m) {} SerialPort::~SerialPort() = default; void SerialPort::pool_rx_byte() const { unsigned int byte = 0; bool available = false; if (!(rx_st_reg & SERP_RX_ST_REG_READY_m)) { rx_st_reg |= SERP_RX_ST_REG_READY_m; emit rx_byte_pool(0, byte, available); if (available) { change_counter++; rx_data_reg = byte; } else { rx_st_reg &= ~SERP_RX_ST_REG_READY_m; } } } WriteResult SerialPort::write(Offset destination, const void *source, size_t size, WriteOptions options) { UNUSED(options) return write_by_u32( destination, source, size, [&](Offset src) { UNUSED(src) return 0; }, [&](Offset src, uint32_t value) { return write_reg(src, byteswap_if(value, internal_endian != simulated_machine_endian)); }); } ReadResult SerialPort::read(void *destination, Offset source, size_t size, ReadOptions options) const { return read_by_u32(destination, source, size, [&](Offset src) { return byteswap_if( read_reg(src, options.type), internal_endian != simulated_machine_endian); }); } void SerialPort::update_rx_irq() const { bool active = (rx_st_reg & SERP_RX_ST_REG_IE_m) != 0; active &= (rx_st_reg & SERP_RX_ST_REG_READY_m) != 0; if (active != rx_irq_active) { rx_irq_active = active; emit signal_interrupt(rx_irq_level, active); } } void SerialPort::rx_queue_check_internal() const { if (rx_st_reg & SERP_RX_ST_REG_IE_m) { pool_rx_byte(); } update_rx_irq(); } void SerialPort::rx_queue_check() const { rx_queue_check_internal(); emit external_backend_change_notify( this, SERP_RX_ST_REG_o, SERP_RX_DATA_REG_o + 3, ae::EXTERNAL_ASYNC); } void SerialPort::update_tx_irq() const { bool active = (tx_st_reg & SERP_TX_ST_REG_IE_m) != 0; active &= (tx_st_reg & SERP_TX_ST_REG_READY_m) != 0; if (active != tx_irq_active) { tx_irq_active = active; emit signal_interrupt(tx_irq_level, active); } } uint32_t SerialPort::read_reg(Offset source, AccessEffects type) const { Q_ASSERT((source & 3U) == 0); // uint32_t aligned uint32_t value = 0; switch (source) { case SERP_RX_ST_REG_o: pool_rx_byte(); value = rx_st_reg; break; case SERP_RX_DATA_REG_o: pool_rx_byte(); if (rx_st_reg & SERP_RX_ST_REG_READY_m) { value = rx_data_reg; if (type == ae::REGULAR) { rx_st_reg &= ~SERP_RX_ST_REG_READY_m; update_rx_irq(); emit external_backend_change_notify( this, SERP_RX_ST_REG_o, SERP_RX_DATA_REG_o + 3, ae::EXTERNAL_ASYNC); } } else { value = 0; } rx_queue_check_internal(); break; case SERP_TX_ST_REG_o: value = tx_st_reg; break; default: WARN("Serial port - read out of range (at 0x%zu).\n", source); break; } emit read_notification(source, value); return value; } bool SerialPort::write_reg(Offset destination, uint32_t value) { Q_ASSERT((destination & 3U) == 0); // uint32_t aligned bool changed = [&]() { switch (destination & ~3U) { case SERP_RX_ST_REG_o: rx_st_reg &= ~SERP_RX_ST_REG_IE_m; rx_st_reg |= value & SERP_RX_ST_REG_IE_m; rx_queue_check_internal(); update_rx_irq(); return true; case SERP_TX_ST_REG_o: tx_st_reg &= ~SERP_TX_ST_REG_IE_m; tx_st_reg |= value & SERP_TX_ST_REG_IE_m; update_tx_irq(); return true; case SERP_TX_DATA_REG_o: emit tx_byte(value & 0xffu); update_tx_irq(); return true; default: WARN("Serial port - write out of range (at 0x%zu).\n", destination); return false; } }(); emit write_notification(destination, value); return changed; } LocationStatus SerialPort::location_status(Offset offset) const { switch (offset & ~3U) { case SERP_RX_ST_REG_o: FALLTROUGH case SERP_TX_ST_REG_o: FALLTROUGH case SERP_TX_DATA_REG_o: // This is actually write only, but there is no // enum for that. { return LOCSTAT_NONE; } case SERP_RX_DATA_REG_o: { return LOCSTAT_READ_ONLY; } default: { return LOCSTAT_ILLEGAL; } } } uint32_t SerialPort::get_change_counter() const { return change_counter; } } // namespace machine ================================================ FILE: src/machine/memory/backend/serialport.h ================================================ #ifndef SERIALPORT_H #define SERIALPORT_H #include "common/endian.h" #include "memory/backend/backend_memory.h" #include "memory/backend/peripheral.h" #include "simulator_exception.h" #include namespace machine { class SerialPort : public BackendMemory { Q_OBJECT public: explicit SerialPort(Endian simulated_machine_endian); ~SerialPort() override; signals: void tx_byte(unsigned int data); void rx_byte_pool(int fd, unsigned int &data, bool &available) const; void write_notification(Offset address, uint32_t value); void read_notification(Offset address, uint32_t value) const; void signal_interrupt(uint irq_level, bool active) const; public slots: void rx_queue_check() const; public: WriteResult write(Offset destination, const void *source, size_t size, WriteOptions options) override; ReadResult read(void *destination, Offset source, size_t size, ReadOptions options) const override; LocationStatus location_status(Offset offset) const override; private: uint32_t read_reg(Offset source, AccessEffects type) const; bool write_reg(Offset destination, uint32_t value); void rx_queue_check_internal() const; void pool_rx_byte() const; void update_rx_irq() const; void update_tx_irq() const; uint32_t get_change_counter() const; /** endian of internal registers of the periphery use. */ static constexpr Endian internal_endian = NATIVE_ENDIAN; const uint8_t tx_irq_level; const uint8_t rx_irq_level; mutable uint32_t change_counter = { 0 }; mutable uint32_t tx_st_reg = { 0 }; mutable uint32_t rx_st_reg = { 0 }; mutable uint32_t rx_data_reg = { 0 }; mutable bool tx_irq_active = false; mutable bool rx_irq_active = false; }; } // namespace machine #endif // SERIALPORT_H ================================================ FILE: src/machine/memory/cache/cache.cpp ================================================ #include "memory/cache/cache.h" #include "memory/cache/cache_types.h" #include using ae = machine::AccessEffects; // For enum values, type is obvious from // context. namespace machine { Cache::Cache( FrontendMemory *memory, const CacheConfig *config, uint32_t memory_access_penalty_r, uint32_t memory_access_penalty_w, uint32_t memory_access_penalty_b, bool memory_access_enable_b) : FrontendMemory(memory->simulated_machine_endian) , cache_config(config) , mem(memory) , uncached_start(0xf0000000_addr) , uncached_last(0xfffffffe_addr) , access_pen_r(memory_access_penalty_r) , access_pen_w(memory_access_penalty_w) , access_pen_b(memory_access_penalty_b) , access_ena_b(memory_access_enable_b) , replacement_policy(CachePolicy::get_policy_instance(config)) { // Skip memory allocation if cache is disabled if (!config->enabled()) { return; } dt.resize( config->associativity(), std::vector( config->set_count(), { .valid = false, .dirty = false, .tag = 0, .data = std::vector(config->block_size()) })); } Cache::~Cache() = default; WriteResult Cache::write(AddressWithMode destination, const void *source, size_t size, WriteOptions options) { if (!cache_config.enabled() || is_in_uncached_area(destination) || is_in_uncached_area(destination + size)) { mem_writes++; emit memory_writes_update(mem_writes); update_all_statistics(); return mem->write(destination, source, size, options); } // FIXME: Get rid of the cast // access is mostly the same for read and write but one needs to write // to the address const bool changed = access(destination, const_cast(source), size, WRITE); if (cache_config.write_policy() != CacheConfig::WP_BACK) { mem_writes++; emit memory_writes_update(mem_writes); update_all_statistics(); return mem->write(destination, source, size, options); } return { .n_bytes = size, .changed = changed }; } ReadResult Cache::read(void *destination, AddressWithMode source, size_t size, ReadOptions options) const { if (!cache_config.enabled() || is_in_uncached_area(source) || is_in_uncached_area(source + size)) { mem_reads++; emit memory_reads_update(mem_reads); update_all_statistics(); return mem->read(destination, source, size, options); } if (options.type == ae::INTERNAL) { if (!(location_status(source) & LOCSTAT_CACHED)) { mem->read(destination, source, size, options); } else { internal_read(source, destination, size); } return {}; } access(source, destination, size, READ); return {}; } bool Cache::is_in_uncached_area(Address source) const { return (source >= uncached_start && source <= uncached_last); } void Cache::flush() { if (!cache_config.enabled()) { return; } for (size_t assoc_index = 0; assoc_index < cache_config.associativity(); assoc_index += 1) { for (size_t set_index = 0; set_index < cache_config.set_count(); set_index += 1) { if (dt[assoc_index][set_index].valid) { kick(assoc_index, set_index); emit cache_update(assoc_index, set_index, 0, false, false, 0, nullptr, false); } } } change_counter++; update_all_statistics(); } void Cache::sync() { flush(); } void Cache::reset() { // Set all cells to invalid if (cache_config.enabled()) { for (auto &set : dt) { for (auto &block : set) { block.valid = false; } } // Note: We don't have to zero replacement policy data as those are // zeroed when first used on invalid cell. } hit_read = 0; hit_write = 0; miss_read = 0; miss_write = 0; mem_reads = 0; mem_writes = 0; burst_reads = 0; burst_writes = 0; emit hit_update(get_hit_count()); emit miss_update(get_miss_count()); emit memory_reads_update(get_read_count()); emit memory_writes_update(get_write_count()); update_all_statistics(); if (cache_config.enabled()) { for (size_t assoc_index = 0; assoc_index < cache_config.associativity(); assoc_index++) { for (size_t set_index = 0; set_index < cache_config.set_count(); set_index++) { emit cache_update(assoc_index, set_index, 0, false, false, 0, nullptr, false); } } } } void Cache::internal_read(Address source, void *destination, size_t size) const { CacheLocation loc = compute_location(source); for (size_t assoc_index = 0; assoc_index < cache_config.associativity(); assoc_index++) { if (dt[assoc_index][loc.row].valid && dt[assoc_index][loc.row].tag == loc.tag) { memcpy(destination, (byte *)&dt[assoc_index][loc.row].data[loc.col] + loc.byte, size); return; } } memset(destination, 0, size); // TODO is this correct } bool Cache::access(Address address, void *buffer, size_t size, AccessType access_type) const { const CacheLocation loc = compute_location(address); size_t way = find_block_index(loc); // check for zero because else last_affected_col can became // ULONG_MAX / BLOCK_ITEM_SIZE and update can take forever if (size == 0) return false; // search failed - cache miss if (way >= cache_config.associativity()) { // if write through we do not need to allocate cache line does not // allocate if (access_type == WRITE && cache_config.write_policy() == CacheConfig::WP_THROUGH_NOALLOC) { miss_write++; emit miss_update(get_miss_count()); update_all_statistics(); const size_t size_overflow = calculate_overflow_to_next_blocks(size, loc); if (size_overflow > 0) { const size_t size_within_block = size - size_overflow; return access( address + size_within_block, (byte *)buffer + size_within_block, size_overflow, access_type); } else { return false; } } way = replacement_policy->select_way_to_evict(loc.row); kick(way, loc.row); SANITY_ASSERT( way < cache_config.associativity(), "Probably unimplemented replacement policy"); } struct CacheLine &cd = dt[way][loc.row]; // Update statistics and otherwise read from memory if (cd.valid) { if (access_type == WRITE) { hit_write++; } else { hit_read++; } emit hit_update(get_hit_count()); update_all_statistics(); } else { if (access_type == WRITE) { miss_write++; } else { miss_read++; } emit miss_update(get_miss_count()); mem->read( cd.data.data(), calc_base_address(loc.tag, loc.row), cache_config.block_size() * BLOCK_ITEM_SIZE, { .type = ae::REGULAR }); cd.valid = true; cd.dirty = false; cd.tag = loc.tag; change_counter += cache_config.block_size(); mem_reads += cache_config.block_size(); burst_reads += cache_config.block_size() - 1; emit memory_reads_update(mem_reads); update_all_statistics(); } replacement_policy->update_stats(way, loc.row, cd.valid); const size_t size_overflow = calculate_overflow_to_next_blocks(size, loc); const size_t size_within_block = size - size_overflow; bool changed = false; if (access_type == READ) { memcpy(buffer, (byte *)&cd.data[loc.col] + loc.byte, size_within_block); } else if (access_type == WRITE) { cd.dirty = true; changed = memcmp((byte *)&cd.data[loc.col] + loc.byte, buffer, size_within_block) != 0; if (changed) { memcpy(((byte *)&cd.data[loc.col]) + loc.byte, buffer, size_within_block); change_counter++; } } const auto last_affected_col = (loc.col * BLOCK_ITEM_SIZE + loc.byte + size_within_block - 1) / BLOCK_ITEM_SIZE; for (auto col = loc.col; col <= last_affected_col; col++) { emit cache_update( way, loc.row, col, cd.valid, cd.dirty, cd.tag, cd.data.data(), access_type); } if (size_overflow > 0) { // If access overlaps single cache row, perform access to next row. changed |= access( address + size_within_block, (byte *)buffer + size_within_block, size_overflow, access_type); } return changed; } size_t Cache::calculate_overflow_to_next_blocks(size_t access_size, const CacheLocation &loc) const { return std::max( (ptrdiff_t)(loc.col * BLOCK_ITEM_SIZE + loc.byte + access_size) - (ptrdiff_t)(cache_config.block_size() * BLOCK_ITEM_SIZE), { 0 }); } size_t Cache::find_block_index(const CacheLocation &loc) const { uint32_t index = 0; while (index < cache_config.associativity() and (!dt[index][loc.row].valid or dt[index][loc.row].tag != loc.tag)) { index++; } return index; } void Cache::kick(size_t way, size_t row) const { struct CacheLine &cd = dt[way][row]; if (cd.dirty && cache_config.write_policy() == CacheConfig::WP_BACK) { mem->write( calc_base_address(cd.tag, row), cd.data.data(), cache_config.block_size() * BLOCK_ITEM_SIZE, {}); mem_writes += cache_config.block_size(); burst_writes += cache_config.block_size() - 1; emit memory_writes_update(mem_writes); } cd.valid = false; cd.dirty = false; change_counter++; replacement_policy->update_stats(way, row, false); } void Cache::update_all_statistics() const { emit statistics_update(get_stall_count(), get_speed_improvement(), get_hit_rate()); } Address Cache::calc_base_address(size_t tag, size_t row) const { return Address( (tag * cache_config.set_count() + row) * cache_config.block_size() * BLOCK_ITEM_SIZE); } CacheLocation Cache::compute_location(Address address) const { // Get address in multiples of 4 bytes (basic storage unit size) and get the // reminder to address individual byte within basic storage unit. auto word_index = address.get_raw() / BLOCK_ITEM_SIZE; auto byte = address.get_raw() % BLOCK_ITEM_SIZE; // Associativity does not influence location (hit will be on the // same place in each way). Lets therefore assume associativity degree 1. // Same address modulo `way_size_words` will alias (up to associativity). auto way_size_words = cache_config.set_count() * cache_config.block_size(); // Index in a way, when rows and cols would be rearranged into 1D array. auto index_in_way = word_index % way_size_words; auto tag = word_index / way_size_words; return { .row = index_in_way / cache_config.block_size(), .col = index_in_way % cache_config.block_size(), .tag = tag, .byte = byte }; } enum LocationStatus Cache::location_status(Address address) const { const CacheLocation loc = compute_location(address); if (cache_config.enabled()) { for (auto const &set : dt) { auto const &block = set[loc.row]; if (block.valid && block.tag == loc.tag) { if (block.dirty && cache_config.write_policy() == CacheConfig::WP_BACK) { return (enum LocationStatus)(LOCSTAT_CACHED | LOCSTAT_DIRTY); } else { return LOCSTAT_CACHED; } } } } return mem->location_status(address); } const CacheConfig &Cache::get_config() const { return cache_config; } uint32_t Cache::get_change_counter() const { return change_counter; } uint32_t Cache::get_hit_count() const { return hit_read + hit_write; } uint32_t Cache::get_miss_count() const { return miss_read + miss_write; } uint32_t Cache::get_read_count() const { return mem_reads; } uint32_t Cache::get_write_count() const { return mem_writes; } uint32_t Cache::get_stall_count() const { uint32_t st_cycles = mem_reads * (access_pen_r - 1) + mem_writes * (access_pen_w - 1); st_cycles += (miss_read + miss_write) * cache_config.block_size(); if (access_ena_b) { st_cycles -= burst_reads * (access_pen_r - access_pen_b) + burst_writes * (access_pen_w - access_pen_b); } return st_cycles; } double Cache::get_speed_improvement() const { uint32_t lookup_time; uint32_t mem_access_time; uint32_t comp = hit_read + hit_write + miss_read + miss_write; if (comp == 0) { return 100.0; } lookup_time = hit_read + miss_read; if (cache_config.write_policy() == CacheConfig::WP_BACK) { lookup_time += hit_write + miss_write; } mem_access_time = mem_reads * access_pen_r + mem_writes * access_pen_w; if (access_ena_b) { mem_access_time -= burst_reads * (access_pen_r - access_pen_b) + burst_writes * (access_pen_w - access_pen_b); } return ( (double)((miss_read + hit_read) * access_pen_r + (miss_write + hit_write) * access_pen_w) / (double)(lookup_time + mem_access_time) * 100); } double Cache::get_hit_rate() const { uint32_t comp = hit_read + hit_write + miss_read + miss_write; if (comp == 0) { return 0.0; } return (double)(hit_read + hit_write) / (double)comp * 100.0; } } // namespace machine ================================================ FILE: src/machine/memory/cache/cache.h ================================================ #ifndef CACHE_H #define CACHE_H #include "machineconfig.h" #include "memory/cache/cache_policy.h" #include "memory/cache/cache_types.h" #include "memory/frontend_memory.h" #include #include namespace machine { constexpr size_t BLOCK_ITEM_SIZE = sizeof(uint32_t); /** * NOTE ON TERMINOLOGY: * N-way set associative cache consist of N ways (where N is degree * of associativity). Arguments requesting N are called `associativity` of the * cache. Each way consist of blocks. (When we want to highlight, that we talk * about data + management tags, we speak of cache line. When we speak about * location of block within a way, we use the term `row`. Each block consists of * some number of basic storage units (here `uint32_t`). To locate a single unit * withing block, we use therm `col` (as column). * * Set is consists of all block on the same row across the ways. * * We can imagine a cache as 3D array indexed via triple (`way`, `row`, `col`). * Row and col are derived from part of a address deterministically. The rest * of the address is called `tag`. Set is obtained via linear search and placing * into cache, it is determined by cache replacement policy * (see `memory/cache/cache_policy.h`). */ class Cache : public FrontendMemory { Q_OBJECT public: /** * @param memory backing memory used to handle misses * @param simulated_endian endian of the simulated CPU/memory * system * @param config cache configuration struct * @param memory_access_penalty_r cycles to perform read (stats only) * @param memory_access_penalty_w cycles to perform write (stats only) * @param memory_access_penalty_b cycles to perform burst access (stats * only) * * NOTE: Memory access penalties apply only to statistics and are not taken * into account during simulation itself. There is no point in doing so * without superscalar execution. */ Cache( FrontendMemory *memory, const CacheConfig *config, uint32_t memory_access_penalty_r = 1, uint32_t memory_access_penalty_w = 1, uint32_t memory_access_penalty_b = 0, bool memory_access_enable_b = false); ~Cache() override; WriteResult write(AddressWithMode destination, const void *source, size_t size, WriteOptions options) override; ReadResult read(void *destination, AddressWithMode source, size_t size, ReadOptions options) const override; uint32_t get_change_counter() const override; void flush(); // flush cache void sync() override; // Same as flush uint32_t get_hit_count() const; // Number of recorded hits uint32_t get_miss_count() const; // Number of recorded misses uint32_t get_read_count() const; // Number backing/main memory reads uint32_t get_write_count() const; // Number backing/main memory writes uint32_t get_stall_count() const; // Number of wasted cycles in // memory waiting statistic double get_speed_improvement() const; // Speed improvement in percents in // comare with no used cache double get_hit_rate() const; // Usage efficiency in percents void reset(); // Reset whole state of cache const CacheConfig &get_config() const; enum LocationStatus location_status(Address address) const override; signals: void hit_update(uint32_t) const; void miss_update(uint32_t) const; void statistics_update(uint32_t stalled_cycles, double speed_improv, double hit_rate) const; void cache_update( size_t way, size_t row, size_t col, bool valid, bool dirty, size_t tag, const uint32_t *data, bool write) const; void memory_writes_update(uint32_t) const; void memory_reads_update(uint32_t) const; private: const CacheConfig cache_config; FrontendMemory *const mem; const Address uncached_start; const Address uncached_last; const uint32_t access_pen_r, access_pen_w, access_pen_b; const bool access_ena_b; const std::unique_ptr replacement_policy; mutable std::vector> dt; mutable uint32_t hit_read = 0, miss_read = 0, hit_write = 0, miss_write = 0, mem_reads = 0, mem_writes = 0, burst_reads = 0, burst_writes = 0, change_counter = 0; void internal_read(Address source, void *destination, size_t size) const; bool access(Address address, void *buffer, size_t size, AccessType access_type) const; void kick(size_t way, size_t row) const; Address calc_base_address(size_t tag, size_t row) const; void update_all_statistics() const; CacheLocation compute_location(Address address) const; /** * Searches for given tag in a set * * @param loc requested location in cache * @return associativity index of found block, max index + 1 if not * found */ size_t find_block_index(const CacheLocation &loc) const; bool is_in_uncached_area(Address source) const; /** * RW access to cache may span multiple blocks but it needs to be * performed per block. * This functions calculated the size, that will have to be performed by * repeated access (recursive call of `access` method). */ size_t calculate_overflow_to_next_blocks(size_t access_size, const CacheLocation &loc) const; }; } // namespace machine #endif // CACHE_H ================================================ FILE: src/machine/memory/cache/cache.test.cpp ================================================ #include "cache.test.h" #include "common/endian.h" #include "machine/memory/backend/memory.h" #include "machine/memory/cache/cache.h" #include "machine/memory/cache/cache_policy.h" #include "machine/memory/memory_bus.h" #include "tests/data/cache_test_performance_data.h" #include #include using namespace machine; using std::array; using std::pair; using std::size_t; using std::tuple; using Testcase = tuple; /* * Testing data for memory accesses * (all combinations are tested) */ constexpr array simulated_endians { BIG, LITTLE }; constexpr array accessed_addresses { 0x0, 0xFFFF0, 0xFFFF1, 0xFFFFFF, 0xFFFFFFCC, }; constexpr array strides { 0, 1, 2 }; constexpr array values { 0x4142434445464748 }; /* * Cache configuration parameters for testing * (all combinations are tested) */ constexpr array replacement_policies { CacheConfig::RP_RAND, CacheConfig::RP_LFU, CacheConfig::RP_LRU, CacheConfig::RP_PLRU, CacheConfig::RP_NMRU }; constexpr array write_policies { CacheConfig::WP_THROUGH_NOALLOC, // THIS // IS // BROKEN // IN // CACHE // FOR // STRIDE // 1 CacheConfig::WP_THROUGH_ALLOC, CacheConfig::WP_BACK }; constexpr array, 3> organizations { { { 8, 1 }, { 1, 8 }, { 4, 4 }, } }; constexpr array associativity_degrees { 1, 2, 4 }; // + 1 is for disabled cache constexpr size_t CONFIG_COUNT = (replacement_policies.size() * write_policies.size() * organizations.size() * associativity_degrees.size()) + 1; /** * Creates cache_config objects from all combinations of the above given * parameters. */ array get_testing_cache_configs() { array configs; configs.at(0).set_enabled(false); size_t i = 1; for (auto replacement_policy : replacement_policies) { for (auto write_policy : write_policies) { for (auto organization : organizations) { for (auto associativity : associativity_degrees) { configs.at(i).set_enabled(true); configs.at(i).set_replacement_policy(replacement_policy); configs.at(i).set_write_policy(write_policy); configs.at(i).set_set_count(organization.first); configs.at(i).set_block_size(organization.second); configs.at(i).set_associativity(associativity); i += 1; } } } } Q_ASSERT(i == CONFIG_COUNT); return configs; } void TestCache::cache_data() { QTest::addColumn("cache_c"); QTest::addColumn("hit"); QTest::addColumn("miss"); CacheConfig cache_c; cache_c.set_write_policy(CacheConfig::WP_THROUGH_ALLOC); cache_c.set_enabled(true); cache_c.set_set_count(8); cache_c.set_block_size(1); cache_c.set_associativity(1); QTest::newRow("Directly mapped") << cache_c << (unsigned)3 << (unsigned)7; cache_c.set_set_count(1); cache_c.set_block_size(8); QTest::newRow("Wide") << cache_c << (unsigned)5 << (unsigned)5; cache_c.set_set_count(4); cache_c.set_block_size(4); QTest::newRow("Square") << cache_c << (unsigned)4 << (unsigned)6; } void TestCache::cache() { QFETCH(CacheConfig, cache_c); QFETCH(unsigned, hit); QFETCH(unsigned, miss); Memory m(BIG); TrivialBus m_frontend(&m); Cache cache(&m_frontend, &cache_c); // Test reads // memory_write_u32(&m, 0x200, 0x24); memory_write_u32(&m, 0x204, 0x66); memory_write_u32(&m, 0x21c, 0x12); memory_write_u32(&m, 0x300, 0x32); for (int i = 0; i < 2; i++) { QCOMPARE(cache.read_u32(0x200_addr), (uint32_t)0x24); QCOMPARE(cache.read_u32(0x204_addr), (uint32_t)0x66); QCOMPARE(cache.read_u32(0x21c_addr), (uint32_t)0x12); QCOMPARE(cache.read_u32(0x300_addr), (uint32_t)0x32); } // Test writes // cache.write_u32(0x700_addr, 0x24); QCOMPARE(memory_read_u32(&m, 0x700), (uint32_t)0x24); cache.write_u32(0x700_addr, 0x23); QCOMPARE(memory_read_u32(&m, 0x700), (uint32_t)0x23); // Verify counts QCOMPARE(cache.get_hit_count(), hit); QCOMPARE(cache.get_miss_count(), miss); } void TestCache::cache_correctness_data() { QTest::addColumn("endian"); QTest::addColumn

("address"); QTest::addColumn("stride"); QTest::addColumn("value"); QTest::addColumn("result"); QTest::addColumn("cache_config"); // Used for cache hit/miss performance checking. QTest::addColumn("case_number"); size_t i = 0; for (auto cache_config : get_testing_cache_configs()) { for (auto endian : simulated_endians) { for (auto address : accessed_addresses) { for (auto stride : strides) { for (auto value : values) { // Result when read has `stride` bytes offset to the // write. auto result_value = (endian == BIG) ? (value << (stride * 8)) : (value >> (stride * 8)); QTest::addRow( "endian=%s, address=0x%" PRIx64 ", stride=%ld, " "value=0x%" PRIx64 ", cache_config={ %d, " "r=%d, wr=%d, s=%d, b=%d, a=%d }", to_string(endian), address, stride, value, cache_config.enabled(), cache_config.replacement_policy(), cache_config.write_policy(), cache_config.set_count(), cache_config.block_size(), cache_config.associativity()) << endian << Address(address) << stride << IntegerDecomposition(value, endian) << IntegerDecomposition(result_value, endian) << cache_config << i; i += 1; } } } } } } void TestCache::cache_correctness() { QFETCH(Endian, endian); QFETCH(Address, address); QFETCH(size_t, stride); QFETCH(IntegerDecomposition, value); QFETCH(IntegerDecomposition, result); QFETCH(CacheConfig, cache_config); QFETCH(size_t, case_number); // Prepare memory hierarchy Memory mem(endian); MemoryDataBus bus(endian); bus.insert_device_to_range(&mem, 0_addr, 0xffffffff_addr, false); Cache cache(&bus, &cache_config); // Uninitialized memory should read as zero QCOMPARE(cache.read_u8(address + stride), (uint8_t)0); QCOMPARE(cache.read_u16(address + stride), (uint16_t)0); QCOMPARE(cache.read_u32(address + stride), (uint32_t)0); QCOMPARE(cache.read_u64(address + stride), (uint64_t)0); // Writes value and reads it in all possible ways (by parts). // It is sufficient to test reads with stride to write of largest write. For // smaller writes, correct result is much harder to prepare. if (stride == 0) { cache.write_u8(address, value.u8.at(0)); QCOMPARE(cache.read_u8(address), result.u8.at(0)); cache.write_u16(address, value.u16.at(0)); QCOMPARE(cache.read_u16(address), result.u16.at(0)); for (size_t i = 0; i < 2; ++i) { QCOMPARE(cache.read_u8(address + i), result.u8.at(i)); } cache.write_u32(address, value.u32.at(0)); QCOMPARE(cache.read_u32(address), result.u32.at(0)); for (size_t i = 0; i < 2; ++i) { QCOMPARE(cache.read_u16(address + 2 * i), result.u16.at(i)); } for (size_t i = 0; i < 4; ++i) { QCOMPARE(cache.read_u8(address + i), result.u8.at(i)); } } cache.write_u64(address, value.u64); QCOMPARE(cache.read_u64(address + stride), result.u64); for (size_t i = 0; i < 2; ++i) { QCOMPARE(cache.read_u32(address + stride + 4 * i), result.u32.at(i)); } for (size_t i = 0; i < 4; ++i) { QCOMPARE(cache.read_u16(address + stride + 2 * i), result.u16.at(i)); } for (size_t i = 0; i < 8; ++i) { QCOMPARE(cache.read_u8(address + stride + i), result.u8.at(i)); } tuple performance { cache.get_hit_count(), cache.get_miss_count() }; // CODE USED FOR PERFORMANCE DATA GENERATION // fprintf( // stderr, "{ %d, %d }, ", cache.get_hit_count(), // cache.get_miss_count()); if ((cache_config.replacement_policy() != CacheConfig::RP_RAND) && (cache_config.replacement_policy() != CacheConfig::RP_NMRU)) { // Performance of random policy is implementation dependant and // meaningless. QCOMPARE(performance, cache_test_performance_data.at(case_number)); } } QTEST_APPLESS_MAIN(TestCache) ================================================ FILE: src/machine/memory/cache/cache.test.h ================================================ #ifndef CACHE_TEST_H #define CACHE_TEST_H #include class TestCache : public QObject { Q_OBJECT private slots: static void cache_data(); static void cache(); static void cache_correctness_data(); static void cache_correctness(); }; #endif // CACHE_TEST_H ================================================ FILE: src/machine/memory/cache/cache_policy.cpp ================================================ #include "cache_policy.h" #include "simulator_exception.h" #include "utils.h" #include #include namespace machine { std::unique_ptr CachePolicy::get_policy_instance(const CacheConfig *config) { if (config->enabled()) { switch (config->replacement_policy()) { case CacheConfig::RP_RAND: return std::make_unique(config->associativity()); case CacheConfig::RP_LRU: return std::make_unique(config->associativity(), config->set_count()); case CacheConfig::RP_LFU: return std::make_unique(config->associativity(), config->set_count()); case CacheConfig::RP_PLRU: return std::make_unique(config->associativity(), config->set_count()); case CacheConfig::RP_NMRU: return std::make_unique(config->associativity(), config->set_count()); } } else { // Disabled cache will never use it. return { nullptr }; } Q_UNREACHABLE(); } CachePolicyLRU::CachePolicyLRU(size_t associativity, size_t set_count) : associativity(associativity) { stats.resize(set_count); for (auto &row : stats) { row.reserve(associativity); for (size_t i = 0; i < associativity; i++) { row.push_back(i); } } } void CachePolicyLRU::update_stats(size_t way, size_t row, bool is_valid) { // The following code is essentially a single pass of bubble sort (with // temporary variable instead of inplace swapping) adding one element to // back or front (respectively) of a sorted array. The sort stops, when the // original location of the inserted element is reached and the original // instance is moved to the temporary variable (`next_way`). try { // Statistics corresponding to single cache row std::vector &row_stats = stats.at(row); uint32_t next_way = way; if (is_valid) { ptrdiff_t i = (ptrdiff_t)associativity - 1; do { std::swap(row_stats.at(i), next_way); i--; } while (next_way != way); } else { size_t i = 0; do { std::swap(row_stats.at(i), next_way); i++; } while (next_way != way); } } catch (std::out_of_range &e) { throw SANITY_EXCEPTION("Out of range: LRU lost the way from priority queue."); } } size_t CachePolicyLRU::select_way_to_evict(size_t row) const { return stats.at(row).at(0); } CachePolicyLFU::CachePolicyLFU(size_t associativity, size_t set_count) { stats.resize(set_count, std::vector(associativity, 0)); } void CachePolicyLFU::update_stats(size_t way, size_t row, bool is_valid) { auto &stat_item = stats.at(row).at(way); if (is_valid) { stat_item += 1; } else { stat_item = 0; } } size_t CachePolicyLFU::select_way_to_evict(size_t row) const { size_t index = 0; try { // Statistics corresponding to single cache row auto &row_stats = stats.at(row); size_t lowest = row_stats.at(0); for (size_t i = 0; i < row_stats.size(); i++) { if (row_stats.at(i) == 0) { // Only invalid blocks have zero stat index = i; break; } if (lowest > row_stats.at(i)) { lowest = row_stats.at(i); index = i; } } } catch (std::out_of_range &e) { throw SANITY_EXCEPTION("Out of range: LRU lost the way from priority queue."); } return index; } CachePolicyRAND::CachePolicyRAND(size_t associativity) : associativity(associativity) { // Reset random generator to make result reproducible. // Random is by default seeded by 1 (by cpp standard), so this makes it // consistent across multiple runs. // NOTE: Reproducibility applies only on the same execution environment. std::srand(1); // NOLINT(cert-msc51-cpp) } void CachePolicyRAND::update_stats(size_t way, size_t row, bool is_valid) { UNUSED(way) UNUSED(row) UNUSED(is_valid) // NOP } size_t CachePolicyRAND::select_way_to_evict(size_t row) const { UNUSED(row) return std::rand() % associativity; // NOLINT(cert-msc50-cpp) } CachePolicyPLRU::CachePolicyPLRU(size_t associativity, size_t set_count) : associativity(associativity) , associativityCLog2(std::ceil(log2((float)associativity))) { plru_ptr.resize(set_count); for (auto &row : plru_ptr) { row.resize((1 << associativityCLog2) - 1, 0); // Initially point to block 0 } } void CachePolicyPLRU::update_stats(size_t way, size_t row, bool is_valid) { UNUSED(is_valid) // PLRU use a set of binary tree structured pointers to keep track of // the least recently used block, the number of pointers for each // row is 2^associativityCLog2 - 1, where associativityCLog2 is the ceil // log2 of associativity, e.x. 1 + 2 + 4 for cache with associativity from // 5 to 8. When doing state update, we have to access one pointer in each // level, therefore we have to ues an index (plru_idx) to track the currently accessing pointer uint32_t plru_idx = 0; // Index of pointer auto &row_ptr = plru_ptr.at(row); // Pointer of accessed row for (uint32_t i = 0; i < associativityCLog2; i++) { // Toggle the pointer to another direction row_ptr[plru_idx] = ((way >> (associativityCLog2 - 1 - i)) & 1) ? 0 : 1; plru_idx = (1 << (i + 1)) - 1; plru_idx += way >> (associativityCLog2 - 1 - i); } } size_t CachePolicyPLRU::select_way_to_evict(size_t row) const { uint32_t idx = 0; uint32_t plru_idx = 0; auto &row_ptr = plru_ptr.at(row); for (uint32_t i = 0; i < associativityCLog2; i++) { idx <<= 1; uint32_t ptr = row_ptr[plru_idx]; idx += ptr; plru_idx = (1 << (i + 1)) - 1; plru_idx += idx; } return (idx >= associativity) ? (associativity - 1) : idx; } CachePolicyNMRU::CachePolicyNMRU(size_t associativity, size_t set_count) : associativity(associativity) { mru_ptr.resize(set_count); for (auto &row : mru_ptr) { row = 0; // Initially point to block 0 } std::srand(1); // NOLINT(cert-msc51-cpp) } void CachePolicyNMRU::update_stats(size_t way, size_t row, bool is_valid) { UNUSED(is_valid) auto &row_ptr = mru_ptr.at(row); // Set currently accessed block to most recently used row_ptr = way; } size_t CachePolicyNMRU::select_way_to_evict(size_t row) const { if (associativity == 1) { return 0; } uint32_t idx = std::rand() % (associativity - 1); auto &row_ptr = mru_ptr.at(row); idx = (idx < row_ptr) ? idx : idx + 1; return idx; } } // namespace machine ================================================ FILE: src/machine/memory/cache/cache_policy.h ================================================ #ifndef CACHE_POLICY_H #define CACHE_POLICY_H #include "machineconfig.h" #include "memory/cache/cache_types.h" #include #include #include using std::size_t; namespace machine { /** * Cache replacement policy interface. * * For clarification of cache terminiology, see docstring of `Cache` in * `memory/cache/cache.h`. */ class CachePolicy { public: [[nodiscard]] virtual size_t select_way_to_evict(size_t row) const = 0; /** * To be called by cache on any change of validity. * @param way associativity way * @param row cache row (index of block/set) * @param is_valid is cache data valid (as in `cd.valid`) */ virtual void update_stats(size_t way, size_t row, bool is_valid) = 0; virtual ~CachePolicy() = default; static std::unique_ptr get_policy_instance(const CacheConfig *config); }; /** * Last recently used policy * * Keeps queue of associativity indexes from last accessed * to most recently accesed. * Empty indexes are shifted to the begining. */ class CachePolicyLRU final : public CachePolicy { public: /** * @param associativity degree of assiciaivity * @param set_count number of blocks / rows in a way (or sets in * cache) */ CachePolicyLRU(size_t associativity, size_t set_count); [[nodiscard]] size_t select_way_to_evict(size_t row) const final; void update_stats(size_t way, size_t row, bool is_valid) final; private: /** * Last access order queues for each cache set (row) */ std::vector> stats; const size_t associativity; }; /** * Least frequently used policy * * Keeps statistics of access count for each associativity index. * Resets statistics when set(row) is evicted. */ class CachePolicyLFU final : public CachePolicy { public: /** * @param associativity degree of assiciaivity * @param set_count number of blocks / rows is way (or sets in * cache) */ CachePolicyLFU(size_t associativity, size_t set_count); [[nodiscard]] size_t select_way_to_evict(size_t row) const final; void update_stats(size_t way, size_t row, bool is_valid) final; private: std::vector> stats; }; class CachePolicyRAND final : public CachePolicy { public: /** * @param associativity degree of associativity */ explicit CachePolicyRAND(size_t associativity); [[nodiscard]] size_t select_way_to_evict(size_t row) const final; void update_stats(size_t way, size_t row, bool is_valid) final; private: size_t associativity; }; /** * Pseudo Last recently used policy * * Hardware-Friendly LRU Implementation */ class CachePolicyPLRU final : public CachePolicy { public: /** * @param associativity degree of assiciaivity * @param set_count number of blocks / rows in a way (or sets in * cache) */ CachePolicyPLRU(size_t associativity, size_t set_count); [[nodiscard]] size_t select_way_to_evict(size_t row) const final; void update_stats(size_t way, size_t row, bool is_valid) final; private: /** * Pointer to Least Recently Used Block */ std::vector> plru_ptr; const size_t associativity; const size_t associativityCLog2; }; /** * Not Most Recently Used * * Select Randomly from Not Most * Recently Used Blocks */ class CachePolicyNMRU final : public CachePolicy { public: /** * @param associativity degree of assiciaivity * @param set_count number of blocks / rows in a way (or sets in * cache) */ CachePolicyNMRU(size_t associativity, size_t set_count); [[nodiscard]] size_t select_way_to_evict(size_t row) const final; void update_stats(size_t way, size_t row, bool is_valid) final; private: /** * Pointer to Most Recently Used Block */ std::vector mru_ptr; const size_t associativity; }; } // namespace machine #endif // CACHE_POLICY_H ================================================ FILE: src/machine/memory/cache/cache_types.h ================================================ #ifndef CACHE_TYPES_H #define CACHE_TYPES_H #include namespace machine { /** * Determiners location of address in single way of cache. This mean, where * given addresses should be stored, if present. */ struct CacheLocation { uint64_t row; uint64_t col; uint64_t tag; uint64_t byte; }; /** * Single cache line. Appropriate cache block is stored in `data`. */ struct CacheLine { bool valid, dirty; uint64_t tag; std::vector data; }; /** * This is preferred over bool (write = true|false) for better readability. */ enum AccessType { READ, WRITE }; inline const char *to_string(AccessType a) { switch (a) { case READ: return "READ"; case WRITE: return "WRITE"; } } } // namespace machine #endif // CACHE_TYPES_H ================================================ FILE: src/machine/memory/frontend_memory.cpp ================================================ #include "memory/frontend_memory.h" #include "common/endian.h" #include "tlb/tlb.h" namespace machine { bool FrontendMemory::write_u8(AddressWithMode address, uint8_t value, AccessEffects type) { return write_generic(address, value, type); } bool FrontendMemory::write_u16(AddressWithMode address, uint16_t value, AccessEffects type) { return write_generic(address, value, type); } bool FrontendMemory::write_u32(AddressWithMode address, uint32_t value, AccessEffects type) { return write_generic(address, value, type); } bool FrontendMemory::write_u64(AddressWithMode address, uint64_t value, AccessEffects type) { return write_generic(address, value, type); } uint8_t FrontendMemory::read_u8(AddressWithMode address, AccessEffects type) const { return read_generic(address, type); } uint16_t FrontendMemory::read_u16(AddressWithMode address, AccessEffects type) const { return read_generic(address, type); } uint32_t FrontendMemory::read_u32(AddressWithMode address, AccessEffects type) const { return read_generic(address, type); } uint64_t FrontendMemory::read_u64(AddressWithMode address, AccessEffects type) const { return read_generic(address, type); } void FrontendMemory::write_ctl(enum AccessControl ctl, AddressWithMode offset, RegisterValue value) { switch (ctl) { case AC_NONE: { break; } case AC_I8: case AC_U8: { write_u8(offset, value.as_u8()); break; } case AC_I16: case AC_U16: { write_u16(offset, value.as_u16()); break; } case AC_I32: case AC_U32: { write_u32(offset, value.as_u32()); break; } case AC_I64: case AC_U64: { write_u64(offset, value.as_u64()); break; } default: { throw SIMULATOR_EXCEPTION( UnknownMemoryControl, "Trying to write to memory with unknown ctl", QString::number(ctl)); } } } RegisterValue FrontendMemory::read_ctl(enum AccessControl ctl, AddressWithMode address) const { switch (ctl) { case AC_NONE: return 0; case AC_I8: return (int8_t)read_u8(address); case AC_U8: return read_u8(address); case AC_I16: return (int16_t)read_u16(address); case AC_U16: return read_u16(address); case AC_I32: return (int32_t)read_u32(address); case AC_U32: return read_u32(address); case AC_I64: return (int64_t)read_u64(address); case AC_U64: return read_u64(address); default: { throw SIMULATOR_EXCEPTION( UnknownMemoryControl, "Trying to read from memory with unknown ctl", QString::number(ctl)); } } } void FrontendMemory::sync() {} LocationStatus FrontendMemory::location_status(Address address) const { (void)address; return LOCSTAT_NONE; } template T FrontendMemory::read_generic(AddressWithMode address, AccessEffects type) const { T value; read(&value, address, sizeof(T), { .type = type }); // When cross-simulating (BIG simulator on LITTLE host machine and vice // versa) data needs to be swapped before writing to memory and after // reading from memory to achieve correct results of misaligned reads. See // bellow. // // Example (4 byte write and 4 byte read offseted by 2 bytes): // // BIG on LITTLE // REGISTER: 12 34 56 78 // PRE-SWAP: 78 56 34 12 (still in register) // NATIVE ENDIAN MEM: 12 34 56 78 00 00 (native is LITTLE) // READ IN MEM: 56 78 00 00 // REGISTER: 00 00 78 56 // POST-SWAP: 56 78 00 00 (correct) // // LITTLE on BIG // REGISTER: 12 34 56 78 // PRE-SWAP: 78 56 34 12 (still in register) // NATIVE ENDIAN MEM: 78 56 34 12 00 00 (native is BIG) // READ IN MEM: 34 12 00 00 // REGISTER: 34 12 00 00 // POST-SWAP: 00 00 12 34 (correct) // return byteswap_if(value, this->simulated_machine_endian != NATIVE_ENDIAN); } template bool FrontendMemory::write_generic(AddressWithMode address, const T value, AccessEffects type) { // See example in read_generic for byteswap explanation. const T swapped_value = byteswap_if(value, this->simulated_machine_endian != NATIVE_ENDIAN); return write(address, &swapped_value, sizeof(T), { .type = type }).changed; } FrontendMemory::FrontendMemory(Endian simulated_endian) : simulated_machine_endian(simulated_endian) {} } // namespace machine ================================================ FILE: src/machine/memory/frontend_memory.h ================================================ #ifndef FRONTEND_MEMORY_H #define FRONTEND_MEMORY_H #include "common/endian.h" #include "csr/address.h" #include "machinedefs.h" #include "memory/address.h" #include "memory/memory_utils.h" #include "register_value.h" #include "simulator_exception.h" #include #include #include "address_with_mode.h" // Shortcut for enum class values, type is obvious from context. using ae = machine::AccessEffects; namespace machine { /** * # What is frontend memory * * The core is the entry point to the whole simulated machine. Therefore the * reworked concepts can be best understood from its point of view on the memory * subsystem. Every core configuration has three mandatory memory components. * Two of them are the entry points for memory operations for program and data, * respectively. The entry point starts a chain of memory components of * arbitrary length, each backed by the next one. A component that receives a * request either resolves it or invokes its lower component. For the core, the * chain is invisible, and it only interfaces with the top-level component. The * chain is terminated by a memory data bus. Data bus for data memory is the * third component I have mentioned. All of the above components are instances * of the frontend memory. Frontend memory has three characteristics. Except for * the memory bus, each frontend component must have a frontend component * backing it. The memory bus is backed by zero or more backed devices * (explained later). All addresses used in the frontend are full memory * address. By the word "full", I mean the numerical value observable by a C * program (up to address translation), in contrast to a local relative offset. * The Address datatype is used to store it and pass as an argument. * Finally, frontend memory instances are likely those found on the CPU chip * itself (caches, TLB...). */ class FrontendMemory : public QObject { Q_OBJECT public: /** * It is necessary to know the endian of the whole simulation in the * entrypoint on the memory subsystem, which might be any instance of * frontend memory. * * @param simulated_endian endian of the simulated CPU/memory subsystem */ explicit FrontendMemory(Endian simulated_endian); bool write_u8(AddressWithMode address, uint8_t value, AccessEffects type = ae::REGULAR); bool write_u16(AddressWithMode address, uint16_t value, AccessEffects type = ae::REGULAR); bool write_u32(AddressWithMode address, uint32_t value, AccessEffects type = ae::REGULAR); bool write_u64(AddressWithMode address, uint64_t value, AccessEffects type = ae::REGULAR); [[nodiscard]] uint8_t read_u8(AddressWithMode address, AccessEffects type = ae::REGULAR) const; [[nodiscard]] uint16_t read_u16(AddressWithMode address, AccessEffects type = ae::REGULAR) const; [[nodiscard]] uint32_t read_u32(AddressWithMode address, AccessEffects type = ae::REGULAR) const; [[nodiscard]] uint64_t read_u64(AddressWithMode address, AccessEffects type = ae::REGULAR) const; /** * Store with size specified by the CPU control unit. * * This is for CPU core only and the AccessEffects type is implicitly * REGULAR. * @param control_signal CPU control unit signal */ void write_ctl(AccessControl control_signal, AddressWithMode destination, RegisterValue value); /** * Read with size specified by the CPU control unit. * * This is for CPU core only and the AccessEffects type is implicitly * ae::REGULAR. * @param control_signal CPU control unit signal */ [[nodiscard]] RegisterValue read_ctl(enum AccessControl control_signal, AddressWithMode source) const; virtual void sync(); [[nodiscard]] virtual LocationStatus location_status(Address address) const; [[nodiscard]] virtual uint32_t get_change_counter() const = 0; /** * Write byte sequence to memory * * @param source pointer to array of bytes to be copied * @param destination emulated address of destination to write to * @param size number of bytes to be written * @return true when memory before and after write differs */ virtual WriteResult write(AddressWithMode destination, const void *source, size_t size, WriteOptions options) = 0; /** * Read sequence of bytes from memory * * @param source emulated address of data to be read * @param destination pointer to destination buffer * @param size number of bytes to be read * @param options additional option like debug mode, see type * definition */ virtual ReadResult read(void *destination, AddressWithMode source, size_t size, ReadOptions options) const = 0; /** * Endian of the simulated CPU/memory system. * * Used when frontend_memory subclass instance is used as entrypoint to the * memory system (e.g. `read_XX` and `write_XX` functions.). * @see `convert_endian` in `memory/endian.h`. */ const Endian simulated_machine_endian; signals: /** * Signal used to propagate a change up through the hierarchy. */ void external_change_notify( const FrontendMemory *issuing_memory, Address start_addr, Address last_addr, AccessEffects type) const; private: /** * Read any type from memory * * This function was introduced to make stupid errors * like mismatched type and and its size impossible. * It provides nicer interface than `read`. * by eliminating the need for buffer. * * @tparam T type value should be read as * @param address emulated address to read from * @param type read by visualization etc (type ae::INTERNAL) should * not cause certain effects (counter increments...) * @return requested data with type T */ template T read_generic(AddressWithMode address, AccessEffects type) const; /** * Write to any type from memory * * This function was introduced to make stupid errors like mismatched * type and and its size impossible. It provides nicer interface than * `write` by eliminating the need for buffer. * * @tparam T type of value to be written * @param address emulated address to write to * @param value value of type T to be written * @param type read by visualization etc (type ae::INTERNAL). * should not cause certain effects (counter increments...) * @return true when memory before and after write differs */ template bool write_generic(AddressWithMode address, T value, AccessEffects type); }; } // namespace machine #endif // FRONTEND_MEMORY_H ================================================ FILE: src/machine/memory/memory_bus.cpp ================================================ #include "memory/memory_bus.h" #include "common/endian.h" #include "memory/memory_utils.h" using namespace machine; MemoryDataBus::MemoryDataBus(Endian simulated_endian) : FrontendMemory(simulated_endian) {}; MemoryDataBus::~MemoryDataBus() { ranges_by_addr.clear(); // No stored values are owned. auto iter = ranges_by_device.begin(); while (iter != ranges_by_device.end()) { const RangeDesc *range = iter.value(); iter = ranges_by_device.erase(iter); // Advances the iterator. if (range->owns_device) { delete range->device; } delete range; } } WriteResult MemoryDataBus::write(AddressWithMode destination, const void *source, size_t size, WriteOptions options) { return repeat_access_until_completed( destination, source, size, options, [this](Address dst, const void *src, size_t s, WriteOptions opt) -> WriteResult { return write_single(dst, src, s, opt); }); } WriteResult MemoryDataBus::write_single( Address destination, const void *source, size_t size, WriteOptions options) { const RangeDesc *range = find_range(Address(destination)); if (range == nullptr) { // Write to unused address range - no devices it present. // This could produce a fault but for simplicity, we will // just ignore the write. return (WriteResult) { .n_bytes = 0, .changed = false }; } WriteResult result = range->device->write(destination - range->start_addr, source, size, options); if (result.changed) { change_counter++; } return result; } ReadResult MemoryDataBus::read(void *destination, AddressWithMode source, size_t size, ReadOptions options) const { return repeat_access_until_completed( destination, source, size, options, [this](void *dst, Address src, size_t s, ReadOptions opt) -> ReadResult { return read_single(dst, src, s, opt); }); } ReadResult MemoryDataBus::read_single( void *destination, Address source, size_t size, ReadOptions options) const { const RangeDesc *p_range = find_range(Address(source)); if (p_range == nullptr) { // Write to unused address range, no devices it present. // This could produce a fault but for simplicity, we will // consider unused ranges to be constantly zero. memset(destination, 0, size); return (ReadResult) { .n_bytes = size }; } return p_range->device->read(destination, source - p_range->start_addr, size, options); } uint32_t MemoryDataBus::get_change_counter() const { return change_counter; } enum LocationStatus MemoryDataBus::location_status(Address address) const { const RangeDesc *range = find_range(address); if (range == nullptr) { return LOCSTAT_ILLEGAL; } return range->device->location_status(address - range->start_addr); } const MemoryDataBus::RangeDesc *MemoryDataBus::find_range(Address address) const { // lowerBound finds range what has highest key (which is range->last_addr) // less then or equal to address. // See comment in insert_device_to_range for description, why this works. auto iter = ranges_by_addr.lowerBound(address); if (iter == ranges_by_addr.end()) { return nullptr; } const RangeDesc *range = iter.value(); if (address >= range->start_addr && address <= range->last_addr) { return range; } return nullptr; } bool MemoryDataBus::insert_device_to_range( BackendMemory *device, Address start_addr, Address last_addr, bool move_ownership) { auto iter = ranges_by_addr.lowerBound(start_addr); if (iter != ranges_by_addr.end() && iter.value()->overlaps(start_addr, last_addr)) { // Some part of requested range in already taken. return false; } auto *range = new RangeDesc(device, start_addr, last_addr, move_ownership); // Why are we using last address as key? // // QMap can return greatest lower key (lowerBound), so by indexing by last // address we can simply search any address within range. If searched // address is in given range, it is larger the previous range last address // and smaller or equal than the last address of its. This way we find the // last address of desired range in QMap red black tree and retrieve the // rang. Finally we just make sure, that the found range contains the // searched address for case that range is not present. ranges_by_addr.insert(last_addr, range); ranges_by_device.insert(device, range); connect( device, &BackendMemory::external_backend_change_notify, this, &MemoryDataBus::range_backend_external_change); return true; } bool MemoryDataBus::remove_device(BackendMemory *device) { const RangeDesc *range = ranges_by_device.take(device); if (range == nullptr) { return false; // Device not present. } ranges_by_addr.remove(range->last_addr); if (range->owns_device) { delete range->device; } delete range; return true; } void MemoryDataBus::clean_range(Address start_addr, Address last_addr) { for (auto iter = ranges_by_addr.lowerBound(start_addr); iter != ranges_by_addr.end(); iter++) { const RangeDesc *range = iter.value(); if (range->start_addr <= last_addr) { remove_device(range->device); } else { break; } } } void MemoryDataBus::range_backend_external_change( const BackendMemory *device, Offset start_offset, Offset last_offset, AccessEffects type) { change_counter++; // We only use device here for lookup, so const_cast is safe as find takes // it by const reference . for (auto i = ranges_by_device.find(const_cast(device)); i != ranges_by_device.end(); i++) { const RangeDesc *range = i.value(); emit external_change_notify( this, range->start_addr + start_offset, std::max(range->start_addr + last_offset, range->last_addr), type); } } MemoryDataBus::RangeDesc::RangeDesc( BackendMemory *device, Address start_addr, Address last_addr, bool owns_device) : device(device) , start_addr(start_addr) , last_addr(last_addr) , owns_device(owns_device) {} bool MemoryDataBus::RangeDesc::contains(Address address) const { return start_addr <= address && address <= last_addr; } bool MemoryDataBus::RangeDesc::overlaps(Address start, Address last) const { return contains(start) || contains(last); } TrivialBus::TrivialBus(BackendMemory *backend_memory) : FrontendMemory(backend_memory->simulated_machine_endian) , device(backend_memory) {} WriteResult TrivialBus::write(AddressWithMode destination, const void *source, size_t size, WriteOptions options) { change_counter += 1; // Counter is mandatory by the frontend interface. return device->write(destination.get_raw(), source, size, options); } ReadResult TrivialBus::read(void *destination, AddressWithMode source, size_t size, ReadOptions options) const { return device->read(destination, source.get_raw(), size, options); } uint32_t TrivialBus::get_change_counter() const { return change_counter; } ================================================ FILE: src/machine/memory/memory_bus.h ================================================ #ifndef MEMORY_BUS_H #define MEMORY_BUS_H #include "common/endian.h" #include "machinedefs.h" #include "memory/backend/backend_memory.h" #include "memory/frontend_memory.h" #include "simulator_exception.h" #include "utils.h" #include #include #include namespace machine { /** * Memory bus serves as last level of frontend memory and interconnects it with * backend memory devices, that are subscribed to given address range. * * Simulated core always has exactly one bus. This is necessary to access it * (e.g. from syscall) to map new devices. Backend memory device simulation * classes are implemented in `memory/backend`. For testing purposes, * `TrivialBus` is provided to wrap backend memory device into a minimal * frontend interface. Used mapping is always one to one with identity address * resolution. TrivialBus does not support external changes. * Backend memory devices subscribe to the bus via range descriptions (see * `RangeDecs`). Frontend address (`Address` type) is here converted using the * range descriptions to relative offset within the given backend memory device. * Downstream (frontend -> backend) communication is performed directly and * upstream communication is done via "external_change" signals. */ class MemoryDataBus : public FrontendMemory { Q_OBJECT public: /** * @param simulated_endian endian of the simulated CPU/memory system */ explicit MemoryDataBus(Endian simulated_endian); ~MemoryDataBus() override; /** * Write method that repeats write single (for each affected range) until * whole size is written. * * @see MemoryDataBus::write_single */ WriteResult write(AddressWithMode destination, const void *source, size_t size, WriteOptions options) override; /** * Read method that repeats write single (for each affected range) until * whole size is read. * * @see MemoryDataBus:read_single */ ReadResult read(void *destination, AddressWithMode source, size_t size, ReadOptions options) const override; /** * Number of writes and external changes recorded. */ uint32_t get_change_counter() const override; /** * Connect a backend device to the bus for given address range. * * @param device simulated backend memory device object * @param start_addr start of address range, where the device will be * accessible * @param last_addr end of range, devices occupies * @param move_ownership if true, bus will be responsible for for * device destruction * TODO: consider replace with a smartpointer * @return result of connection, it will fail if range is * already occupied */ bool insert_device_to_range( BackendMemory *device, Address start_addr, Address last_addr, bool move_ownership); /** * Disconnect a device by a pointer to it. * Owned device will be deallocated. * * @param device simulated backend memory device object * @return result of disconnection, false if device not present */ bool remove_device(BackendMemory *device); /** * Disconnect all devices in given range. * * * Owned devices will be deallocated. * * @param start_addr * @param last_addr */ void clean_range(Address start_addr, Address last_addr); enum LocationStatus location_status(Address address) const override; private slots: /** * Receive external changes in underlying memory devices. */ void range_backend_external_change( const BackendMemory *device, Offset start_offset, Offset last_offset, AccessEffects type); private: class RangeDesc; // See declaration bellow; QMultiMap ranges_by_device; /* * Ranges by address noes not own any value it hold. It can be erased at * once. */ QMap ranges_by_addr; mutable uint32_t change_counter = 0; /** * Helper to write into single range. Used by `write`. * * Write spanning multiple ranges will succeed partially and return size, * that was written. * API corresponds to `BackendMemory` interface method `write`. */ WriteResult write_single(Address destination, const void *source, size_t size, WriteOptions options); /** * Helper to read from single range. Used by `read` from Backend memory * interface. * * Read spanning multiple ranges will succeed partially and return * size, that was written. * API corresponds to `BackendMemory` interface method `read`. */ ReadResult read_single(void *destination, Address source, size_t size, ReadOptions options) const; /** * Get range (or nullptr) for arbitrary address (not just start or last). */ const MemoryDataBus::RangeDesc *find_range(Address address) const; }; /** * Describes a address range occupied by a single backend memory device. */ class MemoryDataBus::RangeDesc { public: RangeDesc(BackendMemory *device, Address start_addr, Address last_addr, bool owns_device); /** * Tells, whether given address belongs to this range. */ [[nodiscard]] bool contains(Address address) const; /* * Tells, whether this range (of the RangeDesc) overlaps with supplied * range. */ [[nodiscard]] bool overlaps(Address start, Address last) const; BackendMemory *const device; // TODO consider a shared pointer const Address start_addr; const Address last_addr; const bool owns_device; }; /** * Minimal frontend-backend wrapper. * * Redirects memory requests from frontend to backend one-to-one. * Used in test without cache (core only accepts `FrontendMemory`). * * Do not use in non-test code! */ class TrivialBus final : public FrontendMemory { public: explicit TrivialBus(BackendMemory *backend_memory); WriteResult write(AddressWithMode destination, const void *source, size_t size, WriteOptions options) override; ReadResult read(void *destination, AddressWithMode source, size_t size, ReadOptions options) const override; uint32_t get_change_counter() const override; private: BackendMemory *const device; mutable uint32_t change_counter = 0; }; } // namespace machine #endif // MEMORY_BUS_H ================================================ FILE: src/machine/memory/memory_utils.h ================================================ #ifndef MEMORY_UTILS_H #define MEMORY_UTILS_H #include "common/endian.h" #include "utils.h" #include #include #include #include namespace machine { /** * Determines what effects should memory access cause. */ enum class AccessEffects { REGULAR, //> All (memory, simulation counters, simulation flags, allocation // on read miss (write allocation is necessary)). For accessed // requested by simulated program. INTERNAL, //> Only memory. Internal access performed for visualization, // control and debugging. EXTERNAL_ASYNC, //> Used for DMA. }; /** * Additional options for read operation between memory layers * * The purpose for this struct is to make the API easily * extensible. */ struct ReadOptions { AccessEffects type; }; /** * Additional options for write operation between memory layers * * The purpose for this struct is to make the API easily * extensible. */ struct WriteOptions { AccessEffects type; }; struct ReadResult { /** * Number of bytes successfully read. * * May be lower than requested size in case partial success * like page fault. */ size_t n_bytes = 0; inline ReadResult operator+(const ReadResult &other) const { return { this->n_bytes + other.n_bytes, }; } inline void operator+=(const ReadResult &other) { this->n_bytes += other.n_bytes; } }; struct WriteResult { /** * Number of bytes successfully read. * * May be lower than requested size in case partial success * like page fault. */ size_t n_bytes = 0; /** * Indicates whether write caused change in memory. * Used to reduce UI redraws. */ bool changed = false; inline WriteResult operator+(const WriteResult &other) const { return { this->n_bytes + other.n_bytes, this->changed || other.changed, }; } inline void operator+=(const WriteResult &other) { this->n_bytes += other.n_bytes; this->changed |= other.changed; } }; /** * Perform n-byte read into periphery that only supports u32 access. * * When converting n-byte memory access into aligned series of discrete * accesses each by u32. * * Example: * Periphery supports write by uint32_t. Access of size 4 targets in the middle * of a uint32_t register. Then this function will return 2, which means that * last 2 bytes of the retrieved data will be used (written to register). * * @tparam STORAGE_TYPE a type periphery supports for access * @param ptr pointer-like value used for access * @return size to be used from aligned access */ template inline void partial_access_parameters(size_t &data_offset, size_t &partial_size, uintptr_t ptr, size_t size) { data_offset = ptr % sizeof(STORAGE_TYPE); partial_size = sizeof(STORAGE_TYPE); partial_size -= data_offset; if (partial_size > size) partial_size = size; } /** * Perform n-byte read into periphery that only supports u32 access. * * @tparam FUNC function :: size_t -> uint32_t * @param src data offset in periphery * @param dst pointer to write to * @param size bytes to read * @param data_getter function object which return u32 data for given */ template inline ReadResult read_by_u32(void *dst, size_t src, size_t size, FUNC data_getter) { size_t current_src = src; byte *current_dst = static_cast(dst); size_t remaining_size = size; do { // For simplicity, this is duplicated in write_by_u32. size_t data_offset = current_src % sizeof(uint32_t); size_t partial_size = std::min(sizeof(uint32_t) - data_offset, remaining_size); uint32_t data = data_getter(current_src & ~3u); memcpy(current_dst, (byte *)&data + data_offset, partial_size); remaining_size -= partial_size; current_src += partial_size; current_dst += partial_size; } while (remaining_size > 0); return { .n_bytes = size }; } template inline ReadResult read_by_u16(void *dst, size_t src, size_t size, FUNC data_getter) { size_t current_src = src; byte *current_dst = static_cast(dst); size_t remaining_size = size; do { // For simplicity, this is duplicated in write_by_u16. size_t data_offset = current_src % sizeof(uint16_t); size_t partial_size = std::min(sizeof(uint16_t) - data_offset, remaining_size); uint16_t data = data_getter(current_src & ~1u); memcpy(current_dst, (byte *)&data + data_offset, partial_size); remaining_size -= partial_size; current_src += partial_size; current_dst += partial_size; } while (remaining_size > 0); return { .n_bytes = size }; } template inline ReadResult read_by_u64(void *dst, size_t src, size_t size, FUNC data_getter) { size_t current_src = src; byte *current_dst = static_cast(dst); size_t remaining_size = size; do { // For simplicity, this is duplicated in write_by_u64. size_t data_offset = current_src % sizeof(uint64_t); size_t partial_size = std::min(sizeof(uint64_t) - data_offset, remaining_size); uint64_t data = data_getter(current_src & ~7u); memcpy(current_dst, (byte *)&data + data_offset, partial_size); remaining_size -= partial_size; current_src += partial_size; current_dst += partial_size; } while (remaining_size > 0); return { .n_bytes = size }; } /** * Perform n-byte write into periphery that only supports u32 access. * * @see read_by_u32 * * @tparam FUNC1 function :: size_t -> uint32_t * @tparam FUNC2 function :: size_t, uint32_t -> bool * @param src data source * @param dst offset in periphery * @param size n bytes * @param data_getter function object which return u32 data for given * offset * @param data_setter function object which writes an u32 to givem offset * @return true if write caused a change */ template inline WriteResult write_by_u32(size_t dst, const void *src, size_t size, FUNC1 data_getter, FUNC2 data_setter) { const byte *current_src = static_cast(src); size_t current_dst = dst; size_t remaining_size = size; bool changed = false; do { // For simplicity, this is duplicated in read_by_u32. size_t data_offset = current_dst % sizeof(uint32_t); size_t partial_size = std::min(sizeof(uint32_t) - data_offset, remaining_size); uint32_t data = 0; if (partial_size < sizeof(data)) data = data_getter(current_dst & ~3u); memcpy((byte *)&data + data_offset, current_src, partial_size); changed |= data_setter(current_dst & ~3u, data); remaining_size -= partial_size; current_src += partial_size; current_dst += partial_size; } while (remaining_size > 0); return { .n_bytes = size, .changed = changed }; } template inline WriteResult write_by_u16(size_t dst, const void *src, size_t size, FUNC1 data_getter, FUNC2 data_setter) { const byte *current_src = static_cast(src); size_t current_dst = dst; size_t remaining_size = size; bool changed = false; do { // For simplicity, this is duplicated in read_by_u16. size_t data_offset = current_dst % sizeof(uint16_t); size_t partial_size = std::min(sizeof(uint16_t) - data_offset, remaining_size); uint16_t data = 0; if (partial_size < sizeof(data)) data = data_getter(current_dst & ~1u); memcpy((byte *)&data + data_offset, current_src, partial_size); changed |= data_setter(current_dst & ~1u, data); remaining_size -= partial_size; current_src += partial_size; current_dst += partial_size; } while (remaining_size > 0); return { .n_bytes = size, .changed = changed }; } template inline WriteResult write_by_u64(size_t dst, const void *src, size_t size, FUNC1 data_getter, FUNC2 data_setter) { const byte *current_src = static_cast(src); size_t current_dst = dst; size_t remaining_size = size; bool changed = false; do { // For simplicity, this is duplicated in read_by_u64. size_t data_offset = current_dst % sizeof(uint64_t); size_t partial_size = std::min(sizeof(uint64_t) - data_offset, remaining_size); uint64_t data = 0; if (partial_size < sizeof(data)) data = data_getter(current_dst & ~7u); memcpy((byte *)&data + data_offset, current_src, partial_size); changed |= data_setter(current_dst & ~7u, data); remaining_size -= partial_size; current_src += partial_size; current_dst += partial_size; } while (remaining_size > 0); return { .n_bytes = size, .changed = changed }; } /** * In case that underlying memory representation is fragmented, multiple * invocations of the same code might be needed. This is a common case with the * n-byte memory access API and therefore this function has been introduce to * minimize code duplication. * * @tparam FUNC function with same signature as read or write * @tparam SRC_TYPE corresponding type in read or write * @tparam DST_TYPE corresponding type in read or write * @tparam OPTIONS_TYPE ReadOptions or WriteOptions * @tparam RESULT_TYPE ReadResult or WriteResult * @param src same arg as in read/write * @param dst same arg as in read/write * @param size same arg as in read/write * @param options same arg as in read/write * @param function lambda to perform individual accesses * @return number of bytes obtained, == size if fully successful */ template inline RESULT_TYPE repeat_access_until_completed( DST_TYPE dst, SRC_TYPE src, size_t size, OPTIONS_TYPE options, FUNC function) { size_t remaining_size = size; auto current_src = (uint64_t)(src); auto current_dst = (uint64_t)(dst); RESULT_TYPE total_result {}; // do-while is preferred, because this loop is most likely to be executed only once. Empty // access is not common and does not need to be optimized. do { RESULT_TYPE result = function((DST_TYPE)(current_dst), (SRC_TYPE)(current_src), remaining_size, options); if (result.n_bytes == 0) break; total_result += result; current_src += result.n_bytes; current_dst += result.n_bytes; remaining_size -= result.n_bytes; } while (remaining_size > 0); return total_result; } /** * Helper function for memories, that do not support function like read_u32. * It is used in tests. * This is a generic version followed by named instantiations. */ template T memory_read(MEM_T *mem, ADDR_T address) { T buffer; mem->read(&buffer, address, sizeof(T), {}); return byteswap_if(buffer, mem->simulated_machine_endian != NATIVE_ENDIAN); } template uint8_t memory_read_u8(MEM_T *mem, ADDR_T address) { return memory_read(mem, address); } template uint16_t memory_read_u16(MEM_T *mem, ADDR_T address) { return memory_read(mem, address); } template uint32_t memory_read_u32(MEM_T *mem, ADDR_T address) { return memory_read(mem, address); } template uint64_t memory_read_u64(MEM_T *mem, ADDR_T address) { return memory_read(mem, address); } /** * Helper function for memories, that do not support function like read_u32. * It is used in tests. * This is a generic version followed by named instantiations. */ template void memory_write(MEM_T *mem, ADDR_T address, T value) { const T swapped_value = byteswap_if(value, mem->simulated_machine_endian != NATIVE_ENDIAN); mem->write(address, &swapped_value, sizeof(T), {}); } template void memory_write_u8(MEM_T *mem, ADDR_T address, uint8_t value) { memory_write(mem, address, value); } template void memory_write_u16(MEM_T *mem, ADDR_T address, uint16_t value) { memory_write(mem, address, value); } template void memory_write_u32(MEM_T *mem, ADDR_T address, uint32_t value) { memory_write(mem, address, value); } template void memory_write_u64(MEM_T *mem, ADDR_T address, uint64_t value) { memory_write(mem, address, value); } } // namespace machine #endif // MEMORY_UTILS_H ================================================ FILE: src/machine/memory/tlb/tlb.cpp ================================================ #include "tlb.h" #include "csr/controlstate.h" #include "machine.h" #include "memory/virtual/page_table_walker.h" #include "memory/virtual/sv32.h" LOG_CATEGORY("machine.TLB"); namespace machine { TLB::TLB( FrontendMemory *memory, TLBType type_, const TLBConfig *config, bool vm_enabled, uint32_t memory_access_penalty_r, uint32_t memory_access_penalty_w, uint32_t memory_access_penalty_b, bool memory_access_enable_b) : FrontendMemory(memory->simulated_machine_endian) , mem(memory) , type(type_) , tlb_config(config) , vm_enabled(vm_enabled) , access_pen_r(memory_access_penalty_r) , access_pen_w(memory_access_penalty_w) , access_pen_b(memory_access_penalty_b) , access_ena_b(memory_access_enable_b) { num_sets_ = tlb_config.get_tlb_num_sets(); associativity_ = tlb_config.get_tlb_associativity(); auto pol = tlb_config.get_tlb_replacement_policy(); repl_policy = make_tlb_policy(static_cast(pol), associativity_, num_sets_); table.assign(num_sets_, std::vector(associativity_)); const char *tag = (type == PROGRAM ? "I" : "D"); LOG("TLB[%s] constructed; sets=%zu way=%zu", tag, num_sets_, associativity_); emit hit_update(hit_count_); emit miss_update(miss_count_); emit memory_reads_update(mem_reads); emit memory_writes_update(mem_writes); update_all_statistics(); } void TLB::on_csr_write(size_t internal_id, RegisterValue val) { if (internal_id != CSR::Id::SATP) return; current_satp_raw = static_cast(val.as_u64()); for (size_t s = 0; s < num_sets_; s++) { for (size_t w = 0; w < associativity_; w++) { auto &e = table[s][w]; if (e.valid) { uint16_t old_asid = e.asid; uint64_t old_vpn = e.vpn; e.valid = false; emit tlb_update( static_cast(w), static_cast(s), false, old_asid, old_vpn, 0ull, false); } } } LOG("TLB: SATP changed → flushed all; new SATP=0x%08x", current_satp_raw); update_all_statistics(); } void TLB::flush_single(VirtualAddress va, uint16_t asid) { uint64_t vpn = va.get_raw() >> 12; size_t s = set_index(vpn); for (size_t w = 0; w < associativity_; w++) { auto &e = table[s][w]; if (e.valid && e.vpn == vpn && e.asid == asid) { uint16_t old_asid = e.asid; uint64_t old_vpn = e.vpn; e.valid = false; const char *tag = (type == PROGRAM ? "I" : "D"); LOG("TLB[%s]: flushed VA=0x%llx ASID=%u", tag, (unsigned long long)va.get_raw(), asid); emit tlb_update( static_cast(w), static_cast(s), false, old_asid, old_vpn, 0ull, false); update_all_statistics(); } } } void TLB::flush() { if (num_sets_ == 0 || associativity_ == 0) return; const char *tag = (type == PROGRAM ? "I" : "D"); for (size_t s = 0; s < num_sets_; s++) { for (size_t w = 0; w < associativity_; w++) { auto &e = table[s][w]; if (e.valid) { uint16_t old_asid = e.asid; uint64_t old_vpn = e.vpn; e.valid = false; emit tlb_update( static_cast(w), static_cast(s), false, old_asid, old_vpn, 0ull, false); } } } change_counter++; LOG("TLB[%s]: flushed all entries", tag); update_all_statistics(); } void TLB::sync() { flush(); } Address TLB::translate_virtual_to_physical(AddressWithMode vaddr) { uint64_t virt = vaddr.get_raw(); AccessMode mode = vaddr.access_mode(); CSR::PrivilegeLevel priv = mode.priv(); uint16_t asid = mode.asid(); bool satp_mode_on = is_mode_enabled_in_satp(current_satp_raw); bool should_translate = vm_enabled && satp_mode_on && (priv != CSR::PrivilegeLevel::MACHINE); if (!should_translate) { return vaddr; } constexpr unsigned PAGE_SHIFT = 12; constexpr uint64_t PAGE_MASK = (1ULL << PAGE_SHIFT) - 1; uint64_t off = virt & ((1ULL << PAGE_SHIFT) - 1); uint64_t vpn = virt >> PAGE_SHIFT; size_t s = set_index(vpn); const char *tag = (type == PROGRAM ? "I" : "D"); // Check TLB hit for (size_t w = 0; w < associativity_; w++) { auto &e = table[s][w]; if (e.valid && e.vpn == vpn && e.asid == asid) { repl_policy->notify_access(s, w, /*valid=*/true); uint64_t pbase = e.phys.get_raw() & ~((1ULL << PAGE_SHIFT) - 1); hit_count_++; emit hit_update(hit_count_); emit tlb_update( static_cast(w), static_cast(s), true, e.asid, e.vpn, pbase, false); update_all_statistics(); return Address { pbase + off }; } } // TLB miss -> resolve with page table walker VirtualAddress va { static_cast(virt) }; PageTableWalker walker(mem); Address resolved_pa = walker.walk(va, current_satp_raw); mem_reads += 1; emit memory_reads_update(mem_reads); // Cache the resolved mapping in the TLB uint64_t phys_base = resolved_pa.get_raw() & ~PAGE_MASK; size_t victim = repl_policy->select_way(s); auto &ent = table[s][victim]; ent.valid = true; ent.asid = asid; ent.vpn = vpn; ent.phys = Address { phys_base }; repl_policy->notify_access(s, victim, /*valid=*/true); miss_count_++; emit miss_update(miss_count_); emit tlb_update( static_cast(victim), static_cast(s), true, ent.asid, ent.vpn, phys_base, false); LOG("TLB[%s]: cached VA=0x%llx -> PA=0x%llx (ASID=%u) on miss", tag, (unsigned long long)virt, (unsigned long long)phys_base, asid); update_all_statistics(); return Address { phys_base + off }; } WriteResult TLB::write(AddressWithMode dst, const void *src, size_t sz, WriteOptions opts) { return translate_and_write(dst, src, sz, opts); } ReadResult TLB::read(void *dst, AddressWithMode src, size_t sz, ReadOptions opts) const { return const_cast(this)->translate_and_read(dst, src, sz, opts); } WriteResult TLB::translate_and_write(AddressWithMode dst, const void *src, size_t sz, WriteOptions opts) { Address pa = translate_virtual_to_physical(dst); return mem->write(pa, src, sz, opts); } ReadResult TLB::translate_and_read(void *dst, AddressWithMode src, size_t sz, ReadOptions opts) { Address pa = translate_virtual_to_physical(src); return mem->read(dst, pa, sz, opts); } bool TLB::reverse_lookup(Address paddr, VirtualAddress &out_va) const { uint64_t ppn = paddr.get_raw() >> 12; uint64_t offset = paddr.get_raw() & 0xFFF; for (size_t s = 0; s < num_sets_; s++) { for (size_t w = 0; w < associativity_; w++) { auto &e = table[s][w]; if (e.valid && (e.phys.get_raw() >> 12) == ppn) { out_va = VirtualAddress { (e.vpn << 12) | offset }; return true; } } } return false; } double TLB::get_hit_rate() const { unsigned comp = hit_count_ + miss_count_; if (comp == 0) return 0.0; return (double)hit_count_ / (double)comp * 100.0; } uint32_t TLB::get_stall_count() const { uint32_t st_cycles = mem_reads * (access_pen_r - 1) + mem_writes * (access_pen_w - 1); st_cycles += miss_count_; if (access_ena_b) { st_cycles -= burst_reads * (access_pen_r - access_pen_b) + burst_writes * (access_pen_w - access_pen_b); } return st_cycles; } const TLBConfig &TLB::get_config() const { return tlb_config; } double TLB::get_speed_improvement() const { unsigned comp = hit_count_ + miss_count_; if (comp == 0) return 100.0; uint32_t lookup_time = comp; uint32_t mem_access_time = mem_reads * access_pen_r + mem_writes * access_pen_w; if (access_ena_b) { mem_access_time -= burst_reads * (access_pen_r - access_pen_b) + burst_writes * (access_pen_w - access_pen_b); } double baseline_time = (double)comp * (double)access_pen_r; double with_tlb_time = (double)lookup_time + (double)mem_access_time; if (with_tlb_time == 0.0) return 100.0; return (baseline_time / with_tlb_time) * 100.0; } void TLB::update_all_statistics() { emit statistics_update(get_stall_count(), get_speed_improvement(), get_hit_rate()); } void TLB::reset() { for (size_t s = 0; s < num_sets_; s++) { for (size_t w = 0; w < associativity_; w++) { auto &e = table[s][w]; if (e.valid) { uint16_t old_asid = e.asid; uint64_t old_vpn = e.vpn; e.valid = false; emit tlb_update( static_cast(w), static_cast(s), false, old_asid, old_vpn, 0ull, false); } } } hit_count_ = 0; miss_count_ = 0; mem_reads = 0; mem_writes = 0; burst_reads = 0; burst_writes = 0; change_counter = 0; emit hit_update(get_hit_count()); emit miss_update(get_miss_count()); emit memory_reads_update(get_read_count()); emit memory_writes_update(get_write_count()); update_all_statistics(); } } // namespace machine ================================================ FILE: src/machine/memory/tlb/tlb.h ================================================ #ifndef TLB_H #define TLB_H #include "common/logging.h" #include "csr/address.h" #include "memory/frontend_memory.h" #include "memory/virtual/sv32.h" #include "memory/virtual/virtual_address.h" #include "tlb_policy.h" #include namespace machine { enum TLBType { PROGRAM, DATA }; class Machine; // Implements a set-associative Translation Lookaside Buffer (TLB) frontend over physical memory, // handling virtual to physical translation, flush, and replacement policy. class TLB : public FrontendMemory { Q_OBJECT public: TLB(FrontendMemory *memory, TLBType type, const TLBConfig *config, bool vm_enabled = true, uint32_t memory_access_penalty_r = 1, uint32_t memory_access_penalty_w = 1, uint32_t memory_access_penalty_b = 0, bool memory_access_enable_b = false); void on_csr_write(size_t internal_id, RegisterValue val); void flush_single(VirtualAddress va, uint16_t asid); void flush(); void sync() override; Address translate_virtual_to_physical(AddressWithMode vaddr); WriteResult write(AddressWithMode dst, const void *src, size_t sz, WriteOptions opts) override; ReadResult read(void *dst, AddressWithMode src, size_t sz, ReadOptions opts) const override; uint32_t get_change_counter() const override { return mem->get_change_counter(); } void set_replacement_policy(std::unique_ptr p) { repl_policy = std::move(p); } uint32_t root_page_table_ppn() const { return current_satp_raw & ((1u << PPN_BITS) - 1); } bool reverse_lookup(Address paddr, VirtualAddress &out_va) const; unsigned get_hit_count() const { return hit_count_; } unsigned get_miss_count() const { return miss_count_; } double get_hit_rate() const; uint32_t get_read_count() const { return mem_reads; } uint32_t get_write_count() const { return mem_writes; } uint32_t get_burst_read_count() const { return burst_reads; } uint32_t get_burst_write_count() const { return burst_writes; } uint32_t get_stall_count() const; double get_speed_improvement() const; const TLBConfig &get_config() const; void reset(); void update_all_statistics(); signals: void tlb_update( unsigned way, unsigned set, bool valid, uint16_t asid, uint64_t vpn, uint64_t phys_base, bool is_write); void hit_update(unsigned val); void miss_update(unsigned val); void statistics_update(unsigned stalled_cycles, double speed_improv, double hit_rate); void memory_reads_update(uint32_t val); void memory_writes_update(uint32_t val); private: struct Entry { bool valid = false; uint16_t asid = 0; uint64_t vpn = 0; Address phys = Address { 0 }; uint32_t lru = 0; }; FrontendMemory *mem; TLBType type; const TLBConfig tlb_config; uint32_t current_satp_raw = 0; const bool vm_enabled; size_t num_sets_; size_t associativity_; std::vector> table; std::unique_ptr repl_policy; const uint32_t access_pen_r; const uint32_t access_pen_w; const uint32_t access_pen_b; const bool access_ena_b; mutable unsigned hit_count_ = 0; mutable unsigned miss_count_ = 0; mutable uint32_t mem_reads = 0; mutable uint32_t mem_writes = 0; mutable uint32_t burst_reads = 0; mutable uint32_t burst_writes = 0; mutable uint32_t change_counter = 0; WriteResult translate_and_write(AddressWithMode dst, const void *src, size_t sz, WriteOptions opts); ReadResult translate_and_read(void *dst, AddressWithMode src, size_t sz, ReadOptions opts); inline size_t set_index(uint64_t vpn) const { return vpn & (num_sets_ - 1); } inline bool is_mode_enabled_in_satp(uint32_t satp_raw) { return (satp_raw & (1u << 31)) != 0; } }; } // namespace machine #endif // TLB_H ================================================ FILE: src/machine/memory/tlb/tlb_policy.cpp ================================================ #include "tlb_policy.h" #include #include #include #include namespace machine { TLBPolicyRAND::TLBPolicyRAND(size_t assoc) : associativity(assoc) { std::srand(1); } size_t TLBPolicyRAND::select_way(size_t) const { return std::rand() % associativity; } void TLBPolicyRAND::notify_access(size_t, size_t, bool) { /* no state */ } TLBPolicyLRU::TLBPolicyLRU(size_t assoc, size_t sets) : associativity(assoc), set_count(sets) { stats.resize(sets, std::vector(assoc)); for (auto &row : stats) { std::iota(row.begin(), row.end(), 0); } } size_t TLBPolicyLRU::select_way(size_t set) const { return stats[set][0]; } void TLBPolicyLRU::notify_access(size_t set, size_t way, bool valid) { auto &row = stats[set]; uint32_t next = way; if (valid) { for (int i = int(row.size()) - 1; i >= 0; --i) { std::swap(row[i], next); if (next == way) break; } } else { for (unsigned int &i : row) { std::swap(i, next); if (next == way) break; } } } TLBPolicyLFU::TLBPolicyLFU(size_t assoc, size_t sets) : associativity(assoc), set_count(sets) { stats.assign(sets, std::vector(assoc, 0)); } size_t TLBPolicyLFU::select_way(size_t set) const { const auto &row = stats[set]; size_t idx = 0; uint32_t minv = row[0]; for (size_t i = 1; i < row.size(); ++i) { if (row[i] == 0 || row[i] < minv) { minv = row[i]; idx = i; if (minv == 0) break; } } return idx; } void TLBPolicyLFU::notify_access(size_t set, size_t way, bool valid) { if (valid) { stats[set][way]++; } else { stats[set][way] = 0; } } TLBPolicyPLRU::TLBPolicyPLRU(size_t assoc, size_t sets) : associativity(assoc) , set_count(sets) , c_log2(std::ceil(std::log2(float(assoc)))) { size_t tree_size = (1u << c_log2) - 1; tree.assign(sets, std::vector(tree_size, 0)); } size_t TLBPolicyPLRU::select_way(size_t set) const { const auto &bits = tree[set]; size_t idx = 0; size_t node = 0; for (size_t lvl = 0; lvl < c_log2; ++lvl) { uint8_t b = bits[node]; idx = (idx << 1) | b; node = ((1u << (lvl + 1)) - 1) + idx; } return std::min(idx, associativity - 1); } void TLBPolicyPLRU::notify_access(size_t set, size_t way, bool) { auto &bits = tree[set]; size_t node = 0; for (size_t lvl = 0; lvl < c_log2; ++lvl) { uint8_t dir = (way >> (c_log2 - lvl - 1)) & 1; bits[node] = dir ? 0 : 1; node = ((1u << (lvl + 1)) - 1) + ((dir ? 1 : 0)); } } std::unique_ptr make_tlb_policy(TLBPolicyKind kind, size_t associativity, size_t set_count) { switch (kind) { case TLBPolicyKind::RAND: return std::make_unique(associativity); case TLBPolicyKind::LRU: return std::make_unique(associativity, set_count); case TLBPolicyKind::LFU: return std::make_unique(associativity, set_count); case TLBPolicyKind::PLRU: return std::make_unique(associativity, set_count); } return std::make_unique(associativity, set_count); } } // namespace machine ================================================ FILE: src/machine/memory/tlb/tlb_policy.h ================================================ #ifndef TLB_POLICY_H #define TLB_POLICY_H #include #include #include #include namespace machine { // Abstract TLB replacement policy interface & implementations (RAND, LRU, LFU, PLRU) for // set-associative tables. class TLBPolicy { public: virtual size_t select_way(size_t set) const = 0; virtual void notify_access(size_t set, size_t way, bool valid) = 0; virtual ~TLBPolicy() = default; }; class TLBPolicyRAND final : public TLBPolicy { size_t associativity; public: explicit TLBPolicyRAND(size_t assoc); size_t select_way(size_t set) const override; void notify_access(size_t set, size_t way, bool valid) override; }; class TLBPolicyLRU final : public TLBPolicy { size_t associativity; size_t set_count; std::vector> stats; public: TLBPolicyLRU(size_t assoc, size_t sets); size_t select_way(size_t set) const override; void notify_access(size_t set, size_t way, bool valid) override; }; class TLBPolicyLFU final : public TLBPolicy { size_t associativity; size_t set_count; std::vector> stats; public: TLBPolicyLFU(size_t assoc, size_t sets); size_t select_way(size_t set) const override; void notify_access(size_t set, size_t way, bool valid) override; }; class TLBPolicyPLRU final : public TLBPolicy { size_t associativity; size_t set_count; size_t c_log2; std::vector> tree; public: TLBPolicyPLRU(size_t assoc, size_t sets); size_t select_way(size_t set) const override; void notify_access(size_t set, size_t way, bool valid) override; }; enum class TLBPolicyKind { RAND, LRU, LFU, PLRU }; std::unique_ptr make_tlb_policy(TLBPolicyKind kind, size_t associativity, size_t set_count); } // namespace machine #endif // TLB_POLICY_H ================================================ FILE: src/machine/memory/virtual/page_table_walker.cpp ================================================ #include "page_table_walker.h" #include "common/logging.h" #include "machine.h" #include "memory/backend/backend_memory.h" #include LOG_CATEGORY("machine.PTW"); namespace machine { Address PageTableWalker::walk(const VirtualAddress &va, uint32_t raw_satp) const { if (((raw_satp >> 31) & 1) == 0) return Address { va.get_raw() }; auto root_ppn = raw_satp & ((1u << 22) - 1); auto va_raw = static_cast(va.get_raw()); auto vpn1 = (va_raw >> VPN1_SHIFT) & VPN_MASK; auto vpn0 = (va_raw >> VPN0_SHIFT) & VPN_MASK; auto ppn = root_ppn; for (int lvl = 1; lvl >= 0; --lvl) { uint32_t idx = (lvl == 1 ? vpn1 : vpn0); Address pte_addr { (uint64_t(ppn) << PAGE_SHIFT) + idx * 4 }; uint32_t raw_pte; memory->read(&raw_pte, pte_addr, sizeof(raw_pte), { .type = ae::INTERNAL }); LOG("PTW: L%u PTE@0x%08" PRIx64 " = 0x%08x", lvl, pte_addr.get_raw(), raw_pte); Sv32Pte pte = Sv32Pte::from_uint(raw_pte); if (!pte.is_valid()) { throw SIMULATOR_EXCEPTION( PageFault, "PTW: page fault, leaf PTE invalid", QString::number(pte_addr.get_raw(), 16)); } if (pte.is_leaf()) { Address pa = make_phys(va_raw, pte, lvl); LOG("PTW: L%u leaf → PA=0x%08" PRIx64, lvl, pa.get_raw()); return pa; } if (pte.r() || pte.w() || pte.x()) { throw SIMULATOR_EXCEPTION( PageFault, "PTW: invalid non-leaf", QString::number(raw_pte, 16)); } ppn = unsigned(pte.ppn()); } throw SIMULATOR_EXCEPTION(PageFault, "PTW: no leaf found", ""); } } // namespace machine ================================================ FILE: src/machine/memory/virtual/page_table_walker.h ================================================ #ifndef PAGE_TABLE_WALKER_H #define PAGE_TABLE_WALKER_H #include "memory/frontend_memory.h" #include "sv32.h" #include "virtual_address.h" #include namespace machine { // Performs multi-level page-table walks (SV32) in memory to resolve a virtual address to a physical // one. class PageTableWalker { public: explicit PageTableWalker(FrontendMemory *mem) : memory(mem) {} Address walk(const VirtualAddress &va, uint32_t raw_satp) const; private: FrontendMemory *memory; }; } // namespace machine #endif // PAGE_TABLE_WALKER_H ================================================ FILE: src/machine/memory/virtual/sv32.h ================================================ #ifndef SV32_H #define SV32_H // SV32-specific definitions: page-table entry (PTE) bitfields, shifts/masks, and PTE to physical // address helpers. This header documents the SV32 layout (RISC-V 32-bit virtual memory): // - Page size: 4 KiB (PAGE_SHIFT = 12). // - Virtual page number (VPN) is split into two 10-bit indices VPN[1] and VPN[0]. // - PTE low bits encode flags and low-order info; high bits encode the physical // page number (PPN). namespace machine { static constexpr unsigned PAGE_SHIFT = 12; // Page size = 2^PAGE_SHIFT bytes. For SV32 this is 4 // KiB. static constexpr unsigned VPN_BITS = 10; // Number of bits for each VPN level in SV32: 10 bits for // VPN[1] and 10 bits for VPN[0]. // Shift values for extracting VPN parts from a virtual address: static constexpr unsigned VPN0_SHIFT = PAGE_SHIFT; // VPN0 is the low VPN field, it starts at the // page offset (PAGE_SHIFT). static constexpr unsigned VPN1_SHIFT = PAGE_SHIFT + VPN_BITS; // VPN1 is the next field above VPN0. static constexpr unsigned VPN_MASK = (1u << VPN_BITS) - 1; // Mask to extract a single VPN level (10 // bits, value 0..1023). // Number of bits available for the physical page number (PPN) in SV32 PTE. // SV32 uses 22 PPN bits (bits 10..31 of a 32-bit PTE) which permits addressing up to 4GiB of // physical memory. static constexpr unsigned PPN_BITS = 22; static constexpr unsigned PPN_MASK = (1u << PPN_BITS) - 1; static constexpr uint64_t PHYS_PPN_START = 0x200; // I have noticed that programs are loaded into // memory starting at 0x200. // Sv32Pte wraps the raw 32-bit PTE value and provides helpers to read flags and fields. // Layout (bit indices): // 0 V (valid) // 1 R (read) // 2 W (write) // 3 X (execute) // 4 U (user) // 5 G (global) // 6 A (accessed) // 7 D (dirty) // 8..9 RSW (reserved for supervisor use) // 10..31 PPN (physical page number) // // A PTE is considered a "leaf" when it grants read or execute permission (R or X). // Validation rules: PTE is valid if V==1 and (if W==1 then R must also be 1). struct Sv32Pte { uint64_t raw = 0; // Bit positions (shifts) for the fields described above. static constexpr unsigned V_SHIFT = 0; static constexpr unsigned R_SHIFT = 1; static constexpr unsigned W_SHIFT = 2; static constexpr unsigned X_SHIFT = 3; static constexpr unsigned U_SHIFT = 4; static constexpr unsigned G_SHIFT = 5; static constexpr unsigned A_SHIFT = 6; static constexpr unsigned D_SHIFT = 7; static constexpr unsigned RSW_SHIFT = 8; // two bits: 8..9 static constexpr unsigned PPN_SHIFT = 10; // PPN starts at bit 10 // Masks for each single-bit flag static constexpr uint64_t V_MASK = (1u << V_SHIFT); static constexpr uint64_t R_MASK = (1u << R_SHIFT); static constexpr uint64_t W_MASK = (1u << W_SHIFT); static constexpr uint64_t X_MASK = (1u << X_SHIFT); static constexpr uint64_t U_MASK = (1u << U_SHIFT); static constexpr uint64_t G_MASK = (1u << G_SHIFT); static constexpr uint64_t A_MASK = (1u << A_SHIFT); static constexpr uint64_t D_MASK = (1u << D_SHIFT); // Mask for the 2-bit RSW field (bits 8..9). static constexpr uint64_t RSW_MASK = (0x3u << RSW_SHIFT); // Mask that selects the PPN field within the raw PTE (bits 10..(10 + PPN_BITS - 1)) static constexpr uint64_t PPN_MASK32 = ((PPN_MASK) << PPN_SHIFT); constexpr Sv32Pte() = default; constexpr explicit Sv32Pte(uint64_t raw_) : raw(raw_) {} constexpr uint64_t to_uint() const { return raw; } static constexpr Sv32Pte from_uint(uint64_t r) { return Sv32Pte(r); } // Flag accessors constexpr bool v() const noexcept { return (raw >> V_SHIFT) & 0x1u; } constexpr bool r() const noexcept { return (raw >> R_SHIFT) & 0x1u; } constexpr bool w() const noexcept { return (raw >> W_SHIFT) & 0x1u; } constexpr bool x() const noexcept { return (raw >> X_SHIFT) & 0x1u; } constexpr bool u() const noexcept { return (raw >> U_SHIFT) & 0x1u; } constexpr bool g() const noexcept { return (raw >> G_SHIFT) & 0x1u; } constexpr bool a() const noexcept { return (raw >> A_SHIFT) & 0x1u; } constexpr bool d() const noexcept { return (raw >> D_SHIFT) & 0x1u; } constexpr uint64_t rsw() const noexcept { return (raw >> RSW_SHIFT) & 0x3u; } constexpr uint64_t ppn() const noexcept { return (raw >> PPN_SHIFT) & PPN_MASK; } // Convenience methods used by the page-table walker bool is_leaf() const noexcept { return r() || x(); } bool is_valid() const noexcept { return v() && (!w() || r()); } // Helper to construct a PTE from fields. static constexpr Sv32Pte make( bool v_, bool r_, bool w_, bool x_, bool u_, bool g_, bool a_, bool d_, uint64_t rsw_, uint64_t ppn_) { uint64_t r = 0; r |= (uint64_t(v_) << V_SHIFT); r |= (uint64_t(r_) << R_SHIFT); r |= (uint64_t(w_) << W_SHIFT); r |= (uint64_t(x_) << X_SHIFT); r |= (uint64_t(u_) << U_SHIFT); r |= (uint64_t(g_) << G_SHIFT); r |= (uint64_t(a_) << A_SHIFT); r |= (uint64_t(d_) << D_SHIFT); r |= ((rsw_ & 0x3u) << RSW_SHIFT); r |= ((ppn_ & PPN_MASK) << PPN_SHIFT); return Sv32Pte(r); } }; inline Address make_phys(uint64_t va_raw, const Sv32Pte &pte, int level) { uint64_t offset = va_raw & ((1u << PAGE_SHIFT) - 1); uint64_t phys_ppn = pte.ppn(); if (level == 1) { uint64_t vpn0 = (va_raw >> PAGE_SHIFT) & VPN_MASK; phys_ppn = (phys_ppn & ~VPN_MASK) | vpn0; } return Address { (uint64_t(phys_ppn) << PAGE_SHIFT) | offset }; } } // namespace machine #endif // SV32_H ================================================ FILE: src/machine/memory/virtual/virtual_address.h ================================================ #ifndef VIRTUAL_ADDRESS_H #define VIRTUAL_ADDRESS_H #include "utils.h" #include #include using std::uint64_t; namespace machine { // Lightweight VirtualAddress wrapper offering raw access, alignment checks, arithmetic, and // comparisons. class VirtualAddress { private: uint64_t data; // Raw virtual address public: constexpr explicit VirtualAddress(uint64_t); constexpr VirtualAddress(); constexpr VirtualAddress(const VirtualAddress &address) = default; constexpr VirtualAddress &operator=(const VirtualAddress &address) = default; [[nodiscard]] constexpr uint64_t get_raw() const; constexpr explicit operator uint64_t() const; constexpr static VirtualAddress null(); [[nodiscard]] constexpr bool is_null() const; template [[nodiscard]] constexpr bool is_aligned() const; /* Equality */ constexpr inline bool operator==(const VirtualAddress &other) const; constexpr inline bool operator!=(const VirtualAddress &other) const; /* Ordering */ constexpr inline bool operator<(const VirtualAddress &other) const; constexpr inline bool operator>(const VirtualAddress &other) const; constexpr inline bool operator<=(const VirtualAddress &other) const; constexpr inline bool operator>=(const VirtualAddress &other) const; /* Offset arithmetic */ constexpr inline VirtualAddress operator+(const uint64_t &offset) const; constexpr inline VirtualAddress operator-(const uint64_t &offset) const; inline void operator+=(const uint64_t &offset); inline void operator-=(const uint64_t &offset); /* Bitwise */ constexpr inline VirtualAddress operator&(const uint64_t &mask) const; constexpr inline VirtualAddress operator|(const uint64_t &mask) const; constexpr inline VirtualAddress operator^(const uint64_t &mask) const; constexpr inline VirtualAddress operator>>(const uint64_t &size) const; constexpr inline VirtualAddress operator<<(const uint64_t &size) const; /* Distance arithmetic */ constexpr inline int64_t operator-(const VirtualAddress &other) const; }; constexpr VirtualAddress operator""_vaddr(unsigned long long literal) { return VirtualAddress(literal); } // Implementations constexpr VirtualAddress::VirtualAddress(uint64_t address) : data(address) {} constexpr VirtualAddress::VirtualAddress() : data(0) {} constexpr uint64_t VirtualAddress::get_raw() const { return data; } constexpr VirtualAddress::operator uint64_t() const { return this->get_raw(); } constexpr VirtualAddress VirtualAddress::null() { return VirtualAddress(0x0); } constexpr bool VirtualAddress::is_null() const { return this->get_raw() == 0; } template constexpr bool VirtualAddress::is_aligned() const { return is_aligned_genericdata), T>(this->data); } /* Equality */ constexpr bool VirtualAddress::operator==(const VirtualAddress &other) const { return this->get_raw() == other.get_raw(); } constexpr bool VirtualAddress::operator!=(const VirtualAddress &other) const { return this->get_raw() != other.get_raw(); } /* Ordering */ constexpr bool VirtualAddress::operator<(const VirtualAddress &other) const { return this->get_raw() < other.get_raw(); } constexpr bool VirtualAddress::operator>(const VirtualAddress &other) const { return this->get_raw() > other.get_raw(); } constexpr bool VirtualAddress::operator<=(const VirtualAddress &other) const { return this->get_raw() <= other.get_raw(); } constexpr bool VirtualAddress::operator>=(const VirtualAddress &other) const { return this->get_raw() >= other.get_raw(); } /* Offset arithmetic */ constexpr VirtualAddress VirtualAddress::operator+(const uint64_t &offset) const { return VirtualAddress(this->get_raw() + offset); } constexpr VirtualAddress VirtualAddress::operator-(const uint64_t &offset) const { return VirtualAddress(this->get_raw() - offset); } void VirtualAddress::operator+=(const uint64_t &offset) { data += offset; } void VirtualAddress::operator-=(const uint64_t &offset) { data -= offset; } /* Bitwise */ constexpr VirtualAddress VirtualAddress::operator&(const uint64_t &mask) const { return VirtualAddress(this->get_raw() & mask); } constexpr VirtualAddress VirtualAddress::operator|(const uint64_t &mask) const { return VirtualAddress(this->get_raw() | mask); } constexpr VirtualAddress VirtualAddress::operator^(const uint64_t &mask) const { return VirtualAddress(this->get_raw() ^ mask); } constexpr VirtualAddress VirtualAddress::operator>>(const uint64_t &size) const { return VirtualAddress(this->get_raw() >> size); } constexpr VirtualAddress VirtualAddress::operator<<(const uint64_t &size) const { return VirtualAddress(this->get_raw() << size); } /* Distance arithmetic */ constexpr int64_t VirtualAddress::operator-(const VirtualAddress &other) const { return this->get_raw() - other.get_raw(); } } // namespace machine Q_DECLARE_METATYPE(machine::VirtualAddress) #endif // VIRTUAL_ADDRESS_H ================================================ FILE: src/machine/pipeline.h ================================================ /** * State of the core pipeline. * * Each internal has a state struct. The state struct is composed of the * internal state for visualization and two sets of outgoing interstage * registers - result and final. Both are filled with the same data as the * stage finishes, but result is never later modified. Final will be modified * by flushed, exceptions etc. and it will be used for further execution. * * TODO: * - Move init functions here as methods (constructor/discard). * * @file */ #ifndef STAGES_H #define STAGES_H #include "instruction.h" #include "machinedefs.h" #include "memory/address.h" #include "registers.h" #include #include namespace machine { enum ForwardFrom { FORWARD_NONE = 0b00, FORWARD_FROM_W = 0b01, FORWARD_FROM_M = 0b10, }; struct PCInterstage { bool stop_if = false; }; struct PCState { PCInterstage final {}; }; struct FetchInterstage { Instruction inst = Instruction::NOP; // Loaded instruction Address inst_addr = STAGEADDR_NONE; // Address of instruction Address next_inst_addr = 0_addr; // `inst_addr` + `inst.size()` /** Inspecting other stages to get this value is problematic due to stalls and flushed. * Therefore we pass it through the whole pipeline. */ Address predicted_next_inst_addr = 0_addr; enum ExceptionCause excause = EXCAUSE_NONE; bool is_valid = false; public: /** Reset to value corresponding to NOP. */ void flush() { *this = {}; } }; struct FetchInternalState { RegisterValue fetched_value = 0; unsigned excause_num = 0; }; struct FetchState { FetchInternalState internal {}; FetchInterstage result {}; FetchInterstage final {}; FetchState(const FetchInternalState &stage, const FetchInterstage &interstage) : internal(stage) , result(interstage) , final(interstage) { this->internal.excause_num = static_cast(interstage.excause); } FetchState() = default; FetchState(const FetchState &) = default; FetchState &operator=(const FetchState &) = default; }; struct DecodeInterstage { Instruction inst = Instruction::NOP; Address inst_addr = STAGEADDR_NONE; Address next_inst_addr = 0_addr; Address predicted_next_inst_addr = 0_addr; RegisterValue val_rs = 0; // Value from register rs RegisterValue val_rs_orig = 0; // Value from register rs1 without forwarding RegisterValue val_rt = 0; // Value from register rt RegisterValue val_rt_orig = 0; // Value from register rs1 without forwarding RegisterValue immediate_val = 0; // Sign-extended immediate value // rd according to regd) RegisterValue csr_read_val = 0; // Value read from csr CSR::Address csr_address = CSR::Address(0); ExceptionCause excause = EXCAUSE_NONE; ForwardFrom ff_rs = FORWARD_NONE; ForwardFrom ff_rt = FORWARD_NONE; AluComponent alu_component; // Selects computational component in alu - basic ALU / MUL. AluCombinedOp aluop = { .alu_op = AluOp::ADD }; // Decoded ALU operation AccessControl memctl = AC_NONE; // Decoded memory access type RegisterId num_rs = 0; // Number of the register s1 RegisterId num_rt = 0; // Number of the register s2 RegisterId num_rd = 0; // Number of the register d bool memread = false; // If memory should be read bool memwrite = false; // If memory should write input bool alusrc = false; // If second value to alu is immediate value (rt used otherwise) bool regwrite = false; // If output should be written back to register bool alu_req_rs = false; // requires rs value for ALU bool alu_req_rt = false; // requires rt value for ALU or SW bool branch_bxx = false; // branch instruction bool branch_jal = false; // jump bool branch_val = false; // negate branch condition bool branch_jalr = false; // JALR: write PC+4 to register and jump to ALU result bool stall = false; bool is_valid = false; bool w_operation = false; // ALU or other operation is limited to word size (32-bits) bool alu_mod = false; // alternative versions of ADD and right-shift bool alu_pc = false; // PC is input to ALU bool csr = false; // Zicsr, implies csr read and csr write bool csr_to_alu = false; bool csr_write = false; bool xret = false; // Return from exception, MRET and SRET CSR::PrivilegeLevel xret_privlev = CSR::PrivilegeLevel::UNPRIVILEGED; bool insert_stall_before = false; public: /** Reset to value corresponding to NOP. */ void flush() { *this = {}; } }; struct DecodeInternalState { /** * ALU OP as a number * GUI needs to show a number, not enumerated value (for simple interface). * Core is responsive for the conversion. */ unsigned alu_op_num = 0; unsigned excause_num = 0; RegisterValue inst_bus = 0; bool alu_mul = false; }; struct DecodeState { DecodeInternalState internal {}; DecodeInterstage result {}; DecodeInterstage final {}; DecodeState(const DecodeInternalState &stage, const DecodeInterstage &interstage) : internal(stage) , result(interstage) , final(interstage) { this->internal.excause_num = static_cast(interstage.excause); this->internal.alu_op_num = static_cast(interstage.aluop.alu_op); } DecodeState() = default; DecodeState(const DecodeState &) = default; DecodeState &operator=(const DecodeState &) = default; }; struct ExecuteInterstage { Instruction inst = Instruction::NOP; Address inst_addr = STAGEADDR_NONE; Address next_inst_addr = 0_addr; Address predicted_next_inst_addr = 0_addr; Address branch_jal_target = 0_addr; //> Potential branch target (inst_addr + 4 + imm). RegisterValue val_rt = 0; RegisterValue alu_val = 0; // Result of ALU execution RegisterValue immediate_val = 0; RegisterValue csr_read_val = 0; CSR::Address csr_address = CSR::Address(0); ExceptionCause excause = EXCAUSE_NONE; AccessControl memctl = AC_NONE; RegisterId num_rd = 0; bool memread = false; bool memwrite = false; bool regwrite = false; bool is_valid = false; bool branch_bxx = false; bool branch_jal = false; bool branch_val = false; bool branch_jalr = false; //> @copydoc DecodeInterstage::branch_jalr bool alu_zero = false; bool csr = false; bool csr_write = false; bool xret = false; CSR::PrivilegeLevel xret_privlev = CSR::PrivilegeLevel::UNPRIVILEGED; public: /** Reset to value corresponding to NOP. */ void flush() { *this = {}; } }; struct ExecuteInternalState { RegisterValue alu_src1 = 0; RegisterValue alu_src2 = 0; RegisterValue immediate = 0; RegisterValue rs = 0; RegisterValue rt = 0; unsigned stall_status = 0; /** * ALU OP as a number. * GUI needs to show a number, not enumerated value (for simple interface). * Core is responsive for the conversion. */ unsigned alu_op_num = 0; /** * Forwarding setting as a number. * Same note as alu_op_num. */ unsigned forward_from_rs1_num = 0; /** * Forwarding setting as a number. * Same note as alu_op_num. */ unsigned forward_from_rs2_num = 0; unsigned excause_num = 0; bool alu_src = false; bool alu_mul = false; bool branch_bxx = false; bool alu_pc = false; // PC is input to ALU }; struct ExecuteState { ExecuteInternalState internal {}; ExecuteInterstage result {}; ExecuteInterstage final {}; ExecuteState(const ExecuteInternalState &stage, const ExecuteInterstage &interstage) : internal(stage) , result(interstage) , final(interstage) { this->internal.excause_num = static_cast(interstage.excause); } ExecuteState() = default; ExecuteState(const ExecuteState &) = default; ExecuteState &operator=(const ExecuteState &) = default; }; struct MemoryInterstage { Instruction inst = Instruction::NOP; Address inst_addr = STAGEADDR_NONE; Address next_inst_addr = 0_addr; Address predicted_next_inst_addr = 0_addr; Address computed_next_inst_addr = 0_addr; Address mem_addr = 0_addr; // Address used to access memory RegisterValue towrite_val = 0; ExceptionCause excause = EXCAUSE_NONE; RegisterId num_rd = 0; bool memtoreg = false; bool regwrite = false; bool is_valid = false; bool csr_written = false; CSR::PrivilegeLevel xret_privlev = CSR::PrivilegeLevel::UNPRIVILEGED; public: /** Reset to value corresponding to NOP. */ void flush() { *this = {}; } }; struct MemoryInternalState { RegisterValue mem_read_val = 0; RegisterValue mem_write_val = 0; RegisterValue mem_addr = 0; unsigned excause_num = 0; bool memwrite = false; bool memread = false; bool branch_bxx = false; bool branch_jal = false; bool branch_outcome = false; bool branch_jalx = false; bool branch_jalr = false; bool xret = false; }; struct MemoryState { MemoryInternalState internal {}; MemoryInterstage result {}; MemoryInterstage final {}; MemoryState(const MemoryInternalState &stage, const MemoryInterstage &interstage) : internal(stage) , result(interstage) , final(interstage) { this->internal.excause_num = static_cast(interstage.excause); } MemoryState() = default; MemoryState(const MemoryState &) = default; MemoryState &operator=(const MemoryState &) = default; }; struct WritebackInternalState { Instruction inst = Instruction::NOP; Address inst_addr = STAGEADDR_NONE; RegisterValue value = 0; RegisterId num_rd = 0; bool regwrite = false; bool memtoreg = false; }; struct WritebackState { WritebackInternalState internal {}; explicit WritebackState(WritebackInternalState stage) : internal(std::move(stage)) {} WritebackState() = default; WritebackState(const WritebackState &) = default; WritebackState &operator=(const WritebackState &) = default; }; struct Pipeline { PCState pc {}; FetchState fetch {}; DecodeState decode {}; ExecuteState execute {}; MemoryState memory {}; WritebackState writeback {}; }; } // namespace machine #endif // STAGES_H ================================================ FILE: src/machine/predictor.cpp ================================================ #include "predictor.h" LOG_CATEGORY("machine.BranchPredictor"); using namespace machine; QStringView machine::branch_result_to_string(const BranchResult result, const bool abbrv) { switch (result) { case BranchResult::NOT_TAKEN: return abbrv ? u"NT" : u"Not taken"; case BranchResult::TAKEN: return abbrv ? u"T" : u"Taken"; default: return u""; } } QStringView machine::predictor_state_to_string(const PredictorState state, const bool abbrv) { switch (state) { case PredictorState::NOT_TAKEN: return abbrv ? u"NT" : u"Not taken"; case PredictorState::TAKEN: return abbrv ? u"T" : u"Taken"; case PredictorState::STRONGLY_NOT_TAKEN: return abbrv ? u"SNT" : u"Strongly not taken"; case PredictorState::WEAKLY_NOT_TAKEN: return abbrv ? u"WNT" : u"Weakly not taken"; case PredictorState::WEAKLY_TAKEN: return abbrv ? u"WT" : u"Weakly taken"; case PredictorState::STRONGLY_TAKEN: return abbrv ? u"ST" : u"Strongly taken"; default: return u""; } } QStringView machine::predictor_type_to_string(const PredictorType type) { switch (type) { case PredictorType::ALWAYS_NOT_TAKEN: return u"Always not taken"; case PredictorType::ALWAYS_TAKEN: return u"Always taken"; case PredictorType::BTFNT: return u"Backward Taken Forward Not Taken"; case PredictorType::SMITH_1_BIT: return u"Smith 1 bit"; case PredictorType::SMITH_2_BIT: return u"Smith 2 bit"; case PredictorType::SMITH_2_BIT_HYSTERESIS: return u"Smith 2 bit with hysteresis"; default: return u""; } } QStringView machine::branch_type_to_string(const BranchType type) { switch (type) { case BranchType::JUMP: return u"JUMP"; case BranchType::BRANCH: return u"BRANCH"; default: return u""; } } QString machine::addr_to_hex_str(const machine::Address address) { QString hex_addr, zero_padding; hex_addr = QString::number(address.get_raw(), 16); zero_padding.fill('0', 8 - hex_addr.count()); return "0x" + zero_padding + hex_addr; } bool machine::is_predictor_type_dynamic(const PredictorType type) { switch (type) { case PredictorType::ALWAYS_NOT_TAKEN: case PredictorType::ALWAYS_TAKEN: case PredictorType::BTFNT: return false; case PredictorType::SMITH_1_BIT: case PredictorType::SMITH_2_BIT: case PredictorType::SMITH_2_BIT_HYSTERESIS: return true; default: return false; } } ///////////////////////////////// // BranchHistoryRegister class // ///////////////////////////////// // Constructor BranchHistoryRegister::BranchHistoryRegister(const uint8_t number_of_bits) : number_of_bits(init_number_of_bits(number_of_bits)) , register_mask(init_register_mask(number_of_bits)) {} // Init helper function to check the number of bits uint8_t BranchHistoryRegister::init_number_of_bits(const uint8_t b) const { if (b > BP_MAX_BHR_BITS) { WARN("Number of BHR bits (%u) was larger than %u during init", b, BP_MAX_BHR_BITS); return BP_MAX_BHR_BITS; } return b; } // Init helper function to create the register mask used for masking unused bits uint16_t BranchHistoryRegister::init_register_mask(const uint8_t b) const { if (b >= BP_MAX_BHR_BITS) { return ~0x0; } if (b == 0) { return 0x0; } return (1 << b) - 1; } uint8_t BranchHistoryRegister::get_number_of_bits() const { return number_of_bits; } uint16_t BranchHistoryRegister::get_register_mask() const { return register_mask; } uint16_t BranchHistoryRegister::get_value() const { return value; } // Pushes new value into the register void BranchHistoryRegister::update(const BranchResult result) { // Shift all bits to the left value = value << 1; // Add the result as the new least significant bit // By default the new LSB is 0, only set to 1 if branch was taken if (result == BranchResult::TAKEN) { value |= 0x1; } // Set all bits outside of the scope of the register to zero value = value & register_mask; emit bhr_updated(number_of_bits, value); } void BranchHistoryRegister::clear() { value = 0x0; emit bhr_updated(number_of_bits, value); } ////////////////////////////// // BranchTargetBuffer class // ////////////////////////////// // Constructor BranchTargetBuffer::BranchTargetBuffer(uint8_t number_of_bits) : number_of_bits(init_number_of_bits(number_of_bits)) { btb.resize(qPow(2, number_of_bits)); } uint8_t BranchTargetBuffer::init_number_of_bits(const uint8_t b) const { if (b > BP_MAX_BTB_BITS) { WARN("Number of BTB bits (%u) was larger than %u during init", b, BP_MAX_BTB_BITS); return BP_MAX_BTB_BITS; } return b; } uint8_t BranchTargetBuffer::get_number_of_bits() const { return number_of_bits; } // Calculate index for addressing Branch Target Buffer from instruction address uint16_t BranchTargetBuffer::calculate_index(const Address instruction_address) const { return ((uint16_t)(instruction_address.get_raw() >> 2)) & ((1 << number_of_bits) - 1); } BranchTargetBufferEntry BranchTargetBuffer::get_entry(const Address instruction_address) const { // Get index from instruction address const uint16_t index { calculate_index(instruction_address) }; // Check index validity if (index >= btb.capacity()) { WARN("Tried to read from BTB at invalid index: %u", index); return BranchTargetBufferEntry(); } return btb.at(index); } // Update BTB entry with given values, at index computed from the instruction address void BranchTargetBuffer::update( const Address instruction_address, const Address target_address, const BranchType branch_type) { // Get index from instruction address const uint16_t btb_index { calculate_index(instruction_address) }; // Check index validity if (btb_index >= btb.capacity()) { WARN("Tried to update BTB at invalid index: %u", btb_index); return; } // Write new entry to the table const BranchTargetBufferEntry btb_entry = { .entry_valid = true, .instruction_address = instruction_address, .target_address = target_address, .branch_type = branch_type }; btb.at(btb_index) = btb_entry; // Send signal with the data emit btb_row_updated(btb_index, btb_entry); } void BranchTargetBuffer::clear() { for (uint16_t i = 0; i < btb.capacity(); i++) { btb.at(i) = BranchTargetBufferEntry(); emit btb_row_updated(i, btb.at(i)); } } ///////////////////// // Predictor class // ///////////////////// // Predictor Generic // ################# Predictor::Predictor( uint8_t number_of_bht_addr_bits, uint8_t number_of_bht_bits, PredictorState initial_state) : number_of_bht_addr_bits(init_number_of_bht_addr_bits(number_of_bht_addr_bits)) , number_of_bht_bits(init_number_of_bht_bits(number_of_bht_bits)) , initial_state(initial_state) { bht.resize(qPow(2, number_of_bht_bits)); clear_bht_state(); clear_bht_stats(); } uint8_t Predictor::init_number_of_bht_addr_bits(const uint8_t b) const { if (b > BP_MAX_BHT_ADDR_BITS) { WARN( "Number of BHT bits from incstruction address (%u) was larger than %d during init", b, BP_MAX_BHT_ADDR_BITS); return BP_MAX_BHT_ADDR_BITS; } return b; } uint8_t Predictor::init_number_of_bht_bits(const uint8_t b) const { if (b > BP_MAX_BHT_BITS) { WARN("Number of BHT bits (%u) was larger than %d during init", b, BP_MAX_BHT_BITS); return BP_MAX_BHT_BITS; } return b; } BranchResult Predictor::convert_state_to_prediction(PredictorState state) const { if (state == PredictorState::NOT_TAKEN) { return BranchResult::NOT_TAKEN; } else if (state == PredictorState::TAKEN) { return BranchResult::TAKEN; } else if (state == PredictorState::WEAKLY_NOT_TAKEN) { return BranchResult::NOT_TAKEN; } else if (state == PredictorState::STRONGLY_NOT_TAKEN) { return BranchResult::NOT_TAKEN; } else if (state == PredictorState::WEAKLY_TAKEN) { return BranchResult::TAKEN; } else if (state == PredictorState::STRONGLY_TAKEN) { return BranchResult::TAKEN; } else { WARN("Smith predictor was provided invalid state"); return BranchResult::NOT_TAKEN; } } void Predictor::update_stats(bool prediction_was_correct) { stats.total += 1; if (prediction_was_correct) { stats.correct += 1; } else { stats.wrong += 1; } stats.accuracy = ((stats.correct * 100) / stats.total); emit stats_updated(stats); } void Predictor::update_bht_stats(uint16_t bht_index, bool prediction_was_correct) { if (bht_index >= bht.capacity()) { WARN("Tried to access BHT at invalid index: %u", bht_index); return; } bht.at(bht_index).stats.total += 1; if (prediction_was_correct) { bht.at(bht_index).stats.correct += 1; } else { bht.at(bht_index).stats.wrong += 1; } bht.at(bht_index).stats.accuracy = ((bht.at(bht_index).stats.correct * 100) / bht.at(bht_index).stats.total); emit bht_row_updated(bht_index, bht.at(bht_index)); } // Calculate index for addressing Branch History Table from BHR and instruction address uint16_t Predictor::calculate_bht_index(const uint16_t bhr_value, const Address instruction_address) const { const uint16_t bhr_part = bhr_value << number_of_bht_addr_bits; const uint16_t address_mask = (1 << number_of_bht_addr_bits) - 1; const uint16_t address_part = ((uint16_t)(instruction_address.get_raw() >> 2)) & address_mask; const uint16_t index = bhr_part | address_part; return index; } void Predictor::clear_stats() { stats = PredictionStatistics(); emit stats_updated(stats); } void Predictor::clear_bht_stats() { for (uint16_t i = 0; i < bht.capacity(); i++) { bht.at(i).stats = PredictionStatistics(); emit bht_row_updated(i, bht.at(i)); } } void Predictor::clear_bht_state() { for (uint16_t i = 0; i < bht.capacity(); i++) { bht.at(i).state = initial_state; emit bht_row_updated(i, bht.at(i)); } } void Predictor::clear() { clear_stats(); clear_bht_stats(); clear_bht_state(); } void Predictor::flush() { clear_bht_state(); } // Always Not Taken // ################ PredictorAlwaysNotTaken::PredictorAlwaysNotTaken() : Predictor(0, 0, PredictorState::UNDEFINED) {} BranchResult PredictorAlwaysNotTaken::predict(PredictionInput input) { UNUSED(input); return BranchResult::NOT_TAKEN; } void PredictorAlwaysNotTaken::update(PredictionFeedback feedback) { update_stats(feedback.result == BranchResult::NOT_TAKEN); } // Always Taken // ############ PredictorAlwaysTaken::PredictorAlwaysTaken() : Predictor(0, 0, PredictorState::UNDEFINED) {} BranchResult PredictorAlwaysTaken::predict(PredictionInput input) { UNUSED(input); return BranchResult::TAKEN; } void PredictorAlwaysTaken::update(PredictionFeedback feedback) { update_stats(feedback.result == BranchResult::TAKEN); } // Backward Taken Forward Not Taken // ################################ PredictorBTFNT::PredictorBTFNT() : Predictor(0, 0, PredictorState::UNDEFINED) {} BranchResult PredictorBTFNT::predict(PredictionInput input) { if (input.target_address > input.instruction_address) { // If target address is larger than jump instruction address (forward jump), // predict not taken return BranchResult::NOT_TAKEN; } else { // Otherwise (backward jump) predict taken return BranchResult::TAKEN; } } void PredictorBTFNT::update(PredictionFeedback feedback) { if (feedback.target_address > feedback.instruction_address) { update_stats(feedback.result == BranchResult::NOT_TAKEN); } else { update_stats(feedback.result == BranchResult::TAKEN); } } // Smith 1 Bit // ########### PredictorSmith1Bit::PredictorSmith1Bit( uint8_t number_of_bht_addr_bits, uint8_t number_of_bht_bits, PredictorState initial_state) : Predictor(number_of_bht_addr_bits, number_of_bht_bits, initial_state) {}; BranchResult PredictorSmith1Bit::predict(PredictionInput input) { const uint16_t index { calculate_bht_index(input.bhr_value, input.instruction_address) }; if (index >= bht.capacity()) { WARN("Tried to access BHT at invalid index: %u", index); return BranchResult::NOT_TAKEN; } // Decide prediction return convert_state_to_prediction(bht.at(index).state); } void PredictorSmith1Bit::update(PredictionFeedback feedback) { const uint16_t index { calculate_bht_index(feedback.bhr_value, feedback.instruction_address) }; if (index >= bht.capacity()) { WARN("Tried to access BHT at invalid index: %u", index); return; } update_bht_stats(index, feedback.result == convert_state_to_prediction(bht.at(index).state)); update_stats(feedback.result == convert_state_to_prediction(bht.at(index).state)); // Update internal state if (feedback.result == BranchResult::NOT_TAKEN) { bht.at(index).state = PredictorState::NOT_TAKEN; } else if (feedback.result == BranchResult::TAKEN) { bht.at(index).state = PredictorState::TAKEN; } else { WARN("Smith 1 bit predictor has received invalid prediction result"); } emit bht_row_updated(index, bht.at(index)); } // Smith 2 Bit // ########### PredictorSmith2Bit::PredictorSmith2Bit( uint8_t number_of_bht_addr_bits, uint8_t number_of_bht_bits, PredictorState initial_state) : Predictor(number_of_bht_addr_bits, number_of_bht_bits, initial_state) {}; BranchResult PredictorSmith2Bit::predict(PredictionInput input) { const uint16_t index { calculate_bht_index(input.bhr_value, input.instruction_address) }; if (index >= bht.capacity()) { WARN("Tried to access BHT at invalid index: %u", index); return BranchResult::NOT_TAKEN; } // Decide prediction return convert_state_to_prediction(bht.at(index).state); } void PredictorSmith2Bit::update(PredictionFeedback feedback) { const uint16_t index { calculate_bht_index(feedback.bhr_value, feedback.instruction_address) }; if (index >= bht.capacity()) { WARN("Tried to access BHT at invalid index: %u", index); return; } update_bht_stats(index, feedback.result == convert_state_to_prediction(bht.at(index).state)); update_stats(feedback.result == convert_state_to_prediction(bht.at(index).state)); // Read value from BHT at correct index const PredictorState state = bht.at(index).state; // Update internal state if (feedback.result == BranchResult::NOT_TAKEN) { if (state == PredictorState::STRONGLY_TAKEN) { bht.at(index).state = PredictorState::WEAKLY_TAKEN; } else if (state == PredictorState::WEAKLY_TAKEN) { bht.at(index).state = PredictorState::WEAKLY_NOT_TAKEN; } else if (state == PredictorState::WEAKLY_NOT_TAKEN) { bht.at(index).state = PredictorState::STRONGLY_NOT_TAKEN; } else if (state == PredictorState::STRONGLY_NOT_TAKEN) { bht.at(index).state = PredictorState::STRONGLY_NOT_TAKEN; } else { WARN("Smith 2 bit predictor BHT has returned invalid state"); } } else if (feedback.result == BranchResult::TAKEN) { if (state == PredictorState::STRONGLY_TAKEN) { bht.at(index).state = PredictorState::STRONGLY_TAKEN; } else if (state == PredictorState::WEAKLY_TAKEN) { bht.at(index).state = PredictorState::STRONGLY_TAKEN; } else if (state == PredictorState::WEAKLY_NOT_TAKEN) { bht.at(index).state = PredictorState::WEAKLY_TAKEN; } else if (state == PredictorState::STRONGLY_NOT_TAKEN) { bht.at(index).state = PredictorState::WEAKLY_NOT_TAKEN; } else { WARN("Smith 2 bit predictor BHT has returned invalid state"); } } else { WARN("Smith 2 bit predictor has received invalid prediction result"); } emit bht_row_updated(index, bht.at(index)); } // Smith 2 Bit with hysteresis // ########################### PredictorSmith2BitHysteresis::PredictorSmith2BitHysteresis( uint8_t number_of_bht_addr_bits, uint8_t number_of_bht_bits, PredictorState initial_state) : Predictor(number_of_bht_addr_bits, number_of_bht_bits, initial_state) {}; BranchResult PredictorSmith2BitHysteresis::predict(PredictionInput input) { const uint16_t index { calculate_bht_index(input.bhr_value, input.instruction_address) }; if (index >= bht.capacity()) { WARN("Tried to access BHT at invalid index: %u", index); return BranchResult::NOT_TAKEN; } // Decide prediction return convert_state_to_prediction(bht.at(index).state); } void PredictorSmith2BitHysteresis::update(PredictionFeedback feedback) { const uint16_t index { calculate_bht_index(feedback.bhr_value, feedback.instruction_address) }; if (index >= bht.capacity()) { WARN("Tried to access BHT at invalid index: %u", index); return; } update_bht_stats(index, feedback.result == convert_state_to_prediction(bht.at(index).state)); update_stats(feedback.result == convert_state_to_prediction(bht.at(index).state)); // Read value from BHT at correct index const PredictorState state = bht.at(index).state; // Update internal state if (feedback.result == BranchResult::NOT_TAKEN) { if (state == PredictorState::STRONGLY_TAKEN) { bht.at(index).state = PredictorState::WEAKLY_TAKEN; } else if (state == PredictorState::WEAKLY_TAKEN) { bht.at(index).state = PredictorState::STRONGLY_NOT_TAKEN; } else if (state == PredictorState::WEAKLY_NOT_TAKEN) { bht.at(index).state = PredictorState::STRONGLY_NOT_TAKEN; } else if (state == PredictorState::STRONGLY_NOT_TAKEN) { bht.at(index).state = PredictorState::STRONGLY_NOT_TAKEN; } else { WARN("Smith 2 bit hysteresis predictor BHT has returned invalid state"); } } else if (feedback.result == BranchResult::TAKEN) { if (state == PredictorState::STRONGLY_TAKEN) { bht.at(index).state = PredictorState::STRONGLY_TAKEN; } else if (state == PredictorState::WEAKLY_TAKEN) { bht.at(index).state = PredictorState::STRONGLY_TAKEN; } else if (state == PredictorState::WEAKLY_NOT_TAKEN) { bht.at(index).state = PredictorState::STRONGLY_TAKEN; } else if (state == PredictorState::STRONGLY_NOT_TAKEN) { bht.at(index).state = PredictorState::WEAKLY_NOT_TAKEN; } else { WARN("Smith 2 bit hysteresis predictor BHT has returned invalid state"); } } else { WARN("Smith 2 bit hysteresis predictor has received invalid prediction result"); } emit bht_row_updated(index, bht.at(index)); } /////////////////////////// // BranchPredictor class // /////////////////////////// BranchPredictor::BranchPredictor( bool enabled, PredictorType predictor_type, PredictorState initial_state, uint8_t number_of_btb_bits, uint8_t number_of_bhr_bits, uint8_t number_of_bht_addr_bits) : enabled(enabled) , initial_state(initial_state) , number_of_btb_bits(init_number_of_btb_bits(number_of_btb_bits)) , number_of_bhr_bits(init_number_of_bhr_bits(number_of_bhr_bits)) , number_of_bht_addr_bits(init_number_of_bht_addr_bits(number_of_bht_addr_bits)) , number_of_bht_bits(init_number_of_bht_bits(number_of_bhr_bits, number_of_bht_addr_bits)) { // Create predicotr switch (predictor_type) { case PredictorType::ALWAYS_NOT_TAKEN: predictor = new PredictorAlwaysNotTaken(); break; case PredictorType::ALWAYS_TAKEN: predictor = new PredictorAlwaysTaken(); break; case PredictorType::BTFNT: predictor = new PredictorBTFNT(); break; case PredictorType::SMITH_1_BIT: predictor = new PredictorSmith1Bit(number_of_bht_addr_bits, number_of_bht_bits, initial_state); break; case PredictorType::SMITH_2_BIT: predictor = new PredictorSmith2Bit(number_of_bht_addr_bits, number_of_bht_bits, initial_state); break; case PredictorType::SMITH_2_BIT_HYSTERESIS: predictor = new PredictorSmith2BitHysteresis( number_of_bht_addr_bits, number_of_bht_bits, initial_state); break; default: throw std::invalid_argument("Invalid predictor type selected"); } LOG("Initialized branch predictor: %s", qPrintable(get_predictor_name().toString())); bhr = new BranchHistoryRegister(number_of_bhr_bits); btb = new BranchTargetBuffer(number_of_btb_bits); if (enabled) { // Pass through BTB signals connect(btb, &BranchTargetBuffer::btb_row_updated, this, &BranchPredictor::btb_row_updated); // Pass through BHR signals connect(bhr, &BranchHistoryRegister::bhr_updated, this, &BranchPredictor::bhr_updated); // Pass through predictor signals connect( predictor, &Predictor::stats_updated, this, &BranchPredictor::predictor_stats_updated); connect( predictor, &Predictor::bht_row_updated, this, &BranchPredictor::predictor_bht_row_updated); } } BranchPredictor::~BranchPredictor() { delete predictor; predictor = nullptr; delete bhr; bhr = nullptr; delete btb; btb = nullptr; } uint8_t BranchPredictor::init_number_of_btb_bits(const uint8_t b) const { if (b > BP_MAX_BTB_BITS) { WARN("Number of BTB bits (%u) was larger than %d during init", b, BP_MAX_BTB_BITS); return BP_MAX_BTB_BITS; } return b; } uint8_t BranchPredictor::init_number_of_bhr_bits(const uint8_t b) const { if (b > BP_MAX_BHR_BITS) { WARN("Number of BHR bits (%u) was larger than %d during init", b, BP_MAX_BHR_BITS); return BP_MAX_BHR_BITS; } return b; } uint8_t BranchPredictor::init_number_of_bht_addr_bits(const uint8_t b) const { if (b > BP_MAX_BHT_ADDR_BITS) { WARN( "Number of BHT instruction address bits (%u) was larger than %d during init", b, BP_MAX_BHT_ADDR_BITS); return BP_MAX_BHT_ADDR_BITS; } return b; } uint8_t BranchPredictor::init_number_of_bht_bits(const uint8_t b_bhr, const uint8_t b_addr) const { // Clamp number of BHR bits uint8_t checked_number_of_bits_bhr { b_bhr }; if (checked_number_of_bits_bhr > BP_MAX_BHR_BITS) { checked_number_of_bits_bhr = BP_MAX_BHR_BITS; } // Clamp number of address index bits uint8_t checked_number_of_bits_addr { b_addr }; if (checked_number_of_bits_addr > BP_MAX_BHT_ADDR_BITS) { checked_number_of_bits_addr = BP_MAX_BHT_ADDR_BITS; } // Check sum uint8_t b_sum { (uint8_t)(checked_number_of_bits_bhr + checked_number_of_bits_addr) }; if (b_sum > BP_MAX_BHT_BITS) { b_sum = BP_MAX_BHT_BITS; } return b_sum; } bool BranchPredictor::get_enabled() const { return enabled; } PredictorType BranchPredictor::get_predictor_type() const { if (!enabled) { return PredictorType::UNDEFINED; } return predictor->get_type(); } QStringView BranchPredictor::get_predictor_name() const { if (!enabled) { return u"None"; } return predictor_type_to_string(predictor->get_type()); } PredictorState BranchPredictor::get_initial_state() const { if (!enabled) { return PredictorState::UNDEFINED; } return initial_state; } uint8_t BranchPredictor::get_number_of_btb_bits() const { if (!enabled) { return 0; } return number_of_btb_bits; } uint8_t BranchPredictor::get_number_of_bhr_bits() const { if (!enabled) { return 0; } return number_of_bhr_bits; } uint8_t BranchPredictor::get_number_of_bht_addr_bits() const { if (!enabled) { return 0; } return number_of_bht_addr_bits; } uint8_t BranchPredictor::get_number_of_bht_bits() const { if (!enabled) { return 0; } return number_of_bht_bits; } const PredictionStatistics *BranchPredictor::get_stats() const { if (!enabled) { return nullptr; } return &total_stats; } void BranchPredictor::increment_jumps() { total_stats.total += 1; total_stats.correct = total_stats.total - total_stats.wrong; if (total_stats.total > 0) { total_stats.accuracy = ((total_stats.correct * 100) / total_stats.total); } emit total_stats_updated(total_stats); } void BranchPredictor::increment_mispredictions() { total_stats.wrong += 1; total_stats.correct = total_stats.total - total_stats.wrong; if (total_stats.total > 0) { total_stats.accuracy = ((total_stats.correct * 100) / total_stats.total); } emit total_stats_updated(total_stats); } Address BranchPredictor::predict_next_pc_address( const Instruction instruction, const Address instruction_address) const { // Check if predictor is enabled if (!enabled) { return instruction_address + 4; } // Read entry from BTB const BranchTargetBufferEntry btb_entry = btb->get_entry(instruction_address); if (!btb_entry.entry_valid) { return instruction_address + 4; } if (btb_entry.instruction_address != instruction_address) { return instruction_address + 4; } // Make prediction const PredictionInput prediction_input { .instruction = instruction, .bhr_value = bhr->get_value(), .instruction_address = instruction_address, .target_address = btb_entry.target_address, }; BranchResult predicted_result { BranchResult::UNDEFINED }; if (btb_entry.branch_type == BranchType::BRANCH) { predicted_result = predictor->predict(prediction_input); } else { predicted_result = BranchResult::TAKEN; } emit prediction_done( btb->calculate_index(instruction_address), predictor->calculate_bht_index(bhr->get_value(), instruction_address), prediction_input, predicted_result, btb_entry.branch_type); // If the branch was predicted Taken if (predicted_result == BranchResult::TAKEN) { return btb_entry.target_address; } // Default prediction - Not Taken return instruction_address + 4; } // Function for updating the predictor and the Branch History Register (BHR) void BranchPredictor::update( const Instruction instruction, const Address instruction_address, const Address target_address, const BranchType branch_type, const BranchResult result) { // Check if predictor is enabled if (!enabled) { return; } // Update Branch Target Buffer btb->update(instruction_address, target_address, branch_type); // Update predictor only for conditional branches const PredictionFeedback prediction_feedback { .instruction = instruction, .bhr_value = bhr->get_value(), .instruction_address = instruction_address, .target_address = target_address, .result = result, .branch_type = branch_type }; if (branch_type == BranchType::BRANCH) { predictor->update(prediction_feedback); } increment_jumps(); emit update_done( btb->calculate_index(instruction_address), predictor->calculate_bht_index(bhr->get_value(), instruction_address), prediction_feedback); // Update global branch history bhr->update(result); } void BranchPredictor::clear() { bhr->clear(); btb->clear(); predictor->clear(); emit cleared(); } void BranchPredictor::flush() { bhr->clear(); btb->clear(); predictor->flush(); emit flushed(); } ================================================ FILE: src/machine/predictor.h ================================================ #ifndef PREDICTOR_H #define PREDICTOR_H #include "common/logging.h" #include "instruction.h" #include "memory/address.h" #include "predictor_types.h" #include #include namespace machine { QStringView branch_result_to_string(const BranchResult result, const bool abbrv = false); QStringView predictor_state_to_string(const PredictorState state, const bool abbrv = false); QStringView predictor_type_to_string(const PredictorType type); QStringView branch_type_to_string(const BranchType type); QString addr_to_hex_str(const machine::Address address); bool is_predictor_type_dynamic(const PredictorType type); ///////////////////////////////// // BranchHistoryRegister class // ///////////////////////////////// class BranchHistoryRegister final : public QObject { Q_OBJECT public: // Constructors & Destructor explicit BranchHistoryRegister(const uint8_t number_of_bits); private: // Internal functions uint8_t init_number_of_bits(const uint8_t b) const; uint16_t init_register_mask(const uint8_t b) const; public: // General functions uint8_t get_number_of_bits() const; uint16_t get_register_mask() const; uint16_t get_value() const; void update(const BranchResult result); void clear(); signals: void bhr_updated(uint8_t number_of_bhr_bits, uint16_t register_value); private: // Internal variables const uint8_t number_of_bits; const uint16_t register_mask; uint16_t value { 0 }; }; ///////////////////////////// // BranchTargetBuffer class // ///////////////////////////// struct BranchTargetBufferEntry { bool entry_valid { false }; Address instruction_address { Address::null() }; Address target_address { Address::null() }; BranchType branch_type { BranchType::UNDEFINED }; }; class BranchTargetBuffer final : public QObject { Q_OBJECT public: // Constructors & Destructor explicit BranchTargetBuffer(const uint8_t number_of_bits); private: // Internal functions uint8_t init_number_of_bits(const uint8_t b) const; public: // General functions uint8_t get_number_of_bits() const; uint16_t calculate_index(const Address instruction_address) const; BranchTargetBufferEntry get_entry(const Address instruction_address) const; void update( const Address instruction_address, const Address target_address, const BranchType branch_type); void clear(); signals: void btb_row_updated(uint16_t index, BranchTargetBufferEntry btb_entry) const; private: // Internal variables const uint8_t number_of_bits; std::vector btb; }; ///////////////////// // Predictor class // ///////////////////// struct PredictionInput { Instruction instruction {}; uint16_t bhr_value { 0 }; Address instruction_address { Address::null() }; Address target_address { Address::null() }; }; struct PredictionFeedback { Instruction instruction {}; uint16_t bhr_value { 0 }; Address instruction_address { Address::null() }; Address target_address { Address::null() }; BranchResult result { BranchResult::UNDEFINED }; BranchType branch_type { BranchType::UNDEFINED }; }; struct PredictionStatistics { float accuracy { 0 }; // 0 - 100 % uint32_t total { 0 }; uint32_t correct { 0 }; uint32_t wrong { 0 }; }; struct BranchHistoryTableEntry { PredictorState state { PredictorState::UNDEFINED }; PredictionStatistics stats {}; // Per-entry statistics }; class Predictor : public QObject { Q_OBJECT public: // Constructors & Destructor Predictor( uint8_t number_of_bht_addr_bits, uint8_t number_of_bht_bits, PredictorState initial_state); virtual ~Predictor() = default; protected: // Internal functions uint8_t init_number_of_bht_addr_bits(uint8_t b) const; uint8_t init_number_of_bht_bits(uint8_t b) const; BranchResult convert_state_to_prediction(PredictorState state) const; void update_stats(bool prediction_was_correct); void update_bht_stats(uint16_t bht_index, bool prediction_was_correct); public: // General functions uint16_t calculate_bht_index(const uint16_t bhr_value, const Address instruction_address) const; virtual PredictorType get_type() const = 0; virtual BranchResult predict(PredictionInput input) = 0; // Function which handles all actions // ties to making a branch prediction virtual void update(PredictionFeedback feedback) = 0; // Update predictor based on jump / branch // result void clear_stats(); void clear_bht_stats(); void clear_bht_state(); void clear(); void flush(); signals: void stats_updated(PredictionStatistics stats) const; void bht_row_updated(uint16_t index, BranchHistoryTableEntry entry) const; protected: // Internal variables const uint8_t number_of_bht_addr_bits; // Number of Branch History Table (BHT) bits taken from // instruction address const uint8_t number_of_bht_bits; // Number of Branch History Table (BHT) bits const PredictorState initial_state; PredictionStatistics stats; // Total predictor statistics std::vector bht; // Branch History Table (BHT) }; // Static Predictor - Always predicts not taking the branch class PredictorAlwaysNotTaken final : public Predictor { public: // Constructors & Destructor PredictorAlwaysNotTaken(); public: // General functions PredictorType get_type() const override { return PredictorType::ALWAYS_NOT_TAKEN; }; BranchResult predict(PredictionInput input) override; void update(PredictionFeedback feedback) override; }; // Static Predictor - Always predicts taking the branch class PredictorAlwaysTaken final : public Predictor { public: // Constructors & Destructor PredictorAlwaysTaken(); public: // General functions PredictorType get_type() const override { return PredictorType::ALWAYS_TAKEN; }; BranchResult predict(PredictionInput input) override; void update(PredictionFeedback feedback) override; }; // Static Predictor - Backward Taken Forward Not Taken class PredictorBTFNT final : public Predictor { public: // Constructors & Destructor PredictorBTFNT(); public: // General functions PredictorType get_type() const override { return PredictorType::BTFNT; }; BranchResult predict(PredictionInput input) override; void update(PredictionFeedback feedback) override; }; // Dynamic Predictor - Smith 1 Bit class PredictorSmith1Bit final : public Predictor { public: // Constructors & Destructor PredictorSmith1Bit( uint8_t number_of_bht_addr_bits, uint8_t number_of_bht_bits, PredictorState initial_state); public: // General functions PredictorType get_type() const override { return PredictorType::SMITH_1_BIT; }; BranchResult predict(PredictionInput input) override; void update(PredictionFeedback feedback) override; }; // Dynamic Predictor - Smith 2 Bit class PredictorSmith2Bit final : public Predictor { public: // Constructors & Destructor PredictorSmith2Bit( uint8_t number_of_bht_addr_bits, uint8_t number_of_bht_bits, PredictorState initial_state); public: // General functions PredictorType get_type() const override { return PredictorType::SMITH_2_BIT; }; BranchResult predict(PredictionInput input) override; void update(PredictionFeedback feedback) override; }; // Dynamic Predictor - Smith 2 Bit with hysteresis class PredictorSmith2BitHysteresis final : public Predictor { public: // Constructors & Destructor PredictorSmith2BitHysteresis( uint8_t number_of_bht_addr_bits, uint8_t number_of_bht_bits, PredictorState initial_state); public: // General functions PredictorType get_type() const override { return PredictorType::SMITH_2_BIT_HYSTERESIS; }; BranchResult predict(PredictionInput input) override; void update(PredictionFeedback feedback) override; }; /////////////////////////// // BranchPredictor class // /////////////////////////// class BranchPredictor : public QObject { Q_OBJECT public: // Constructors & Destructor explicit BranchPredictor( bool enabled = false, PredictorType predictor_type = PredictorType::SMITH_1_BIT, PredictorState initial_state = PredictorState::NOT_TAKEN, uint8_t number_of_btb_bits = 2, uint8_t number_of_bhr_bits = 0, uint8_t number_of_bht_addr_bits = 2); ~BranchPredictor(); private: // Internal functions uint8_t init_number_of_btb_bits(const uint8_t b) const; uint8_t init_number_of_bhr_bits(const uint8_t b) const; uint8_t init_number_of_bht_addr_bits(const uint8_t b) const; uint8_t init_number_of_bht_bits(const uint8_t b_bhr, const uint8_t b_addr) const; public: // General functions bool get_enabled() const; PredictorType get_predictor_type() const; QStringView get_predictor_name() const; PredictorState get_initial_state() const; uint8_t get_number_of_btb_bits() const; uint8_t get_number_of_bhr_bits() const; uint8_t get_number_of_bht_addr_bits() const; uint8_t get_number_of_bht_bits() const; const PredictionStatistics *get_stats() const; void increment_jumps(); void increment_mispredictions(); Address predict_next_pc_address(const Instruction instruction, const Address instruction_address) const; void update( const Instruction instruction, const Address instruction_address, const Address target_address, const BranchType branch_type, const BranchResult result); void clear(); void flush(); signals: void total_stats_updated(PredictionStatistics total_stats); void prediction_done( uint16_t btb_index, uint16_t bht_index, PredictionInput input, BranchResult result, BranchType branch_type) const; void update_done(uint16_t btb_index, uint16_t bht_index, PredictionFeedback feedback) const; void predictor_stats_updated(PredictionStatistics stats) const; void predictor_bht_row_updated(uint16_t index, BranchHistoryTableEntry entry) const; void bhr_updated(uint8_t number_of_bhr_bits, uint16_t register_value) const; void btb_row_updated(uint16_t index, BranchTargetBufferEntry btb_entry) const; void cleared() const; // All infomration was reset void flushed() const; // Only BHT state and BTB rows were reset private: // Internal variables bool enabled { false }; PredictionStatistics total_stats; Predictor *predictor; BranchHistoryRegister *bhr; BranchTargetBuffer *btb; const PredictorState initial_state; const uint8_t number_of_btb_bits; // Number of bits for addressing Branch Target Buffer (all // taken from instruction address) const uint8_t number_of_bhr_bits; // Number of bits in Branch History Register const uint8_t number_of_bht_addr_bits; // Number of bits in Branch History Table which are taken // from instruction address const uint8_t number_of_bht_bits; // = number_of_bhr_bits + number_of_bht_addr_bits }; } // namespace machine #endif // PREDICTOR_H ================================================ FILE: src/machine/predictor_types.h ================================================ #ifndef PREDICTOR_TYPES_H #define PREDICTOR_TYPES_H namespace machine { Q_NAMESPACE // Should not exceed 16, because uint16_t is used for addressing #define BP_MAX_BTB_BITS 8 #define BP_MAX_BHR_BITS 8 #define BP_MAX_BHT_ADDR_BITS 8 #define BP_MAX_BHT_BITS (BP_MAX_BHT_ADDR_BITS + BP_MAX_BHT_ADDR_BITS) enum class BranchType { JUMP, // JAL, JALR - Unconditional BRANCH, // BXX - Conditional UNDEFINED }; Q_ENUM_NS(machine::BranchType) enum class BranchResult { NOT_TAKEN, TAKEN, UNDEFINED }; Q_ENUM_NS(machine::BranchResult) enum class PredictorType { ALWAYS_NOT_TAKEN, ALWAYS_TAKEN, BTFNT, // Backward Taken, Forward Not Taken SMITH_1_BIT, SMITH_2_BIT, SMITH_2_BIT_HYSTERESIS, UNDEFINED }; Q_ENUM_NS(machine::PredictorType) enum class PredictorState { NOT_TAKEN, // Smith 1 bit TAKEN, // Smith 1 bit STRONGLY_NOT_TAKEN, // Smith 2 bit WEAKLY_NOT_TAKEN, // Smith 2 bit WEAKLY_TAKEN, // Smith 2 bit STRONGLY_TAKEN, // Smith 2 bit UNDEFINED }; Q_ENUM_NS(machine::PredictorState) } // namespace machine #endif // PREDICTOR_TYPES_H ================================================ FILE: src/machine/programloader.cpp ================================================ #include "programloader.h" #include "common/endian.h" #include "common/logging.h" #include "simulator_exception.h" #include #include #include // This is a workaround to ignore libelfin ref-counting cycle. #ifdef __SANITIZE_ADDRESS__ #include #endif LOG_CATEGORY("machine.ProgramLoader"); using namespace machine; constexpr int EM_RISCV = 243; class MemLoader : public elf::loader { public: MemLoader(const QString &fname) : file(fname), mapped(nullptr), size(0) { if (!file.open(QIODevice::ReadOnly | QIODevice::Unbuffered)) { throw SIMULATOR_EXCEPTION( Input, QString("Can't open input elf file for reading (") + fname + QString(")"), file.errorString()); } size = file.size(); mapped = file.map(0, size); if (mapped == nullptr) { throw SIMULATOR_EXCEPTION( Input, QString("Can't mmap input elf file (") + fname + QString(")"), file.errorString()); } } ~MemLoader() override { close(); } void close() { if (mapped != nullptr) { file.unmap(mapped); mapped = nullptr; } if (file.isOpen()) { file.close(); } } const void *load(off_t offset, size_t len) override { if ((size_t)offset + len > (size_t)size) { throw SANITY_EXCEPTION("ELF loader requested offset exceeds file size"); } return mapped + offset; } private: QFile file; unsigned char *mapped; std::int64_t size; }; ProgramLoader::ProgramLoader(const QString &file) { try { #ifdef __SANITIZE_ADDRESS__ __lsan_disable(); #endif elf_file = elf::elf(std::make_shared(file)); #ifdef __SANITIZE_ADDRESS__ __lsan_enable(); #endif } catch (const std::exception &e) { #ifdef __SANITIZE_ADDRESS__ __lsan_enable(); #endif throw SIMULATOR_EXCEPTION(Input, "Elf library initialization failed", e.what()); } const auto &hdr = elf_file.get_hdr(); executable_entry = Address(hdr.entry); if (hdr.type != elf::et::exec) { throw SIMULATOR_EXCEPTION(Input, "Invalid input file type", ""); } if (hdr.machine != EM_RISCV) { throw SIMULATOR_EXCEPTION(Input, "Invalid input file architecture", ""); } if (hdr.ei_class == elf::elfclass::_32) { LOG("Loaded executable: 32bit"); architecture_type = ARCH32; } else if (hdr.ei_class == elf::elfclass::_64) { LOG("Loaded executable: 64bit"); architecture_type = ARCH64; WARN("64bit simulation is not fully supported."); } else { throw SIMULATOR_EXCEPTION( Input, "Unsupported architecture type." "This simulator only supports 32bit and 64bit CPUs.", ""); } for (const auto &seg : elf_file.segments()) { if (seg.get_hdr().type == elf::pt::load) { load_segments.push_back(seg); } } } ProgramLoader::ProgramLoader(const char *file) : ProgramLoader(QString::fromLocal8Bit(file)) {} ProgramLoader::~ProgramLoader() { // This is a fix for upstream issue where libelf creates a ref-counting cycle. auto loader = elf_file.get_loader(); if (loader) { auto mem_loader = std::dynamic_pointer_cast(loader); if (mem_loader) { mem_loader->close(); } } } void ProgramLoader::to_memory(Memory *mem) { for (const auto &seg : load_segments) { uint64_t base_address = seg.get_hdr().vaddr; const char *data = (const char *)seg.data(); size_t filesz = seg.get_hdr().filesz; for (size_t i = 0; i < filesz; i++) { memory_write_u8(mem, base_address + i, (uint8_t)data[i]); } } } Address ProgramLoader::end() { uint64_t last = 0; for (const auto &seg : load_segments) { uint64_t end_addr = seg.get_hdr().vaddr + seg.get_hdr().filesz; if (end_addr > last) { last = end_addr; } } return Address(last + 0x10); // We add offset so we are sure that also // pipeline is empty TODO propagate address // deeper } Address ProgramLoader::get_executable_entry() const { return executable_entry; } SymbolTable *ProgramLoader::get_symbol_table() { auto *p_st = new SymbolTable(); for (const auto &sec : elf_file.sections()) { if (sec.get_hdr().type == elf::sht::symtab) { for (const auto &sym : sec.as_symtab()) { const auto &d = sym.get_data(); p_st->add_symbol(sym.get_name().c_str(), d.value, d.size, d.info, d.other); } } } return p_st; } Endian ProgramLoader::get_endian() const { if (elf_file.get_hdr().ei_data == elf::elfdata::lsb) { return LITTLE; } else if (elf_file.get_hdr().ei_data == elf::elfdata::msb) { return BIG; } else { throw SIMULATOR_EXCEPTION( Input, "ELF header e_ident malformed." "Unknown value of the byte EI_DATA." "Expected value little (=1) or big (=2).", ""); } } ArchitectureType ProgramLoader::get_architecture_type() const { return architecture_type; } ================================================ FILE: src/machine/programloader.h ================================================ #ifndef PROGRAM_H #define PROGRAM_H #include "common/endian.h" #include "memory/backend/memory.h" #include "symboltable.h" #include #include #include #include #include namespace machine { enum ArchitectureType { ARCH32, ARCH64, }; class ProgramLoader { public: explicit ProgramLoader(const char *file); explicit ProgramLoader(const QString &file); ~ProgramLoader(); void to_memory(Memory *mem); // Writes all loaded sections to memory TODO: // really to memory ??? Address end(); // Return address after which there is no more code for // sure Address get_executable_entry() const; SymbolTable *get_symbol_table(); Endian get_endian() const; /** Tells whether the executable is 32bit or 64bit. */ ArchitectureType get_architecture_type() const; private: elf::elf elf_file; ArchitectureType architecture_type; Address executable_entry; std::vector load_segments; }; } // namespace machine #endif // PROGRAM_H ================================================ FILE: src/machine/programloader.test.cpp ================================================ #include "programloader.test.h" #include "machine/instruction.h" #include "machine/memory/memory_utils.h" #include "machine/programloader.h" #include "memory/backend/memory.h" using namespace machine; // This is common program start (initial value of program counter) #define PC_INIT 0x00000200 const char *EXECUTABLE_NAME = "data"; void TestProgramLoader::program_loader() { if (not QFile::exists(EXECUTABLE_NAME)) { QSKIP("Executable is not present, cannot test program loader."); } ProgramLoader pl(EXECUTABLE_NAME); Memory m(BIG); pl.to_memory(&m); // addi $1, $0, 6 // QCOMPARE(Instruction(memory_read_u32(&m, PC_INIT)), Instruction(8, 0, 1, 6)); // j (8)0020000 (only 28 bits are used and they are logically shifted left // by 2) // QCOMPARE(Instruction(memory_read_u32(&m, PC_INIT + 4)), Instruction(2, Address(0x20000 >> // 2))); // TODO add some more code to data and do more compares (for example more // sections) } QTEST_APPLESS_MAIN(TestProgramLoader) ================================================ FILE: src/machine/programloader.test.h ================================================ #ifndef PROGRAMLOADER_TEST_H #define PROGRAMLOADER_TEST_H #include class TestProgramLoader : public QObject { Q_OBJECT public slots: void program_loader(); }; #endif // PROGRAMLOADER_TEST_H ================================================ FILE: src/machine/register_value.h ================================================ #ifndef REGISTER_VALUE_H #define REGISTER_VALUE_H #include "machine/machineconfig.h" #include namespace machine { /* * Register size configuration * * TODO: make compile time option */ using register_storage_t = uint64_t; /** * Represents a value stored in register * * Register value is semantically considered to be only an array of bits * and with no meaning assumed, therefore no operations are implemented * and value has to be interpreted as numerical value. * * By default, registers are initialized to zero. */ class RegisterValue { public: /** * * NOTE ON IMPLICIT CONVERSION: * Implicit conversion from unsigned integer is allowed as RegisterValue * as it essentially mean to forget the meaning of the value. Reverse * direction is always explicit. * * Constructor needs to be defined even for uint32_t as cpp cannot decide * whether to use uint64_t or int32_t. */ constexpr inline RegisterValue(uint64_t value) // NOLINT(google-explicit-constructor) : data(value) {}; // Must be present to avoid ambiguity. constexpr inline RegisterValue(uint32_t value) // NOLINT(google-explicit-constructor) : data(value) {}; constexpr inline RegisterValue() : data(0) {}; constexpr inline RegisterValue(const RegisterValue &other) = default; constexpr inline RegisterValue &operator=(const RegisterValue &other) = default; /* Sign-extending constructors */ constexpr inline RegisterValue(int64_t value) // NOLINT(google-explicit-constructor) : data(value) {}; constexpr inline RegisterValue(int32_t value) // NOLINT(google-explicit-constructor) : data(value) {}; constexpr inline RegisterValue(int16_t value) // NOLINT(google-explicit-constructor) : data(value) {}; constexpr inline RegisterValue(int8_t value) // NOLINT(google-explicit-constructor) : data(value) {}; [[nodiscard]] constexpr inline uint64_t as_xlen(Xlen xlen) const { switch (xlen) { case Xlen::_32: return as_u32(); case Xlen::_64: return as_u64(); default: UNREACHABLE } } [[nodiscard]] constexpr inline int8_t as_i8() const { return (int8_t)data; }; [[nodiscard]] constexpr inline uint8_t as_u8() const { return (uint8_t)data; }; [[nodiscard]] constexpr inline int16_t as_i16() const { return (int16_t)data; }; [[nodiscard]] constexpr inline uint16_t as_u16() const { return (uint16_t)data; }; [[nodiscard]] constexpr inline int32_t as_i32() const { return (int32_t)data; }; [[nodiscard]] constexpr inline uint32_t as_u32() const { return (uint32_t)data; }; [[nodiscard]] constexpr inline int64_t as_i64() const { return (int64_t)data; }; [[nodiscard]] constexpr inline uint64_t as_u64() const { return (uint64_t)data; }; constexpr explicit operator int8_t() const { return as_i8(); }; constexpr explicit operator uint8_t() const { return as_u8(); }; constexpr explicit operator int16_t() const { return as_i16(); }; constexpr explicit operator uint16_t() const { return as_u16(); }; constexpr explicit operator int32_t() const { return as_i32(); }; constexpr explicit operator uint32_t() const { return as_u32(); }; constexpr explicit operator int64_t() const { return as_i64(); }; constexpr explicit operator uint64_t() const { return as_u64(); }; /** * Equality operator is implemented as bit by bit comparison is reasonable * for bit array. * It is necessary to make gp-register array comparable. */ constexpr inline bool operator==(const RegisterValue &other) const { return data == other.data; } constexpr inline bool operator!=(const RegisterValue &other) const { return !(other == *this); } private: register_storage_t data; }; } // namespace machine Q_DECLARE_METATYPE(machine::RegisterValue) #endif // REGISTER_VALUE_H ================================================ FILE: src/machine/registers.cpp ================================================ #include "registers.h" #include "memory/address.h" #include "simulator_exception.h" using namespace machine; // TODO should this be configurable? ////////////////////////////////////////////////////////////////////////////// /// Program counter initial value #define PC_INIT 0x00000200_addr #define SP_INIT 0xbfffff00_addr ////////////////////////////////////////////////////////////////////////////// Registers::Registers() : QObject() { reset(); } Registers::Registers(const Registers &orig) : QObject() { this->pc = orig.read_pc(); this->gp = orig.gp; } Address Registers::read_pc() const { return this->pc; } void Registers::write_pc(machine::Address address) { if (address.get_raw() % 4) { throw SIMULATOR_EXCEPTION( UnalignedJump, "Trying to jump to unaligned address", QString::number(address.get_raw(), 16)); } this->pc = address; emit pc_update(this->pc); } RegisterValue Registers::read_gp(RegisterId reg) const { if (reg == 0) { return { 0 }; // $0 always reads as 0 } RegisterValue value = read_gp_internal(reg); emit gp_read(reg, value); return value; } RegisterValue Registers::read_gp_internal(RegisterId reg) const { if (reg == 0) { return { 0 }; // $0 always reads as 0 } return gp.at(reg); } void Registers::write_gp(RegisterId reg, RegisterValue value) { if (reg == 0) { return; // Skip write to $0 } this->gp.at(reg) = value; emit gp_update(reg, value); } bool Registers::operator==(const Registers &c) const { if (read_pc() != c.read_pc()) { return false; } if (this->gp != c.gp) { return false; } return true; } bool Registers::operator!=(const Registers &c) const { return !this->operator==(c); } void Registers::reset() { write_pc(PC_INIT); // Initialize to beginning program section for (int i = 1; i < 32; i++) { write_gp(i, 0); } write_gp(2_reg, SP_INIT.get_raw()); // initialize to safe RAM area - // corresponds to Linux } ================================================ FILE: src/machine/registers.h ================================================ #ifndef REGISTERS_H #define REGISTERS_H #include "memory/address.h" #include "register_value.h" #include "simulator_exception.h" #include #include #include namespace machine { /** * General-purpose register count */ constexpr size_t REGISTER_COUNT = 32; /** * General-purpose register identifier */ class RegisterId { public: inline constexpr RegisterId(uint8_t value); // NOLINT(google-explicit-constructor) inline RegisterId(); constexpr operator size_t() const { return data; }; // NOLINT(google-explicit-constructor) private: uint8_t data; }; inline constexpr RegisterId::RegisterId(uint8_t value) : data(value) { // Bounds on the id are checked at creation time and its value is immutable. // Therefore, all check at when used are redundant. // Main advantage is, that possible errors will appear when creating the // value, which is probably close to the bug source. SANITY_ASSERT( value < REGISTER_COUNT, QString("Trying to create register id for out-of-bounds register ") + QString::number(data)); } inline RegisterId::RegisterId() : RegisterId(0) {} inline RegisterId operator""_reg(unsigned long long value) { return { static_cast(value) }; } /** * Register file */ class Registers : public QObject { Q_OBJECT public: Registers(); Registers(const Registers &); Address read_pc() const; // Return current value of program counter void write_pc(Address address); // Absolute jump in program counter RegisterValue read_gp(RegisterId reg) const; // Read general-purpose // register RegisterValue read_gp_internal(RegisterId reg) const; // For use from GUI. void write_gp(RegisterId reg, RegisterValue value); // Write general-purpose // register bool operator==(const Registers &c) const; bool operator!=(const Registers &c) const; void reset(); // Reset all values to zero (except pc) signals: void pc_update(Address val); void gp_update(RegisterId reg, RegisterValue val); void gp_read(RegisterId reg, RegisterValue val) const; private: /** * General purpose registers * * Zero register is always zero, but is allocated to avoid off-by-one. * Getters and setters will never try to read or write zero register. */ std::array gp {}; Address pc {}; // program counter }; } // namespace machine Q_DECLARE_METATYPE(machine::Registers) #endif // REGISTERS_H ================================================ FILE: src/machine/registers.test.cpp ================================================ #include "registers.test.h" #include "machine/registers.h" using namespace machine; void TestRegisters::registers_gp0() { Registers r; QCOMPARE(r.read_gp(0), RegisterValue(0)); r.write_gp(0, 0xff); QCOMPARE(r.read_gp(0), RegisterValue(0)); } void TestRegisters::registers_rw_gp() { Registers r; for (int i = 1; i < 32; i++) { r.write_gp(i, 0xf00 + i); QCOMPARE(r.read_gp(i), RegisterValue(0xf00 + i)); } } void TestRegisters::registers_compare() { Registers r1, r2; QCOMPARE(r1, r2); // General purpose register r1.write_gp(1, 24); QVERIFY(r1 != r2); r2.write_gp(1, 24); QCOMPARE(r1, r2); // Program counter r1.write_pc(r1.read_pc() + 4); QVERIFY(r1 != r2); r2.write_pc(r2.read_pc() + 4); QCOMPARE(r1, r2); // Now let's try copy (and verify only with gp this time) Registers r3(r1); QCOMPARE(r3, r1); r3.write_gp(12, 19); QVERIFY(r1 != r3); r1.write_gp(12, 19); QCOMPARE(r3, r1); } QTEST_APPLESS_MAIN(TestRegisters) ================================================ FILE: src/machine/registers.test.h ================================================ #ifndef REGISTERS_TEST_H #define REGISTERS_TEST_H #include class TestRegisters : public QObject { Q_OBJECT public slots: static void registers_gp0(); static void registers_rw_gp(); static void registers_compare(); }; #endif // REGISTERS_TEST_H ================================================ FILE: src/machine/simulator_exception.cpp ================================================ #include "simulator_exception.h" #include #include #include using namespace machine; SimulatorException::SimulatorException(QString reason, QString ext, QString file, int line) { this->name = "Exception"; this->reason = std::move(reason); this->ext = std::move(ext); this->file = std::move(file); this->line = line; this->cached_what = nullptr; } SimulatorException::~SimulatorException() { if (this->cached_what) { delete[] this->cached_what; } } const char *SimulatorException::what() const noexcept { if (this->cached_what) { return this->cached_what; } std::string message = this->msg(true).toStdString(); this->cached_what = new char[message.length() + 1]; std::strcpy(this->cached_what, message.c_str()); return this->cached_what; } QString SimulatorException::msg(bool pos) const { QString message; message += name; if (pos) { message += QString(" (") + QString(this->file) + QString(":") + QString::number(this->line) + QString(")"); } message += ": " + this->reason; if (!this->ext.isEmpty()) { message += QString(": "); message += this->ext; } return message; } #define EXCEPTION(NAME, PARENT) \ SimulatorException##NAME::SimulatorException##NAME( \ QString reason, QString ext, QString file, int line) \ : SimulatorException##PARENT(reason, ext, file, line) { \ name = #NAME; \ } SIMULATOR_EXCEPTIONS #undef EXCEPTION ================================================ FILE: src/machine/simulator_exception.h ================================================ #ifndef SIMULATOR_EXCEPTION_H #define SIMULATOR_EXCEPTION_H #include #include namespace machine { // Base exception for all machine ones class SimulatorException : public std::exception { public: SimulatorException(QString reason, QString ext, QString file, int line); ~SimulatorException(); const char *what() const noexcept override; QString msg(bool pos) const; protected: QString name, reason, ext, file; int line; private: mutable char *cached_what; }; /* This is list of all QtRvSim specific exceptions * * Input: * Exception durring input loading * Runtime: * Exceptions caused by machine invalid input or unsupported action * UnsupportedInstruction: * Decoded instruction is not supported. * This can be cause by really using some unimplemented instruction or because * of problems in instruction decode. UnsupportedAluOperation: Decoded ALU * operation is not supported This is basically same exception as * SimulatorExceptionUnsupportedInstruction but it is emmited from ALU when * executed and not before that. Overflow: Integer operation resulted to * overflow (or underflow as we are working with unsigned values) This is for * sure caused by program it self. UnalignedJump: Instruction is jumping to * unaligned address (ADDR % 4 != 0) This can be caused by bug or by user * program as it can be jumping relative to register This shouldn't be happening * with non-register jumps as those should be verified by compiler * UnknownMemoryControl: * Used unknown MemoryAccess control value (write_ctl or read_ctl) * This can be raised by invalid instruction but in such case we shoul raise * UnknownInstruction instead So this should signal just some QtRvSim bug. * OutOfMemoryAccess: * Trying to access address outside of the memory * As we are simulating whole 32bit memory address space then this is most * probably QtRvSim bug if raised not program. Sanity: This is sanity check * exception * PageFault: * A page-fault occurs during virtual memory translation when the requested * access cannot be satisfied. Typical causes: * - No PTE present for the accessed virtual page (page not mapped). * - PTE is present but not valid (V == 0). * - Permissions violation (access type not allowed by PTE: read/write/execute). * - Malformed or unexpected PTE contents (e.g. non-leaf with R/W/X set). * - Wrong privilege level or ASID mismatch. * PageFault is a runtime exception raised by the page-table walker and * is recoverable after the page-fault handler allocates pages or installs * mappings (demand paging). */ #define SIMULATOR_EXCEPTIONS \ EXCEPTION(Input, ) \ EXCEPTION(Runtime, ) \ EXCEPTION(UnsupportedInstruction, Runtime) \ EXCEPTION(UnsupportedAluOperation, Runtime) \ EXCEPTION(Overflow, Runtime) \ EXCEPTION(UnalignedJump, Runtime) \ EXCEPTION(UnknownMemoryControl, Runtime) \ EXCEPTION(OutOfMemoryAccess, Runtime) \ EXCEPTION(PageFault, Runtime) \ EXCEPTION(Sanity, ) \ EXCEPTION(SyscallUnknown, Runtime) #define EXCEPTION(NAME, PARENT) \ class SimulatorException##NAME : public SimulatorException##PARENT { \ public: \ SimulatorException##NAME(QString reason, QString ext, QString file, int line); \ }; SIMULATOR_EXCEPTIONS #undef EXCEPTION // This is helper macro for throwing QtRvSim exceptions #define SIMULATOR_EXCEPTION(TYPE, REASON, EXT) \ (machine::SimulatorException##TYPE(QString(REASON), QString(EXT), QString(__FILE__), __LINE__)) #define SANITY_EXCEPTION(MSG) \ SIMULATOR_EXCEPTION( \ Sanity, "Internal error", \ "An internal error occurred in the simulator. We are sorry for the inconvenience." \ "To help get the simulator fixed ASAP, please report this incident to your " \ "teacher and/or file an issue at\n\n" \ "https://github.com/cvut/qtrvsim/issues.\n\n" \ "Please attach the program you were executing, used configuration of the " \ "simulator, description of steps you have taken and a copy of the following " \ "message:\n\n" #MSG) // Sanity comparison potentially throwing SimulatorExceptionSanity #define SANITY_ASSERT(COND, MSG) \ do { \ if (!(COND)) throw SANITY_EXCEPTION("Sanity check failed (" #COND "):" #MSG); \ } while (false) } // namespace machine #endif // SIMULATOR_EXCEPTION_H ================================================ FILE: src/machine/symboltable.cpp ================================================ #include "symboltable.h" #include using namespace machine; SymbolTableEntry::SymbolTableEntry( QString name, SymbolValue value, SymbolSize size, SymbolInfo info, SymbolOther other) : name(std::move(name)) , value(value) , size(size) , info(info) , other(other) {} SymbolTable::SymbolTable(QObject *parent) : QObject(parent) , map_name_to_symbol() , map_value_to_symbol() {} SymbolTable::~SymbolTable() { map_name_to_symbol.clear(); // Does not own data. auto iter = map_value_to_symbol.begin(); while (iter != map_value_to_symbol.end()) { const SymbolTableEntry *p_entry = iter.value(); iter = map_value_to_symbol.erase(iter); // Advances iterator. delete p_entry; } } void SymbolTable::add_symbol( const QString &name, SymbolValue value, uint32_t size, unsigned char info, unsigned char other) { auto *p_entry = new SymbolTableEntry(name, value, size, info, other); map_value_to_symbol.insert(value, p_entry); map_name_to_symbol.insert(name, p_entry); } void SymbolTable::remove_symbol(const QString &name) { auto *p_entry = map_name_to_symbol.take(name); if (p_entry == nullptr) { return; } map_value_to_symbol.remove(p_entry->value, p_entry); delete p_entry; } void SymbolTable::set_symbol( const QString &name, SymbolValue value, uint32_t size, unsigned char info, unsigned char other) { remove_symbol(name); add_symbol(name, value, size, info, other); } // TODO cpp17 - return optional bool SymbolTable::name_to_value(SymbolValue &value, const QString &name) const { auto *p_entry = map_name_to_symbol.value(name); if (p_entry == nullptr) { value = 0; return false; } value = p_entry->value; return true; } // TODO cpp17 - return optional bool SymbolTable::location_to_name(QString &name, SymbolValue value) const { auto *p_entry = map_value_to_symbol.value(value); if (p_entry == nullptr) { name = ""; return false; } name = p_entry->name; return true; } QStringList SymbolTable::names() const { return map_name_to_symbol.keys(); } ================================================ FILE: src/machine/symboltable.h ================================================ #ifndef SYMBOLTABLE_H #define SYMBOLTABLE_H #include "utils.h" #include #include #include #include #include namespace machine { using SymbolValue = uint64_t; using SymbolSize = uint32_t; using SymbolInfo = unsigned char; using SymbolOther = unsigned char; struct SymbolTableEntry { SymbolTableEntry( QString name, SymbolValue value, SymbolSize size, SymbolInfo info = 0, SymbolOther other = 0); const QString name; const SymbolValue value; const SymbolSize size; const SymbolInfo info; const SymbolOther other; }; /** * ELF symbol table. * * Used tu jump to symbol and for integrated assembler. * To learn more, about ELF symbol table internal, I recommend this * [link](https://blogs.oracle.com/solaris/inside-elf-symbol-tables-v2). */ class SymbolTable : public QObject { Q_OBJECT public: explicit SymbolTable(QObject *parent = nullptr); ~SymbolTable() override; void add_symbol( const QString &name, SymbolValue value, SymbolSize size, SymbolInfo info = 0, SymbolOther other = 0); void set_symbol( const QString &name, SymbolValue value, SymbolSize size, SymbolInfo info = 0, SymbolOther other = 0); void remove_symbol(const QString &name); QStringList names() const; public slots: bool name_to_value(SymbolValue &value, const QString &name) const; /** * FIXME: This is design is flawed. The table can contain multiple names for * single location as it is multimap. */ bool location_to_name(QString &name, SymbolValue value) const; private: // QString cannot be made const, because it would not fit into QT gui API. QMap map_name_to_symbol; QMultiMap map_value_to_symbol; }; } // namespace machine #endif // SYMBOLTABLE_H ================================================ FILE: src/machine/tests/data/cache_test_performance_data.h ================================================ #ifndef CACHE_TEST_PERFORMANCE_DATA_H #define CACHE_TEST_PERFORMANCE_DATA_H #include #include using std::array; using std::tuple; /** * Cache hits amd misses recorded after creation of these test. * Values are invalidated by any change of testcases. * Values were not checked manually, failure of test indicates only a change * not a bug. */ constexpr std::array, 3270> cache_test_performance_data { { { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 18, 28 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 18, 28 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 18, 28 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 18, 28 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 18, 28 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 18, 28 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 18, 28 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 18, 28 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 35, 2 }, { 27, 3 }, { 25, 3 }, { 45, 3 }, { 26, 3 }, { 29, 3 }, { 48, 3 }, { 21, 3 }, { 27, 4 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 16, 30 }, { 18, 3 }, { 18, 3 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 33, 1 }, { 19, 1 }, { 19, 1 }, { 44, 2 }, { 19, 2 }, { 19, 2 }, { 0, 0 }, { 0, 0 }, { 0, 0 }, } }; #endif // CACHE_TEST_PERFORMANCE_DATA_H ================================================ FILE: src/machine/tests/utils/integer_decomposition.h ================================================ #ifndef INTEGER_DECOMPOSITION_H #define INTEGER_DECOMPOSITION_H #include "common/endian.h" #include #include using namespace machine; using std::array; using std::uint16_t; using std::uint32_t; using std::uint64_t; using std::uint8_t; template constexpr void decompose_int_to_array(array &dst, uint64_t value, Endian endian) { for (size_t i = 0; i < N; ++i) { T val = (T)(value >> (8 * sizeof(T) * i)); if (endian == LITTLE) { dst.at(i) = val; } else { dst.at(N - i - 1) = val; } } } struct IntegerDecomposition { uint64_t u64 {}; array u32 {}; array u16 {}; array u8 {}; constexpr IntegerDecomposition() = default; constexpr IntegerDecomposition(uint64_t value, Endian endian) { u64 = value; decompose_int_to_array(u32, value, endian); decompose_int_to_array(u16, value, endian); decompose_int_to_array(u8, value, endian); } constexpr bool operator==(const IntegerDecomposition &rhs) const { return u64 == rhs.u64 && u32 == rhs.u32 && u16 == rhs.u16 && u8 == rhs.u8; } constexpr bool operator!=(const IntegerDecomposition &rhs) const { return !(rhs == *this); } }; Q_DECLARE_METATYPE(IntegerDecomposition) #endif // INTEGER_DECOMPOSITION_H ================================================ FILE: src/machine/utils.h ================================================ #ifndef UTILS_H #define UTILS_H #include #include #include #include #include #include #include using std::size_t; #if __GNUC__ >= 7 #define FALLTROUGH __attribute__((fallthrough)); #else #define FALLTROUGH #endif /** * Raw byte in memory * - Intended for raw byte array */ typedef unsigned char byte; inline constexpr uint32_t sign_extend(uint16_t v) { return ((v & 0x8000) ? 0xFFFF0000 : 0) | v; } template void ignore(const T &) {} #define UNIMPLEMENTED throw std::logic_error("Unimplemented"); #define PANIC throw std::logic_error("The program panicked."); #define UNREACHABLE Q_UNREACHABLE(); #define UNUSED(arg) ignore(arg); /** * Annotate pointer ownership. * Smartpointer may be used in the future. */ #define OWNED /** * Test whether given address is aligned to the given type. * * @tparam Address type used to store the address * @tparam T type to check alignment * @param address address to check * @return true if is aligned */ template inline bool is_aligned_generic(Address address) { return static_cast(address) % std::alignment_of::value; } /** * Divide and round up */ template inline constexpr T1 divide_and_ceil(T1 divident, T2 divisor) { return ((divident + divisor - 1) / divisor); } /** * Rounds number `n` down to the multiple of `base`. * (To by used as better macro.) * * @tparam T1 type of n * @tparam T2 type of base */ template inline constexpr T1 round_down_to_multiple(T1 n, T2 base) { return (n / base) * base; } /** * Rounds number `n` up to the multiple of `base`. * (To by used as better macro.) * * @tparam T1 type of n * @tparam T2 type of base */ template inline constexpr T1 round_up_to_multiple(T1 n, T2 base) { return round_down_to_multiple(n + base, base); } #endif // UTILS_H ================================================ FILE: src/os_emulation/CMakeLists.txt ================================================ project(os_emulation DESCRIPTION "Simple emulation of a Linux like kernel") set(CMAKE_AUTOMOC ON) set(os_emulation_SOURCES ossyscall.cpp ) set(os_emulation_HEADERS ossyscall.h syscall_nr.h target_errno.h ) add_library(os_emulation STATIC ${os_emulation_SOURCES} ${os_emulation_HEADERS}) target_link_libraries(os_emulation PRIVATE ${QtLib}::Core) ================================================ FILE: src/os_emulation/ossyscall.cpp ================================================ #include "ossyscall.h" #include "machine/core.h" #include "machine/utils.h" #include "posix_polyfill.h" #include "syscall_nr.h" #include "target_errno.h" #include #include #include #include #include using namespace machine; using namespace osemu; // The copied from musl-libc #define TARGET_O_CREAT 0100 #define TARGET_O_EXCL 0200 #define TARGET_O_NOCTTY 0400 #define TARGET_O_TRUNC 01000 #define TARGET_O_APPEND 02000 #define TARGET_O_NONBLOCK 04000 #define TARGET_O_DSYNC 010000 #define TARGET_O_SYNC 04010000 #define TARGET_O_RSYNC 04010000 #define TARGET_O_DIRECTORY 0200000 #define TARGET_O_NOFOLLOW 0400000 #define TARGET_O_CLOEXEC 02000000 #define TARGET_O_PATH 010000000 #define TARGET_O_SYNC1 040000 #define TARGET_O_ACCMODE (03 | TARGET_O_PATH) #define TARGET_O_RDONLY 00 #define TARGET_O_WRONLY 01 #define TARGET_O_RDWR 02 #define TARGET_AT_FDCWD -100 static const QMap map_target_o_flags_to_o_flags = { #ifdef O_CREAT { TARGET_O_CREAT, O_CREAT }, #endif #ifdef O_EXCL { TARGET_O_EXCL, O_EXCL }, #endif #ifdef O_NOCTTY { TARGET_O_NOCTTY, O_NOCTTY }, #endif #ifdef O_TRUNC { TARGET_O_TRUNC, O_TRUNC }, #endif #ifdef O_APPEND { TARGET_O_APPEND, O_APPEND }, #endif #ifdef O_NONBLOCK { TARGET_O_NONBLOCK, O_NONBLOCK }, #endif #ifdef O_DSYNC { TARGET_O_DSYNC, O_DSYNC }, #endif #ifdef O_SYNC { TARGET_O_SYNC1, O_SYNC }, #endif #ifdef O_RSYNC { TARGET_O_SYNC1, O_RSYNC }, #endif #ifdef O_DIRECTORY { TARGET_O_DIRECTORY, O_DIRECTORY }, #endif #ifdef O_NOFOLLOW { TARGET_O_NOFOLLOW, O_NOFOLLOW }, #endif #ifdef O_CLOEXEC { TARGET_O_CLOEXEC, O_CLOEXEC }, #endif }; #if defined(S_IRUSR) & defined(S_IWUSR) & defined(S_IRGRP) & defined(S_IWGRP) & defined(S_IROTH) \ & defined(S_IWOTH) #define OPEN_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH) #else #define OPEN_MODE 0 #endif // The list copyied from musl-libc static const QMap errno_map = { #ifdef EPERM { EPERM, TARGET_EPERM }, #endif #ifdef ENOENT { ENOENT, TARGET_ENOENT }, #endif #ifdef ESRCH { ESRCH, TARGET_ESRCH }, #endif #ifdef EINTR { EINTR, TARGET_EINTR }, #endif #ifdef EIO { EIO, TARGET_EIO }, #endif #ifdef ENXIO { ENXIO, TARGET_ENXIO }, #endif #ifdef E2BIG { E2BIG, TARGET_E2BIG }, #endif #ifdef ENOEXEC { ENOEXEC, TARGET_ENOEXEC }, #endif #ifdef EBADF { EBADF, TARGET_EBADF }, #endif #ifdef ECHILD { ECHILD, TARGET_ECHILD }, #endif #ifdef EAGAIN { EAGAIN, TARGET_EAGAIN }, #endif #ifdef ENOMEM { ENOMEM, TARGET_ENOMEM }, #endif #ifdef EACCES { EACCES, TARGET_EACCES }, #endif #ifdef EFAULT { EFAULT, TARGET_EFAULT }, #endif #ifdef ENOTBLK { ENOTBLK, TARGET_ENOTBLK }, #endif #ifdef EBUSY { EBUSY, TARGET_EBUSY }, #endif #ifdef EEXIST { EEXIST, TARGET_EEXIST }, #endif #ifdef EXDEV { EXDEV, TARGET_EXDEV }, #endif #ifdef ENODEV { ENODEV, TARGET_ENODEV }, #endif #ifdef ENOTDIR { ENOTDIR, TARGET_ENOTDIR }, #endif #ifdef EISDIR { EISDIR, TARGET_EISDIR }, #endif #ifdef EINVAL { EINVAL, TARGET_EINVAL }, #endif #ifdef ENFILE { ENFILE, TARGET_ENFILE }, #endif #ifdef EMFILE { EMFILE, TARGET_EMFILE }, #endif #ifdef ENOTTY { ENOTTY, TARGET_ENOTTY }, #endif #ifdef ETXTBSY { ETXTBSY, TARGET_ETXTBSY }, #endif #ifdef EFBIG { EFBIG, TARGET_EFBIG }, #endif #ifdef ENOSPC { ENOSPC, TARGET_ENOSPC }, #endif #ifdef ESPIPE { ESPIPE, TARGET_ESPIPE }, #endif #ifdef EROFS { EROFS, TARGET_EROFS }, #endif #ifdef EMLINK { EMLINK, TARGET_EMLINK }, #endif #ifdef EPIPE { EPIPE, TARGET_EPIPE }, #endif #ifdef EDOM { EDOM, TARGET_EDOM }, #endif #ifdef ERANGE { ERANGE, TARGET_ERANGE }, #endif #ifdef ENOMSG { ENOMSG, TARGET_ENOMSG }, #endif #ifdef EIDRM { EIDRM, TARGET_EIDRM }, #endif #ifdef ECHRNG { ECHRNG, TARGET_ECHRNG }, #endif #ifdef EL2NSYNC { EL2NSYNC, TARGET_EL2NSYNC }, #endif #ifdef EL3HLT { EL3HLT, TARGET_EL3HLT }, #endif #ifdef EL3RST { EL3RST, TARGET_EL3RST }, #endif #ifdef ELNRNG { ELNRNG, TARGET_ELNRNG }, #endif #ifdef EUNATCH { EUNATCH, TARGET_EUNATCH }, #endif #ifdef ENOCSI { ENOCSI, TARGET_ENOCSI }, #endif #ifdef EL2HLT { EL2HLT, TARGET_EL2HLT }, #endif #ifdef EDEADLK { EDEADLK, TARGET_EDEADLK }, #endif #ifdef ENOLCK { ENOLCK, TARGET_ENOLCK }, #endif #ifdef EBADE { EBADE, TARGET_EBADE }, #endif #ifdef EBADR { EBADR, TARGET_EBADR }, #endif #ifdef EXFULL { EXFULL, TARGET_EXFULL }, #endif #ifdef ENOANO { ENOANO, TARGET_ENOANO }, #endif #ifdef EBADRQC { EBADRQC, TARGET_EBADRQC }, #endif #ifdef EBADSLT { EBADSLT, TARGET_EBADSLT }, #endif #ifdef EDEADLOCK { EDEADLOCK, TARGET_EDEADLOCK }, #endif #ifdef EBFONT { EBFONT, TARGET_EBFONT }, #endif #ifdef ENOSTR { ENOSTR, TARGET_ENOSTR }, #endif #ifdef ENODATA { ENODATA, TARGET_ENODATA }, #endif #ifdef ETIME { ETIME, TARGET_ETIME }, #endif #ifdef ENOSR { ENOSR, TARGET_ENOSR }, #endif #ifdef ENONET { ENONET, TARGET_ENONET }, #endif #ifdef ENOPKG { ENOPKG, TARGET_ENOPKG }, #endif #ifdef EREMOTE { EREMOTE, TARGET_EREMOTE }, #endif #ifdef ENOLINK { ENOLINK, TARGET_ENOLINK }, #endif #ifdef EADV { EADV, TARGET_EADV }, #endif #ifdef ESRMNT { ESRMNT, TARGET_ESRMNT }, #endif #ifdef ECOMM { ECOMM, TARGET_ECOMM }, #endif #ifdef EPROTO { EPROTO, TARGET_EPROTO }, #endif #ifdef EDOTDOT { EDOTDOT, TARGET_EDOTDOT }, #endif #ifdef EMULTIHOP { EMULTIHOP, TARGET_EMULTIHOP }, #endif #ifdef EBADMSG { EBADMSG, TARGET_EBADMSG }, #endif #ifdef ENAMETOOLONG { ENAMETOOLONG, TARGET_ENAMETOOLONG }, #endif #ifdef EOVERFLOW { EOVERFLOW, TARGET_EOVERFLOW }, #endif #ifdef ENOTUNIQ { ENOTUNIQ, TARGET_ENOTUNIQ }, #endif #ifdef EBADFD { EBADFD, TARGET_EBADFD }, #endif #ifdef EREMCHG { EREMCHG, TARGET_EREMCHG }, #endif #ifdef ELIBACC { ELIBACC, TARGET_ELIBACC }, #endif #ifdef ELIBBAD { ELIBBAD, TARGET_ELIBBAD }, #endif #ifdef ELIBSCN { ELIBSCN, TARGET_ELIBSCN }, #endif #ifdef ELIBMAX { ELIBMAX, TARGET_ELIBMAX }, #endif #ifdef ELIBEXEC { ELIBEXEC, TARGET_ELIBEXEC }, #endif #ifdef EILSEQ { EILSEQ, TARGET_EILSEQ }, #endif #ifdef ENOSYS { ENOSYS, TARGET_ENOSYS }, #endif #ifdef ELOOP { ELOOP, TARGET_ELOOP }, #endif #ifdef ERESTART { ERESTART, TARGET_ERESTART }, #endif #ifdef ESTRPIPE { ESTRPIPE, TARGET_ESTRPIPE }, #endif #ifdef ENOTEMPTY { ENOTEMPTY, TARGET_ENOTEMPTY }, #endif #ifdef EUSERS { EUSERS, TARGET_EUSERS }, #endif #ifdef ENOTSOCK { ENOTSOCK, TARGET_ENOTSOCK }, #endif #ifdef EDESTADDRREQ { EDESTADDRREQ, TARGET_EDESTADDRREQ }, #endif #ifdef EMSGSIZE { EMSGSIZE, TARGET_EMSGSIZE }, #endif #ifdef EPROTOTYPE { EPROTOTYPE, TARGET_EPROTOTYPE }, #endif #ifdef ENOPROTOOPT { ENOPROTOOPT, TARGET_ENOPROTOOPT }, #endif #ifdef EPROTONOSUPPORT { EPROTONOSUPPORT, TARGET_EPROTONOSUPPORT }, #endif #ifdef ESOCKTNOSUPPORT { ESOCKTNOSUPPORT, TARGET_ESOCKTNOSUPPORT }, #endif #ifdef EOPNOTSUPP { EOPNOTSUPP, TARGET_EOPNOTSUPP }, #endif #ifdef ENOTSUP { ENOTSUP, TARGET_ENOTSUP }, #endif #ifdef EPFNOSUPPORT { EPFNOSUPPORT, TARGET_EPFNOSUPPORT }, #endif #ifdef EAFNOSUPPORT { EAFNOSUPPORT, TARGET_EAFNOSUPPORT }, #endif #ifdef EADDRINUSE { EADDRINUSE, TARGET_EADDRINUSE }, #endif #ifdef EADDRNOTAVAIL { EADDRNOTAVAIL, TARGET_EADDRNOTAVAIL }, #endif #ifdef ENETDOWN { ENETDOWN, TARGET_ENETDOWN }, #endif #ifdef ENETUNREACH { ENETUNREACH, TARGET_ENETUNREACH }, #endif #ifdef ENETRESET { ENETRESET, TARGET_ENETRESET }, #endif #ifdef ECONNABORTED { ECONNABORTED, TARGET_ECONNABORTED }, #endif #ifdef ECONNRESET { ECONNRESET, TARGET_ECONNRESET }, #endif #ifdef ENOBUFS { ENOBUFS, TARGET_ENOBUFS }, #endif #ifdef EISCONN { EISCONN, TARGET_EISCONN }, #endif #ifdef ENOTCONN { ENOTCONN, TARGET_ENOTCONN }, #endif #ifdef EUCLEAN { EUCLEAN, TARGET_EUCLEAN }, #endif #ifdef ENOTNAM { ENOTNAM, TARGET_ENOTNAM }, #endif #ifdef ENAVAIL { ENAVAIL, TARGET_ENAVAIL }, #endif #ifdef EISNAM { EISNAM, TARGET_EISNAM }, #endif #ifdef EREMOTEIO { EREMOTEIO, TARGET_EREMOTEIO }, #endif #ifdef ESHUTDOWN { ESHUTDOWN, TARGET_ESHUTDOWN }, #endif #ifdef ETOOMANYREFS { ETOOMANYREFS, TARGET_ETOOMANYREFS }, #endif #ifdef ETIMEDOUT { ETIMEDOUT, TARGET_ETIMEDOUT }, #endif #ifdef ECONNREFUSED { ECONNREFUSED, TARGET_ECONNREFUSED }, #endif #ifdef EHOSTDOWN { EHOSTDOWN, TARGET_EHOSTDOWN }, #endif #ifdef EHOSTUNREACH { EHOSTUNREACH, TARGET_EHOSTUNREACH }, #endif #ifdef EWOULDBLOCK { EWOULDBLOCK, TARGET_EWOULDBLOCK }, #endif #ifdef EALREADY { EALREADY, TARGET_EALREADY }, #endif #ifdef EINPROGRESS { EINPROGRESS, TARGET_EINPROGRESS }, #endif #ifdef ESTALE { ESTALE, TARGET_ESTALE }, #endif #ifdef ECANCELED { ECANCELED, TARGET_ECANCELED }, #endif #ifdef ENOMEDIUM { ENOMEDIUM, TARGET_ENOMEDIUM }, #endif #ifdef EMEDIUMTYPE { EMEDIUMTYPE, TARGET_EMEDIUMTYPE }, #endif #ifdef ENOKEY { ENOKEY, TARGET_ENOKEY }, #endif #ifdef EKEYEXPIRED { EKEYEXPIRED, TARGET_EKEYEXPIRED }, #endif #ifdef EKEYREVOKED { EKEYREVOKED, TARGET_EKEYREVOKED }, #endif #ifdef EKEYREJECTED { EKEYREJECTED, TARGET_EKEYREJECTED }, #endif #ifdef EOWNERDEAD { EOWNERDEAD, TARGET_EOWNERDEAD }, #endif #ifdef ENOTRECOVERABLE { ENOTRECOVERABLE, TARGET_ENOTRECOVERABLE }, #endif #ifdef ERFKILL { ERFKILL, TARGET_ERFKILL }, #endif #ifdef EHWPOISON { EHWPOISON, TARGET_EHWPOISON }, #endif #ifdef EDQUOT { EDQUOT, TARGET_EDQUOT }, #endif }; uint32_t result_errno_if_error(uint32_t result) { if (result < (uint32_t)-4096) return result; result = (uint32_t)-errno_map.value(errno); if (!result) result = (uint32_t)-1; return result; } int status_from_result(uint32_t result) { if (result < (uint32_t)-4096) return 0; else return -result; } typedef int (OsSyscallExceptionHandler::*syscall_handler_t)( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6); struct mips_syscall_desc_t { const char *name; unsigned int args; syscall_handler_t handler; }; struct rv_syscall_desc_t { unsigned int args; syscall_handler_t handler; const char *name; }; static const rv_syscall_desc_t rv_syscall_args[] = { #include "syscallent.h" }; const unsigned rv_syscall_count = sizeof(rv_syscall_args) / sizeof(*rv_syscall_args); OsSyscallExceptionHandler::OsSyscallExceptionHandler( bool known_syscall_stop, bool unknown_syscall_stop, QString fs_root) : fd_mapping(3, FD_TERMINAL) { brk_limit = 0; anonymous_base = 0x60000000; anonymous_last = anonymous_base; this->known_syscall_stop = known_syscall_stop; this->unknown_syscall_stop = unknown_syscall_stop; this->fs_root = fs_root; } bool OsSyscallExceptionHandler::handle_exception( Core *core, Registers *regs, ExceptionCause excause, Address inst_addr, Address next_addr, Address jump_branch_pc, Address mem_ref_addr) { uint64_t syscall_num = regs->read_gp(17).as_u64(); const rv_syscall_desc_t *sdesc; RegisterValue a1 = 0, a2 = 0, a3 = 0, a4 = 0, a5 = 0, a6 = 0; uint64_t result; int status; FrontendMemory *mem_program = core->get_mem_program(); (void)mem_program; #if 1 printf( "Exception cause %d instruction PC 0x%08" PRIx64 " next PC 0x%08" PRIx64 " jump branch " "PC 0x%08" PRIx64 "registers PC 0x%08" PRIx64 " mem ref 0x%08" PRIx64 "\n", excause, inst_addr.get_raw(), next_addr.get_raw(), jump_branch_pc.get_raw(), regs->read_pc().get_raw(), mem_ref_addr.get_raw()); #else (void)excause; (void)inst_addr; (void)next_addr; (void)mem_ref_addr; (void)regs; (void)jump_branch_pc; #endif // handle Linux syscalls if (syscall_num < rv_syscall_count) { sdesc = &rv_syscall_args[syscall_num]; } else { throw SIMULATOR_EXCEPTION( SyscallUnknown, "System call number unknown ", QString::number(syscall_num)); } // sdesc is populated by syscall description matching syscall number. // if such syscall doesn't exist, this part is unreachable, because // of the above exception. // read out values from registers. Not sure I like this... // Maybe the handling functions themselves should do this on their own. a1 = regs->read_gp(10); a2 = regs->read_gp(11); a3 = regs->read_gp(12); a4 = regs->read_gp(13); a5 = regs->read_gp(14); a6 = regs->read_gp(15); #if 1 printf( "Syscall %s number %" PRId64 "/0x%" PRIx64 " a1=%" PRIu64 " a2=%" PRIu64 " a3=%" PRIu64 " a4=%" PRIu64 "\n", sdesc->name, syscall_num, syscall_num, a1.as_u64(), a2.as_u64(), a3.as_u64(), a4.as_u64()); #endif status = (this->*sdesc->handler)( result, core, syscall_num, a1.as_u64(), a2.as_u64(), a3.as_u64(), a4.as_u64(), a5.as_u64(), a6.as_u64()); if (known_syscall_stop) { emit core->stop_on_exception_reached(); } if (status < 0) { regs->write_gp(10, status); } else { regs->write_gp(10, result); } return true; } bool OsSyscallExceptionHandler::map_stdin_to_hostfile(const QString &hostpath) { if (hostpath.isEmpty()) return false; int hostfd = open(hostpath.toLatin1().data(), O_RDONLY); if (hostfd < 0) { fprintf(stderr, "map_stdin_to_hostfile: failed to open host file '%s' errno=%d", hostpath.toLatin1().data(), errno); return false; } if (fd_mapping.size() <= 0) { fd_mapping.push_back(FD_UNUSED); } // If there is an existing host fd mapped at target fd 0, close it (but don't close FD_TERMINAL/FD_UNUSED/FD_INVALID) int prev = fd_mapping[0]; if (prev >= 0 && prev != FD_TERMINAL && prev != FD_UNUSED && prev != FD_INVALID) { close(prev); } fd_mapping[0] = hostfd; return true; } int32_t OsSyscallExceptionHandler::write_mem( machine::FrontendMemory *mem, Address addr, const QVector &data, uint32_t count) { if ((uint32_t)data.size() < count) count = data.size(); for (uint32_t i = 0; i < count; i++) { mem->write_u8(addr, data[i]); addr += 1; } return count; } int32_t OsSyscallExceptionHandler::read_mem( machine::FrontendMemory *mem, Address addr, QVector &data, uint32_t count) { data.resize(count); for (uint32_t i = 0; i < count; i++) { data[i] = mem->read_u8(addr); addr += 1; } return count; } int32_t OsSyscallExceptionHandler::write_io(int fd, const QVector &data, uint32_t count) { if ((uint32_t)data.size() < count) count = data.size(); if (fd == FD_UNUSED) { return -1; } else if (fd == FD_TERMINAL) { for (uint32_t i = 0; i < count; i++) emit char_written(fd, data[i]); } else { count = write(fd, data.data(), count); } return result_errno_if_error(count); } int32_t OsSyscallExceptionHandler::read_io( int fd, QVector &data, uint32_t count, bool add_nl_at_eof) { data.resize(count); if ((uint32_t)data.size() < count) count = data.size(); if (fd == FD_UNUSED) { return -1; } else if (fd == FD_TERMINAL) { for (uint32_t i = 0; i < count; i++) { unsigned int byte; bool available = false; emit rx_byte_pool(fd, byte, available); if (!available) { // add final newline if there are no more data if (add_nl_at_eof) data[i] = '\n'; count = i + 1; break; } data[i] = byte; } } else { count = read(fd, data.data(), count); } if ((int32_t)count >= 0) data.resize(count); return result_errno_if_error(count); } int OsSyscallExceptionHandler::allocate_fd(int val) { int i; for (i = 0; i < fd_mapping.size(); i++) { if (fd_mapping[i] == FD_UNUSED) { fd_mapping[i] = val; return i; } } i = fd_mapping.size(); fd_mapping.resize(i + 1); fd_mapping[i] = val; return i; } int OsSyscallExceptionHandler::file_open(QString fname, int flags, int mode) { int targetfd, fd; int hostflags = 0; (void)mode; for (auto i = map_target_o_flags_to_o_flags.begin(); i != map_target_o_flags_to_o_flags.end(); i++) if (flags & i.key()) hostflags |= i.value(); switch (flags & TARGET_O_ACCMODE) { case TARGET_O_RDONLY: hostflags |= O_RDONLY; break; case TARGET_O_WRONLY: hostflags |= O_WRONLY; break; case TARGET_O_RDWR: hostflags |= O_RDWR; break; } if (fs_root.size() == 0) { return allocate_fd(FD_TERMINAL); } fname = filepath_to_host(fname); fd = open(fname.toLatin1().data(), hostflags, OPEN_MODE); if (fd >= 0) { targetfd = allocate_fd(fd); } else { targetfd = result_errno_if_error(fd); } return targetfd; } int OsSyscallExceptionHandler::targetfd_to_fd(int targetfd) { if (targetfd < 0) return FD_INVALID; if (targetfd >= fd_mapping.size()) return FD_INVALID; return fd_mapping.at(targetfd); } void OsSyscallExceptionHandler::close_fd(int targetfd) { if (targetfd <= fd_mapping.size()) fd_mapping[targetfd] = FD_UNUSED; } QString OsSyscallExceptionHandler::filepath_to_host(QString path) { int pos = 0; int prev; while (true) { if (((path.size() - pos == 2) && (path.mid(pos, -1) == "..")) || ((path.size() - pos > 2) && (path.mid(pos, 3) == "../"))) { if (pos == 0) { prev = 0; } else { if (pos == 1) prev = 0; else prev = path.lastIndexOf('/', pos - 2) + 1; } path.remove(prev, pos + 3 - prev); pos = prev; continue; } pos = path.indexOf('/', pos); if (pos == -1) break; pos += 1; } if ((path.size() >= 1) && path.at(0) == '/') path = fs_root + path; else path = fs_root + '/' + path; return path; } int OsSyscallExceptionHandler::syscall_default_handler( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { const rv_syscall_desc_t *sdesc = &rv_syscall_args[syscall_num]; #if 1 printf( "Unimplemented syscall %s number %" PRId64 "/0x%" PRIx64 " a1 %" PRId64 " a2 %" PRId64 " a3 %" PRId64 " a4 %" PRId64 "\n", sdesc->name, syscall_num, syscall_num, a1, a2, a3, a4); #endif (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; result = 0; if (unknown_syscall_stop) emit core->stop_on_exception_reached(); return TARGET_ENOSYS; } // void exit(int status); int OsSyscallExceptionHandler::do_sys_exit( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; result = 0; int status = a1; printf("sys_exit status %d\n", status); emit core->stop_on_exception_reached(); return 0; } // ssize_t writev(int fd, const struct iovec *iov, int iovcnt); int OsSyscallExceptionHandler::do_sys_writev( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; result = 0; int fd = a1; Address iov = Address(core->get_xlen_from_reg(a2)); int iovcnt = a3; FrontendMemory *mem = core->get_mem_data(); int32_t count; QVector data; printf("sys_writev to fd %d\n", fd); fd = targetfd_to_fd(fd); if (fd == FD_INVALID) { result = -TARGET_EINVAL; return 0; } while (iovcnt-- > 0) { Address iov_base = Address(mem->read_u32(iov)); uint32_t iov_len = mem->read_u32(iov + 4); iov += 8; read_mem(mem, iov_base, data, iov_len); count = write_io(fd, data, iov_len); if (count >= 0) { result += count; } else { if (result == 0) result = count; } if (count < (int32_t)iov_len) break; } return status_from_result(result); } // ssize_t write(int fd, const void *buf, size_t count); int OsSyscallExceptionHandler::do_sys_write( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; result = 0; int fd = a1; Address buf = Address(core->get_xlen_from_reg(a2)); int size = core->get_xlen_from_reg(a3); FrontendMemory *mem = core->get_mem_data(); int32_t count; QVector data; printf("sys_write to fd %d\n", fd); fd = targetfd_to_fd(fd); if (fd == FD_INVALID) { result = -TARGET_EINVAL; return 0; } read_mem(mem, buf, data, size); count = write_io(fd, data, size); result = count; return status_from_result(result); } // ssize_t readv(int fd, const struct iovec *iov, int iovcnt); int OsSyscallExceptionHandler::do_sys_readv( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; result = 0; int fd = a1; Address iov = Address(core->get_xlen_from_reg(a2)); int iovcnt = a3; FrontendMemory *mem = core->get_mem_data(); int32_t count; QVector data; printf("sys_readv to fd %d\n", fd); fd = targetfd_to_fd(fd); if (fd == FD_INVALID) { result = -TARGET_EINVAL; return 0; } while (iovcnt-- > 0) { Address iov_base = Address(mem->read_u32(iov)); uint32_t iov_len = mem->read_u32(iov + 4); iov += 8; count = read_io(fd, data, iov_len, true); if (count >= 0) { write_mem(mem, iov_base, data, count); result += count; } else { if (result == 0) result = count; } if (count < (int32_t)iov_len) break; } return status_from_result(result); } // ssize_t read(int fd, void *buf, size_t count); int OsSyscallExceptionHandler::do_sys_read( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; result = 0; int fd = a1; Address buf = Address(core->get_xlen_from_reg(a2)); int size = core->get_xlen_from_reg(a3); FrontendMemory *mem = core->get_mem_data(); int32_t count; QVector data; printf("sys_read to fd %d\n", fd); fd = targetfd_to_fd(fd); if (fd == FD_INVALID) { result = -TARGET_EINVAL; return 0; } result = 0; count = read_io(fd, data, size, true); if (count >= 0) { write_mem(mem, buf, data, size); } result = count; return status_from_result(result); } // int openat(int fd, const char *pathname, int flags, mode_t mode); int OsSyscallExceptionHandler::do_sys_openat( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; result = 0; if (int64_t(a1) != TARGET_AT_FDCWD) { printf("Unimplemented openat argument a1 %" PRId64 "\n", a1); if (unknown_syscall_stop) { emit core->stop_on_exception_reached(); } return TARGET_ENOSYS; } Address pathname_ptr = Address(core->get_xlen_from_reg(a2)); int flags = a3; int mode = a4; uint32_t ch; FrontendMemory *mem = core->get_mem_data(); printf("sys_open filename\n"); QString fname; while (true) { ch = mem->read_u8(pathname_ptr); pathname_ptr += 1; if (ch == 0) break; fname.append(QChar(ch)); } result = file_open(fname, flags, mode); return status_from_result(result); } // int close(int fd); int OsSyscallExceptionHandler::do_sys_close( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; result = 0; int fd = a1; printf("sys_close fd %d\n", fd); int targetfd = fd; fd = targetfd_to_fd(fd); if (fd == FD_INVALID) { result = -TARGET_EINVAL; return 0; } close(fd); close_fd(targetfd); return status_from_result(result); } // int ftruncate(int fd, off_t length); int OsSyscallExceptionHandler::do_sys_ftruncate( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; result = 0; int fd = a1; uint64_t length = core->get_xlen_from_reg(a2); if (core->get_xlen() == Xlen::_32) { length |= core->get_xlen_from_reg(a3) << 32; } printf("sys_ftruncate fd %d\n", fd); fd = targetfd_to_fd(fd); if (fd == FD_INVALID) { result = -TARGET_EINVAL; return 0; } result = result_errno_if_error(ftruncate(fd, length)); return status_from_result(result); } // int or void * brk(void *addr); int OsSyscallExceptionHandler::do_sys_brk( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; result = 0; uint32_t new_limit = a1; brk_limit = new_limit; result = brk_limit; return 0; } // void *mmap(void *addr, size_t length, int prot, // int flags, int fd, off_t pgoffset); int OsSyscallExceptionHandler::do_sys_mmap( uint64_t &result, Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) { (void)core; (void)syscall_num; (void)a1; (void)a2; (void)a3; (void)a4; (void)a5; (void)a6; // TODO: actually mmmap file, now that we can. result = 0; uint32_t addr = a1; uint32_t lenght = a2; uint32_t prot = a3; uint32_t flags = a4; uint32_t fd = a5; uint64_t offset = a6; printf( "sys_mmap addr = 0x%08lx lenght= 0x%08lx prot = 0x%08lx flags = " "0x%08lx fd = %d offset = 0x%08llx\n", (unsigned long)addr, (unsigned long)lenght, (unsigned long)prot, (unsigned long)flags, (int)fd, (unsigned long long)offset); result = anonymous_last; anonymous_last += lenght; return 0; } ================================================ FILE: src/os_emulation/ossyscall.h ================================================ #ifndef OSSYCALL_H #define OSSYCALL_H #include "machine/core.h" #include "machine/instruction.h" #include "machine/machineconfig.h" #include "machine/memory/backend/memory.h" #include "machine/memory/frontend_memory.h" #include "machine/registers.h" #include "machine/simulator_exception.h" #include #include #include namespace osemu { #define OSSYCALL_HANDLER_DECLARE(name) \ int name( \ uint64_t &result, machine::Core *core, uint64_t syscall_num, uint64_t a1, uint64_t a2, \ uint64_t a3, uint64_t a4, uint64_t a5, uint64_t a6) class OsSyscallExceptionHandler : public machine::ExceptionHandler { Q_OBJECT public: explicit OsSyscallExceptionHandler( bool known_syscall_stop = false, bool unknown_syscall_stop = false, QString fs_root = ""); bool handle_exception( machine::Core *core, machine::Registers *regs, machine::ExceptionCause excause, machine::Address inst_addr, machine::Address next_addr, machine::Address jump_branch_pc, machine::Address mem_ref_addr) override; /** * Map target fd 0 (stdin) to a host file opened from @hostpath. * Returns true on success (host file opened and mapped), false on error. */ bool map_stdin_to_hostfile(const QString &hostpath); OSSYCALL_HANDLER_DECLARE(syscall_default_handler); OSSYCALL_HANDLER_DECLARE(do_sys_exit); OSSYCALL_HANDLER_DECLARE(do_sys_set_thread_area); OSSYCALL_HANDLER_DECLARE(do_sys_writev); OSSYCALL_HANDLER_DECLARE(do_sys_write); OSSYCALL_HANDLER_DECLARE(do_sys_readv); OSSYCALL_HANDLER_DECLARE(do_sys_read); OSSYCALL_HANDLER_DECLARE(do_sys_openat); OSSYCALL_HANDLER_DECLARE(do_sys_close); OSSYCALL_HANDLER_DECLARE(do_sys_ftruncate); OSSYCALL_HANDLER_DECLARE(do_sys_brk); OSSYCALL_HANDLER_DECLARE(do_sys_mmap); OSSYCALL_HANDLER_DECLARE(do_spim_print_integer); OSSYCALL_HANDLER_DECLARE(do_spim_print_string); OSSYCALL_HANDLER_DECLARE(do_spim_read_string); OSSYCALL_HANDLER_DECLARE(do_spim_sbrk); OSSYCALL_HANDLER_DECLARE(do_spim_exit); OSSYCALL_HANDLER_DECLARE(do_spim_print_character); OSSYCALL_HANDLER_DECLARE(do_spim_read_character); signals: void char_written(int fd, unsigned int val); void rx_byte_pool(int fd, unsigned int &data, bool &available); private: enum FdMapping { FD_UNUSED = -1, FD_INVALID = -1, FD_TERMINAL = -2, }; int32_t write_mem( machine::FrontendMemory *mem, machine::Address addr, const QVector &data, uint32_t count); int32_t read_mem( machine::FrontendMemory *mem, machine::Address addr, QVector &data, uint32_t count); int32_t write_io(int fd, const QVector &data, uint32_t count); int32_t read_io(int fd, QVector &data, uint32_t count, bool add_nl_at_eof = false); int allocate_fd(int val = FD_UNUSED); int file_open(QString fname, int flags, int mode); int targetfd_to_fd(int targetfd); void close_fd(int targetfd); QString filepath_to_host(QString path); QVector fd_mapping; uint32_t brk_limit; uint32_t anonymous_base; uint32_t anonymous_last; bool known_syscall_stop; bool unknown_syscall_stop; QString fs_root; }; #undef OSSYCALL_HANDLER_DECLARE } // namespace osemu #endif // OSSYCALL_H ================================================ FILE: src/os_emulation/posix_polyfill.h ================================================ #ifndef POSIX_POLYFILL_H #define POSIX_POLYFILL_H #ifndef _WIN32 // POSIX already provides these functions, just include unistd #include #else // wrap the "Low-Level I/O API" provided by Microsoft CRT, which exposes // a POSIX-like wrapper over Win32 #include #define open _open #define close _close #define read _read #define write _write #define ftruncate _chsize_s #endif // _WIN32 #endif // POSIX_POLYFILL_H ================================================ FILE: src/os_emulation/syscall_nr.h ================================================ #ifndef SYSCALL_NR_H #define SYSCALL_NR_H /* * Linux o32 style syscalls are in the range from 4000 to 4999. */ #define TARGET_NR_Linux 4000 #define TARGET_NR_syscall (TARGET_NR_Linux + 0) #define TARGET_NR_exit (TARGET_NR_Linux + 1) #define TARGET_NR_fork (TARGET_NR_Linux + 2) #define TARGET_NR_read (TARGET_NR_Linux + 3) #define TARGET_NR_write (TARGET_NR_Linux + 4) #define TARGET_NR_open (TARGET_NR_Linux + 5) #define TARGET_NR_close (TARGET_NR_Linux + 6) #define TARGET_NR_waitpid (TARGET_NR_Linux + 7) #define TARGET_NR_creat (TARGET_NR_Linux + 8) #define TARGET_NR_link (TARGET_NR_Linux + 9) #define TARGET_NR_unlink (TARGET_NR_Linux + 10) #define TARGET_NR_execve (TARGET_NR_Linux + 11) #define TARGET_NR_chdir (TARGET_NR_Linux + 12) #define TARGET_NR_time (TARGET_NR_Linux + 13) #define TARGET_NR_mknod (TARGET_NR_Linux + 14) #define TARGET_NR_chmod (TARGET_NR_Linux + 15) #define TARGET_NR_lchown (TARGET_NR_Linux + 16) #define TARGET_NR_break (TARGET_NR_Linux + 17) #define TARGET_NR_unused18 (TARGET_NR_Linux + 18) #define TARGET_NR_lseek (TARGET_NR_Linux + 19) #define TARGET_NR_getpid (TARGET_NR_Linux + 20) #define TARGET_NR_mount (TARGET_NR_Linux + 21) #define TARGET_NR_umount (TARGET_NR_Linux + 22) #define TARGET_NR_setuid (TARGET_NR_Linux + 23) #define TARGET_NR_getuid (TARGET_NR_Linux + 24) #define TARGET_NR_stime (TARGET_NR_Linux + 25) #define TARGET_NR_ptrace (TARGET_NR_Linux + 26) #define TARGET_NR_alarm (TARGET_NR_Linux + 27) #define TARGET_NR_unused28 (TARGET_NR_Linux + 28) #define TARGET_NR_pause (TARGET_NR_Linux + 29) #define TARGET_NR_utime (TARGET_NR_Linux + 30) #define TARGET_NR_stty (TARGET_NR_Linux + 31) #define TARGET_NR_gtty (TARGET_NR_Linux + 32) #define TARGET_NR_access (TARGET_NR_Linux + 33) #define TARGET_NR_nice (TARGET_NR_Linux + 34) #define TARGET_NR_ftime (TARGET_NR_Linux + 35) #define TARGET_NR_sync (TARGET_NR_Linux + 36) #define TARGET_NR_kill (TARGET_NR_Linux + 37) #define TARGET_NR_rename (TARGET_NR_Linux + 38) #define TARGET_NR_mkdir (TARGET_NR_Linux + 39) #define TARGET_NR_rmdir (TARGET_NR_Linux + 40) #define TARGET_NR_dup (TARGET_NR_Linux + 41) #define TARGET_NR_pipe (TARGET_NR_Linux + 42) #define TARGET_NR_times (TARGET_NR_Linux + 43) #define TARGET_NR_prof (TARGET_NR_Linux + 44) #define TARGET_NR_brk (TARGET_NR_Linux + 45) #define TARGET_NR_setgid (TARGET_NR_Linux + 46) #define TARGET_NR_getgid (TARGET_NR_Linux + 47) #define TARGET_NR_signal (TARGET_NR_Linux + 48) #define TARGET_NR_geteuid (TARGET_NR_Linux + 49) #define TARGET_NR_getegid (TARGET_NR_Linux + 50) #define TARGET_NR_acct (TARGET_NR_Linux + 51) #define TARGET_NR_umount2 (TARGET_NR_Linux + 52) #define TARGET_NR_lock (TARGET_NR_Linux + 53) #define TARGET_NR_ioctl (TARGET_NR_Linux + 54) #define TARGET_NR_fcntl (TARGET_NR_Linux + 55) #define TARGET_NR_mpx (TARGET_NR_Linux + 56) #define TARGET_NR_setpgid (TARGET_NR_Linux + 57) #define TARGET_NR_ulimit (TARGET_NR_Linux + 58) #define TARGET_NR_unused59 (TARGET_NR_Linux + 59) #define TARGET_NR_umask (TARGET_NR_Linux + 60) #define TARGET_NR_chroot (TARGET_NR_Linux + 61) #define TARGET_NR_ustat (TARGET_NR_Linux + 62) #define TARGET_NR_dup2 (TARGET_NR_Linux + 63) #define TARGET_NR_getppid (TARGET_NR_Linux + 64) #define TARGET_NR_getpgrp (TARGET_NR_Linux + 65) #define TARGET_NR_setsid (TARGET_NR_Linux + 66) #define TARGET_NR_sigaction (TARGET_NR_Linux + 67) #define TARGET_NR_sgetmask (TARGET_NR_Linux + 68) #define TARGET_NR_ssetmask (TARGET_NR_Linux + 69) #define TARGET_NR_setreuid (TARGET_NR_Linux + 70) #define TARGET_NR_setregid (TARGET_NR_Linux + 71) #define TARGET_NR_sigsuspend (TARGET_NR_Linux + 72) #define TARGET_NR_sigpending (TARGET_NR_Linux + 73) #define TARGET_NR_sethostname (TARGET_NR_Linux + 74) #define TARGET_NR_setrlimit (TARGET_NR_Linux + 75) #define TARGET_NR_getrlimit (TARGET_NR_Linux + 76) #define TARGET_NR_getrusage (TARGET_NR_Linux + 77) #define TARGET_NR_gettimeofday (TARGET_NR_Linux + 78) #define TARGET_NR_settimeofday (TARGET_NR_Linux + 79) #define TARGET_NR_getgroups (TARGET_NR_Linux + 80) #define TARGET_NR_setgroups (TARGET_NR_Linux + 81) #define TARGET_NR_reserved82 (TARGET_NR_Linux + 82) #define TARGET_NR_symlink (TARGET_NR_Linux + 83) #define TARGET_NR_unused84 (TARGET_NR_Linux + 84) #define TARGET_NR_readlink (TARGET_NR_Linux + 85) #define TARGET_NR_uselib (TARGET_NR_Linux + 86) #define TARGET_NR_swapon (TARGET_NR_Linux + 87) #define TARGET_NR_reboot (TARGET_NR_Linux + 88) #define TARGET_NR_readdir (TARGET_NR_Linux + 89) #define TARGET_NR_mmap (TARGET_NR_Linux + 90) #define TARGET_NR_munmap (TARGET_NR_Linux + 91) #define TARGET_NR_truncate (TARGET_NR_Linux + 92) #define TARGET_NR_ftruncate (TARGET_NR_Linux + 93) #define TARGET_NR_fchmod (TARGET_NR_Linux + 94) #define TARGET_NR_fchown (TARGET_NR_Linux + 95) #define TARGET_NR_getpriority (TARGET_NR_Linux + 96) #define TARGET_NR_setpriority (TARGET_NR_Linux + 97) #define TARGET_NR_profil (TARGET_NR_Linux + 98) #define TARGET_NR_statfs (TARGET_NR_Linux + 99) #define TARGET_NR_fstatfs (TARGET_NR_Linux + 100) #define TARGET_NR_ioperm (TARGET_NR_Linux + 101) #define TARGET_NR_socketcall (TARGET_NR_Linux + 102) #define TARGET_NR_syslog (TARGET_NR_Linux + 103) #define TARGET_NR_setitimer (TARGET_NR_Linux + 104) #define TARGET_NR_getitimer (TARGET_NR_Linux + 105) #define TARGET_NR_stat (TARGET_NR_Linux + 106) #define TARGET_NR_lstat (TARGET_NR_Linux + 107) #define TARGET_NR_fstat (TARGET_NR_Linux + 108) #define TARGET_NR_unused109 (TARGET_NR_Linux + 109) #define TARGET_NR_iopl (TARGET_NR_Linux + 110) #define TARGET_NR_vhangup (TARGET_NR_Linux + 111) #define TARGET_NR_idle (TARGET_NR_Linux + 112) #define TARGET_NR_vm86 (TARGET_NR_Linux + 113) #define TARGET_NR_wait4 (TARGET_NR_Linux + 114) #define TARGET_NR_swapoff (TARGET_NR_Linux + 115) #define TARGET_NR_sysinfo (TARGET_NR_Linux + 116) #define TARGET_NR_ipc (TARGET_NR_Linux + 117) #define TARGET_NR_fsync (TARGET_NR_Linux + 118) #define TARGET_NR_sigreturn (TARGET_NR_Linux + 119) #define TARGET_NR_clone (TARGET_NR_Linux + 120) #define TARGET_NR_setdomainname (TARGET_NR_Linux + 121) #define TARGET_NR_uname (TARGET_NR_Linux + 122) #define TARGET_NR_modify_ldt (TARGET_NR_Linux + 123) #define TARGET_NR_adjtimex (TARGET_NR_Linux + 124) #define TARGET_NR_mprotect (TARGET_NR_Linux + 125) #define TARGET_NR_sigprocmask (TARGET_NR_Linux + 126) #define TARGET_NR_create_module (TARGET_NR_Linux + 127) #define TARGET_NR_init_module (TARGET_NR_Linux + 128) #define TARGET_NR_delete_module (TARGET_NR_Linux + 129) #define TARGET_NR_get_kernel_syms (TARGET_NR_Linux + 130) #define TARGET_NR_quotactl (TARGET_NR_Linux + 131) #define TARGET_NR_getpgid (TARGET_NR_Linux + 132) #define TARGET_NR_fchdir (TARGET_NR_Linux + 133) #define TARGET_NR_bdflush (TARGET_NR_Linux + 134) #define TARGET_NR_sysfs (TARGET_NR_Linux + 135) #define TARGET_NR_personality (TARGET_NR_Linux + 136) #define TARGET_NR_afs_syscall (TARGET_NR_Linux + 137) /* Syscall for Andrew File System */ #define TARGET_NR_setfsuid (TARGET_NR_Linux + 138) #define TARGET_NR_setfsgid (TARGET_NR_Linux + 139) #define TARGET_NR__llseek (TARGET_NR_Linux + 140) #define TARGET_NR_getdents (TARGET_NR_Linux + 141) #define TARGET_NR__newselect (TARGET_NR_Linux + 142) #define TARGET_NR_flock (TARGET_NR_Linux + 143) #define TARGET_NR_msync (TARGET_NR_Linux + 144) #define TARGET_NR_readv (TARGET_NR_Linux + 145) #define TARGET_NR_writev (TARGET_NR_Linux + 146) #define TARGET_NR_cacheflush (TARGET_NR_Linux + 147) #define TARGET_NR_cachectl (TARGET_NR_Linux + 148) #define TARGET_NR_sysmips (TARGET_NR_Linux + 149) #define TARGET_NR_unused150 (TARGET_NR_Linux + 150) #define TARGET_NR_getsid (TARGET_NR_Linux + 151) #define TARGET_NR_fdatasync (TARGET_NR_Linux + 152) #define TARGET_NR__sysctl (TARGET_NR_Linux + 153) #define TARGET_NR_mlock (TARGET_NR_Linux + 154) #define TARGET_NR_munlock (TARGET_NR_Linux + 155) #define TARGET_NR_mlockall (TARGET_NR_Linux + 156) #define TARGET_NR_munlockall (TARGET_NR_Linux + 157) #define TARGET_NR_sched_setparam (TARGET_NR_Linux + 158) #define TARGET_NR_sched_getparam (TARGET_NR_Linux + 159) #define TARGET_NR_sched_setscheduler (TARGET_NR_Linux + 160) #define TARGET_NR_sched_getscheduler (TARGET_NR_Linux + 161) #define TARGET_NR_sched_yield (TARGET_NR_Linux + 162) #define TARGET_NR_sched_get_priority_max (TARGET_NR_Linux + 163) #define TARGET_NR_sched_get_priority_min (TARGET_NR_Linux + 164) #define TARGET_NR_sched_rr_get_interval (TARGET_NR_Linux + 165) #define TARGET_NR_nanosleep (TARGET_NR_Linux + 166) #define TARGET_NR_mremap (TARGET_NR_Linux + 167) #define TARGET_NR_accept (TARGET_NR_Linux + 168) #define TARGET_NR_bind (TARGET_NR_Linux + 169) #define TARGET_NR_connect (TARGET_NR_Linux + 170) #define TARGET_NR_getpeername (TARGET_NR_Linux + 171) #define TARGET_NR_getsockname (TARGET_NR_Linux + 172) #define TARGET_NR_getsockopt (TARGET_NR_Linux + 173) #define TARGET_NR_listen (TARGET_NR_Linux + 174) #define TARGET_NR_recv (TARGET_NR_Linux + 175) #define TARGET_NR_recvfrom (TARGET_NR_Linux + 176) #define TARGET_NR_recvmsg (TARGET_NR_Linux + 177) #define TARGET_NR_send (TARGET_NR_Linux + 178) #define TARGET_NR_sendmsg (TARGET_NR_Linux + 179) #define TARGET_NR_sendto (TARGET_NR_Linux + 180) #define TARGET_NR_setsockopt (TARGET_NR_Linux + 181) #define TARGET_NR_shutdown (TARGET_NR_Linux + 182) #define TARGET_NR_socket (TARGET_NR_Linux + 183) #define TARGET_NR_socketpair (TARGET_NR_Linux + 184) #define TARGET_NR_setresuid (TARGET_NR_Linux + 185) #define TARGET_NR_getresuid (TARGET_NR_Linux + 186) #define TARGET_NR_query_module (TARGET_NR_Linux + 187) #define TARGET_NR_poll (TARGET_NR_Linux + 188) #define TARGET_NR_nfsservctl (TARGET_NR_Linux + 189) #define TARGET_NR_setresgid (TARGET_NR_Linux + 190) #define TARGET_NR_getresgid (TARGET_NR_Linux + 191) #define TARGET_NR_prctl (TARGET_NR_Linux + 192) #define TARGET_NR_rt_sigreturn (TARGET_NR_Linux + 193) #define TARGET_NR_rt_sigaction (TARGET_NR_Linux + 194) #define TARGET_NR_rt_sigprocmask (TARGET_NR_Linux + 195) #define TARGET_NR_rt_sigpending (TARGET_NR_Linux + 196) #define TARGET_NR_rt_sigtimedwait (TARGET_NR_Linux + 197) #define TARGET_NR_rt_sigqueueinfo (TARGET_NR_Linux + 198) #define TARGET_NR_rt_sigsuspend (TARGET_NR_Linux + 199) #define TARGET_NR_pread64 (TARGET_NR_Linux + 200) #define TARGET_NR_pwrite64 (TARGET_NR_Linux + 201) #define TARGET_NR_chown (TARGET_NR_Linux + 202) #define TARGET_NR_getcwd (TARGET_NR_Linux + 203) #define TARGET_NR_capget (TARGET_NR_Linux + 204) #define TARGET_NR_capset (TARGET_NR_Linux + 205) #define TARGET_NR_sigaltstack (TARGET_NR_Linux + 206) #define TARGET_NR_sendfile (TARGET_NR_Linux + 207) #define TARGET_NR_getpmsg (TARGET_NR_Linux + 208) #define TARGET_NR_putpmsg (TARGET_NR_Linux + 209) #define TARGET_NR_mmap2 (TARGET_NR_Linux + 210) #define TARGET_NR_truncate64 (TARGET_NR_Linux + 211) #define TARGET_NR_ftruncate64 (TARGET_NR_Linux + 212) #define TARGET_NR_stat64 (TARGET_NR_Linux + 213) #define TARGET_NR_lstat64 (TARGET_NR_Linux + 214) #define TARGET_NR_fstat64 (TARGET_NR_Linux + 215) #define TARGET_NR_pivot_root (TARGET_NR_Linux + 216) #define TARGET_NR_mincore (TARGET_NR_Linux + 217) #define TARGET_NR_madvise (TARGET_NR_Linux + 218) #define TARGET_NR_getdents64 (TARGET_NR_Linux + 219) #define TARGET_NR_fcntl64 (TARGET_NR_Linux + 220) #define TARGET_NR_reserved221 (TARGET_NR_Linux + 221) #define TARGET_NR_gettid (TARGET_NR_Linux + 222) #define TARGET_NR_readahead (TARGET_NR_Linux + 223) #define TARGET_NR_setxattr (TARGET_NR_Linux + 224) #define TARGET_NR_lsetxattr (TARGET_NR_Linux + 225) #define TARGET_NR_fsetxattr (TARGET_NR_Linux + 226) #define TARGET_NR_getxattr (TARGET_NR_Linux + 227) #define TARGET_NR_lgetxattr (TARGET_NR_Linux + 228) #define TARGET_NR_fgetxattr (TARGET_NR_Linux + 229) #define TARGET_NR_listxattr (TARGET_NR_Linux + 230) #define TARGET_NR_llistxattr (TARGET_NR_Linux + 231) #define TARGET_NR_flistxattr (TARGET_NR_Linux + 232) #define TARGET_NR_removexattr (TARGET_NR_Linux + 233) #define TARGET_NR_lremovexattr (TARGET_NR_Linux + 234) #define TARGET_NR_fremovexattr (TARGET_NR_Linux + 235) #define TARGET_NR_tkill (TARGET_NR_Linux + 236) #define TARGET_NR_sendfile64 (TARGET_NR_Linux + 237) #define TARGET_NR_futex (TARGET_NR_Linux + 238) #define TARGET_NR_sched_setaffinity (TARGET_NR_Linux + 239) #define TARGET_NR_sched_getaffinity (TARGET_NR_Linux + 240) #define TARGET_NR_io_setup (TARGET_NR_Linux + 241) #define TARGET_NR_io_destroy (TARGET_NR_Linux + 242) #define TARGET_NR_io_getevents (TARGET_NR_Linux + 243) #define TARGET_NR_io_submit (TARGET_NR_Linux + 244) #define TARGET_NR_io_cancel (TARGET_NR_Linux + 245) #define TARGET_NR_exit_group (TARGET_NR_Linux + 246) #define TARGET_NR_lookup_dcookie (TARGET_NR_Linux + 247) #define TARGET_NR_epoll_create (TARGET_NR_Linux + 248) #define TARGET_NR_epoll_ctl (TARGET_NR_Linux + 249) #define TARGET_NR_epoll_wait (TARGET_NR_Linux + 250) #define TARGET_NR_remap_file_pages (TARGET_NR_Linux + 251) #define TARGET_NR_set_tid_address (TARGET_NR_Linux + 252) #define TARGET_NR_restart_syscall (TARGET_NR_Linux + 253) #define TARGET_NR_fadvise64_64 (TARGET_NR_Linux + 254) #define TARGET_NR_statfs64 (TARGET_NR_Linux + 255) #define TARGET_NR_fstatfs64 (TARGET_NR_Linux + 256) #define TARGET_NR_timer_create (TARGET_NR_Linux + 257) #define TARGET_NR_timer_settime (TARGET_NR_Linux + 258) #define TARGET_NR_timer_gettime (TARGET_NR_Linux + 259) #define TARGET_NR_timer_getoverrun (TARGET_NR_Linux + 260) #define TARGET_NR_timer_delete (TARGET_NR_Linux + 261) #define TARGET_NR_clock_settime (TARGET_NR_Linux + 262) #define TARGET_NR_clock_gettime (TARGET_NR_Linux + 263) #define TARGET_NR_clock_getres (TARGET_NR_Linux + 264) #define TARGET_NR_clock_nanosleep (TARGET_NR_Linux + 265) #define TARGET_NR_tgkill (TARGET_NR_Linux + 266) #define TARGET_NR_utimes (TARGET_NR_Linux + 267) #define TARGET_NR_mbind (TARGET_NR_Linux + 268) #define TARGET_NR_get_mempolicy (TARGET_NR_Linux + 269) #define TARGET_NR_set_mempolicy (TARGET_NR_Linux + 270) #define TARGET_NR_mq_open (TARGET_NR_Linux + 271) #define TARGET_NR_mq_unlink (TARGET_NR_Linux + 272) #define TARGET_NR_mq_timedsend (TARGET_NR_Linux + 273) #define TARGET_NR_mq_timedreceive (TARGET_NR_Linux + 274) #define TARGET_NR_mq_notify (TARGET_NR_Linux + 275) #define TARGET_NR_mq_getsetattr (TARGET_NR_Linux + 276) #define TARGET_NR_vserver (TARGET_NR_Linux + 277) #define TARGET_NR_waitid (TARGET_NR_Linux + 278) /* #define TARGET_NR_sys_setaltroot (TARGET_NR_Linux + 279) */ #define TARGET_NR_add_key (TARGET_NR_Linux + 280) #define TARGET_NR_request_key (TARGET_NR_Linux + 281) #define TARGET_NR_keyctl (TARGET_NR_Linux + 282) #define TARGET_NR_set_thread_area (TARGET_NR_Linux + 283) #define TARGET_NR_inotify_init (TARGET_NR_Linux + 284) #define TARGET_NR_inotify_add_watch (TARGET_NR_Linux + 285) #define TARGET_NR_inotify_rm_watch (TARGET_NR_Linux + 286) #define TARGET_NR_migrate_pages (TARGET_NR_Linux + 287) #define TARGET_NR_openat (TARGET_NR_Linux + 288) #define TARGET_NR_mkdirat (TARGET_NR_Linux + 289) #define TARGET_NR_mknodat (TARGET_NR_Linux + 290) #define TARGET_NR_fchownat (TARGET_NR_Linux + 291) #define TARGET_NR_futimesat (TARGET_NR_Linux + 292) #define TARGET_NR_fstatat64 (TARGET_NR_Linux + 293) #define TARGET_NR_unlinkat (TARGET_NR_Linux + 294) #define TARGET_NR_renameat (TARGET_NR_Linux + 295) #define TARGET_NR_linkat (TARGET_NR_Linux + 296) #define TARGET_NR_symlinkat (TARGET_NR_Linux + 297) #define TARGET_NR_readlinkat (TARGET_NR_Linux + 298) #define TARGET_NR_fchmodat (TARGET_NR_Linux + 299) #define TARGET_NR_faccessat (TARGET_NR_Linux + 300) #define TARGET_NR_pselect6 (TARGET_NR_Linux + 301) #define TARGET_NR_ppoll (TARGET_NR_Linux + 302) #define TARGET_NR_unshare (TARGET_NR_Linux + 303) #define TARGET_NR_splice (TARGET_NR_Linux + 304) #define TARGET_NR_sync_file_range (TARGET_NR_Linux + 305) #define TARGET_NR_tee (TARGET_NR_Linux + 306) #define TARGET_NR_vmsplice (TARGET_NR_Linux + 307) #define TARGET_NR_move_pages (TARGET_NR_Linux + 308) #define TARGET_NR_set_robust_list (TARGET_NR_Linux + 309) #define TARGET_NR_get_robust_list (TARGET_NR_Linux + 310) #define TARGET_NR_kexec_load (TARGET_NR_Linux + 311) #define TARGET_NR_getcpu (TARGET_NR_Linux + 312) #define TARGET_NR_epoll_pwait (TARGET_NR_Linux + 313) #define TARGET_NR_ioprio_set (TARGET_NR_Linux + 314) #define TARGET_NR_ioprio_get (TARGET_NR_Linux + 315) #define TARGET_NR_utimensat (TARGET_NR_Linux + 316) #define TARGET_NR_signalfd (TARGET_NR_Linux + 317) #define TARGET_NR_timerfd (TARGET_NR_Linux + 318) #define TARGET_NR_eventfd (TARGET_NR_Linux + 319) #define TARGET_NR_fallocate (TARGET_NR_Linux + 320) #define TARGET_NR_timerfd_create (TARGET_NR_Linux + 321) #define TARGET_NR_timerfd_gettime (TARGET_NR_Linux + 322) #define TARGET_NR_timerfd_settime (TARGET_NR_Linux + 323) #define TARGET_NR_signalfd4 (TARGET_NR_Linux + 324) #define TARGET_NR_eventfd2 (TARGET_NR_Linux + 325) #define TARGET_NR_epoll_create1 (TARGET_NR_Linux + 326) #define TARGET_NR_dup3 (TARGET_NR_Linux + 327) #define TARGET_NR_pipe2 (TARGET_NR_Linux + 328) #define TARGET_NR_inotify_init1 (TARGET_NR_Linux + 329) #define TARGET_NR_preadv (TARGET_NR_Linux + 330) #define TARGET_NR_pwritev (TARGET_NR_Linux + 331) #define TARGET_NR_rt_tgsigqueueinfo (TARGET_NR_Linux + 332) #define TARGET_NR_perf_event_open (TARGET_NR_Linux + 333) #define TARGET_NR_accept4 (TARGET_NR_Linux + 334) #define TARGET_NR_recvmmsg (TARGET_NR_Linux + 335) #define TARGET_NR_fanotify_init (TARGET_NR_Linux + 336) #define TARGET_NR_fanotify_mark (TARGET_NR_Linux + 337) #define TARGET_NR_prlimit64 (TARGET_NR_Linux + 338) #define TARGET_NR_name_to_handle_at (TARGET_NR_Linux + 339) #define TARGET_NR_open_by_handle_at (TARGET_NR_Linux + 340) #define TARGET_NR_clock_adjtime (TARGET_NR_Linux + 341) #define TARGET_NR_syncfs (TARGET_NR_Linux + 342) #define TARGET_NR_sendmmsg (TARGET_NR_Linux + 343) #define TARGET_NR_setns (TARGET_NR_Linux + 344) #define TARGET_NR_process_vm_readv (TARGET_NR_Linux + 345) #define TARGET_NR_process_vm_writev (TARGET_NR_Linux + 346) #define TARGET_NR_kcmp (TARGET_NR_Linux + 347) #define TARGET_NR_finit_module (TARGET_NR_Linux + 348) #define TARGET_NR_sched_setattr (TARGET_NR_Linux + 349) #define TARGET_NR_sched_getattr (TARGET_NR_Linux + 350) #define TARGET_NR_renameat2 (TARGET_NR_Linux + 351) #define TARGET_NR_seccomp (TARGET_NR_Linux + 352) #define TARGET_NR_getrandom (TARGET_NR_Linux + 353) #define TARGET_NR_memfd_create (TARGET_NR_Linux + 354) #define TARGET_NR_bpf (TARGET_NR_Linux + 355) #define TARGET_NR_execveat (TARGET_NR_Linux + 356) #define TARGET_NR_userfaultfd (TARGET_NR_Linux + 357) #define TARGET_NR_membarrier (TARGET_NR_Linux + 358) #define TARGET_NR_mlock2 (TARGET_NR_Linux + 359) #define TARGET_NR_copy_file_range (TARGET_NR_Linux + 360) #define TARGET_NR_preadv2 (TARGET_NR_Linux + 361) #define TARGET_NR_pwritev2 (TARGET_NR_Linux + 362) #define TARGET_NR_pkey_mprotect (TARGET_NR_Linux + 363) #define TARGET_NR_pkey_alloc (TARGET_NR_Linux + 364) #define TARGET_NR_pkey_free (TARGET_NR_Linux + 365) #define TARGET_NR_statx (TARGET_NR_Linux + 366) #define TARGET_NR_rseq (TARGET_NR_Linux + 367) #define TARGET_NR_io_pgetevents (TARGET_NR_Linux + 368) #endif /*SYSCALL_NR_H*/ ================================================ FILE: src/os_emulation/syscallent.h ================================================ /* * Copyright (c) 2015-2021 The strace developers. * All rights reserved. * * SPDX-License-Identifier: LGPL-2.1-or-later */ #define HANDLER(syscall) &OsSyscallExceptionHandler::syscall [0] = { 2, HANDLER(syscall_default_handler), "io_setup" }, [1] = { 1, HANDLER(syscall_default_handler), "io_destroy" }, [2] = { 3, HANDLER(syscall_default_handler), "io_submit" }, [3] = { 3, HANDLER(syscall_default_handler), "io_cancel" }, [4] = { 5, HANDLER(syscall_default_handler), "io_getevents" }, [5] = { 5, HANDLER(syscall_default_handler), "setxattr" }, [6] = { 5, HANDLER(syscall_default_handler), "lsetxattr" }, [7] = { 5, HANDLER(syscall_default_handler), "fsetxattr" }, [8] = { 4, HANDLER(syscall_default_handler), "getxattr" }, [9] = { 4, HANDLER(syscall_default_handler), "lgetxattr" }, [10] = { 4, HANDLER(syscall_default_handler), "fgetxattr" }, [11] = { 3, HANDLER(syscall_default_handler), "listxattr" }, [12] = { 3, HANDLER(syscall_default_handler), "llistxattr" }, [13] = { 3, HANDLER(syscall_default_handler), "flistxattr" }, [14] = { 2, HANDLER(syscall_default_handler), "removexattr" }, [15] = { 2, HANDLER(syscall_default_handler), "lremovexattr" }, [16] = { 2, HANDLER(syscall_default_handler), "fremovexattr" }, [17] = { 2, HANDLER(syscall_default_handler), "getcwd" }, [18] = { 3, HANDLER(syscall_default_handler), "lookup_dcookie" }, [19] = { 2, HANDLER(syscall_default_handler), "eventfd2" }, [20] = { 1, HANDLER(syscall_default_handler), "epoll_create1" }, [21] = { 4, HANDLER(syscall_default_handler), "epoll_ctl" }, [22] = { 6, HANDLER(syscall_default_handler), "epoll_pwait" }, [23] = { 1, HANDLER(syscall_default_handler), "dup" }, [24] = { 3, HANDLER(syscall_default_handler), "dup3" }, [25] = { 3, HANDLER(syscall_default_handler), "fcntl" }, [26] = { 1, HANDLER(syscall_default_handler), "inotify_init1" }, [27] = { 3, HANDLER(syscall_default_handler), "inotify_add_watch" }, [28] = { 2, HANDLER(syscall_default_handler), "inotify_rm_watch" }, [29] = { 3, HANDLER(syscall_default_handler), "ioctl" }, [30] = { 3, HANDLER(syscall_default_handler), "ioprio_set" }, [31] = { 2, HANDLER(syscall_default_handler), "ioprio_get" }, [32] = { 2, HANDLER(syscall_default_handler), "flock" }, [33] = { 4, HANDLER(syscall_default_handler), "mknodat" }, [34] = { 3, HANDLER(syscall_default_handler), "mkdirat" }, [35] = { 3, HANDLER(syscall_default_handler), "unlinkat" }, [36] = { 3, HANDLER(syscall_default_handler), "symlinkat" }, [37] = { 5, HANDLER(syscall_default_handler), "linkat" }, [38] = { 4, HANDLER(syscall_default_handler), "renameat" }, [39] = { 2, HANDLER(syscall_default_handler), "umount2" }, [40] = { 5, HANDLER(syscall_default_handler), "mount" }, [41] = { 2, HANDLER(syscall_default_handler), "pivot_root" }, [42] = { 3, HANDLER(syscall_default_handler), "nfsservctl" }, [43] = { 2, HANDLER(syscall_default_handler), "statfs" }, [44] = { 2, HANDLER(syscall_default_handler), "fstatfs" }, [45] = { 2, HANDLER(syscall_default_handler), "truncate" }, [46] = { 2, HANDLER(do_sys_ftruncate), "ftruncate" }, [47] = { 4, HANDLER(syscall_default_handler), "fallocate" }, [48] = { 3, HANDLER(syscall_default_handler), "faccessat" }, [49] = { 1, HANDLER(syscall_default_handler), "chdir" }, [50] = { 1, HANDLER(syscall_default_handler), "fchdir" }, [51] = { 1, HANDLER(syscall_default_handler), "chroot" }, [52] = { 2, HANDLER(syscall_default_handler), "fchmod" }, [53] = { 3, HANDLER(syscall_default_handler), "fchmodat" }, [54] = { 5, HANDLER(syscall_default_handler), "fchownat" }, [55] = { 3, HANDLER(syscall_default_handler), "fchown" }, [56] = { 4, HANDLER(do_sys_openat), "openat" }, [57] = { 1, HANDLER(do_sys_close), "close" }, [58] = { 0, HANDLER(syscall_default_handler), "vhangup" }, [59] = { 2, HANDLER(syscall_default_handler), "pipe2" }, [60] = { 4, HANDLER(syscall_default_handler), "quotactl" }, [61] = { 3, HANDLER(syscall_default_handler), "getdents64" }, [62] = { 3, HANDLER(syscall_default_handler), "lseek" }, [63] = { 3, HANDLER(do_sys_read), "read" }, [64] = { 3, HANDLER(do_sys_write), "write" }, [65] = { 3, HANDLER(do_sys_readv), "readv" }, [66] = { 3, HANDLER(do_sys_writev), "writev" }, [67] = { 4, HANDLER(syscall_default_handler), "pread64" }, [68] = { 4, HANDLER(syscall_default_handler), "pwrite64" }, [69] = { 4, HANDLER(syscall_default_handler), "preadv" }, [70] = { 4, HANDLER(syscall_default_handler), "pwritev" }, [71] = { 4, HANDLER(syscall_default_handler), "sendfile" }, [72] = { 6, HANDLER(syscall_default_handler), "pselect6" }, [73] = { 5, HANDLER(syscall_default_handler), "ppoll" }, [74] = { 4, HANDLER(syscall_default_handler), "signalfd4" }, [75] = { 4, HANDLER(syscall_default_handler), "vmsplice" }, [76] = { 6, HANDLER(syscall_default_handler), "splice" }, [77] = { 4, HANDLER(syscall_default_handler), "tee" }, [78] = { 4, HANDLER(syscall_default_handler), "readlinkat" }, [79] = { 4, HANDLER(syscall_default_handler), "newfstatat" }, [80] = { 2, HANDLER(syscall_default_handler), "fstat" }, [81] = { 0, HANDLER(syscall_default_handler), "sync" }, [82] = { 1, HANDLER(syscall_default_handler), "fsync" }, [83] = { 1, HANDLER(syscall_default_handler), "fdatasync" }, [84] = { 4, HANDLER(syscall_default_handler), "sync_file_range" }, [85] = { 2, HANDLER(syscall_default_handler), "timerfd_create" }, [86] = { 4, HANDLER(syscall_default_handler), "timerfd_settime" }, [87] = { 2, HANDLER(syscall_default_handler), "timerfd_gettime" }, [88] = { 4, HANDLER(syscall_default_handler), "utimensat" }, [89] = { 1, HANDLER(syscall_default_handler), "acct" }, [90] = { 2, HANDLER(syscall_default_handler), "capget" }, [91] = { 2, HANDLER(syscall_default_handler), "capset" }, [92] = { 1, HANDLER(syscall_default_handler), "personality" }, [93] = { 1, HANDLER(do_sys_exit), "exit" }, [94] = { 1, HANDLER(syscall_default_handler), "exit_group" }, [95] = { 5, HANDLER(syscall_default_handler), "waitid" }, [96] = { 1, HANDLER(syscall_default_handler), "set_tid_address" }, [97] = { 1, HANDLER(syscall_default_handler), "unshare" }, [98] = { 6, HANDLER(syscall_default_handler), "futex" }, [99] = { 2, HANDLER(syscall_default_handler), "set_robust_list" }, [100] = { 3, HANDLER(syscall_default_handler), "get_robust_list" }, [101] = { 2, HANDLER(syscall_default_handler), "nanosleep" }, [102] = { 2, HANDLER(syscall_default_handler), "getitimer" }, [103] = { 3, HANDLER(syscall_default_handler), "setitimer" }, [104] = { 4, HANDLER(syscall_default_handler), "kexec_load" }, [105] = { 3, HANDLER(syscall_default_handler), "init_module" }, [106] = { 2, HANDLER(syscall_default_handler), "delete_module" }, [107] = { 3, HANDLER(syscall_default_handler), "timer_create" }, [108] = { 2, HANDLER(syscall_default_handler), "timer_gettime" }, [109] = { 1, HANDLER(syscall_default_handler), "timer_getoverrun" }, [110] = { 4, HANDLER(syscall_default_handler), "timer_settime" }, [111] = { 1, HANDLER(syscall_default_handler), "timer_delete" }, [112] = { 2, HANDLER(syscall_default_handler), "clock_settime" }, [113] = { 2, HANDLER(syscall_default_handler), "clock_gettime" }, [114] = { 2, HANDLER(syscall_default_handler), "clock_getres" }, [115] = { 4, HANDLER(syscall_default_handler), "clock_nanosleep" }, [116] = { 3, HANDLER(syscall_default_handler), "syslog" }, [117] = { 4, HANDLER(syscall_default_handler), "ptrace" }, [118] = { 2, HANDLER(syscall_default_handler), "sched_setparam" }, [119] = { 3, HANDLER(syscall_default_handler), "sched_setscheduler" }, [120] = { 1, HANDLER(syscall_default_handler), "sched_getscheduler" }, [121] = { 2, HANDLER(syscall_default_handler), "sched_getparam" }, [122] = { 3, HANDLER(syscall_default_handler), "sched_setaffinity" }, [123] = { 3, HANDLER(syscall_default_handler), "sched_getaffinity" }, [124] = { 0, HANDLER(syscall_default_handler), "sched_yield" }, [125] = { 1, HANDLER(syscall_default_handler), "sched_get_priority_max" }, [126] = { 1, HANDLER(syscall_default_handler), "sched_get_priority_min" }, [127] = { 2, HANDLER(syscall_default_handler), "sched_rr_get_interval" }, [128] = { 0, HANDLER(syscall_default_handler), "restart_syscall" }, [129] = { 2, HANDLER(syscall_default_handler), "kill" }, [130] = { 2, HANDLER(syscall_default_handler), "tkill" }, [131] = { 3, HANDLER(syscall_default_handler), "tgkill" }, [132] = { 2, HANDLER(syscall_default_handler), "sigaltstack" }, [133] = { 2, HANDLER(syscall_default_handler), "rt_sigsuspend" }, [134] = { 4, HANDLER(syscall_default_handler), "rt_sigaction" }, [135] = { 4, HANDLER(syscall_default_handler), "rt_sigprocmask" }, [136] = { 2, HANDLER(syscall_default_handler), "rt_sigpending" }, [137] = { 4, HANDLER(syscall_default_handler), "rt_sigtimedwait" }, [138] = { 3, HANDLER(syscall_default_handler), "rt_sigqueueinfo" }, [139] = { 0, HANDLER(syscall_default_handler), "rt_sigreturn" }, [140] = { 3, HANDLER(syscall_default_handler), "setpriority" }, [141] = { 2, HANDLER(syscall_default_handler), "getpriority" }, [142] = { 4, HANDLER(syscall_default_handler), "reboot" }, [143] = { 2, HANDLER(syscall_default_handler), "setregid" }, [144] = { 1, HANDLER(syscall_default_handler), "setgid" }, [145] = { 2, HANDLER(syscall_default_handler), "setreuid" }, [146] = { 1, HANDLER(syscall_default_handler), "setuid" }, [147] = { 3, HANDLER(syscall_default_handler), "setresuid" }, [148] = { 3, HANDLER(syscall_default_handler), "getresuid" }, [149] = { 3, HANDLER(syscall_default_handler), "setresgid" }, [150] = { 3, HANDLER(syscall_default_handler), "getresgid" }, [151] = { 1, HANDLER(syscall_default_handler), "setfsuid" }, [152] = { 1, HANDLER(syscall_default_handler), "setfsgid" }, [153] = { 1, HANDLER(syscall_default_handler), "times" }, [154] = { 2, HANDLER(syscall_default_handler), "setpgid" }, [155] = { 1, HANDLER(syscall_default_handler), "getpgid" }, [156] = { 1, HANDLER(syscall_default_handler), "getsid" }, [157] = { 0, HANDLER(syscall_default_handler), "setsid" }, [158] = { 2, HANDLER(syscall_default_handler), "getgroups" }, [159] = { 2, HANDLER(syscall_default_handler), "setgroups" }, [160] = { 1, HANDLER(syscall_default_handler), "uname" }, [161] = { 2, HANDLER(syscall_default_handler), "sethostname" }, [162] = { 2, HANDLER(syscall_default_handler), "setdomainname" }, [163] = { 2, HANDLER(syscall_default_handler), "getrlimit" }, [164] = { 2, HANDLER(syscall_default_handler), "setrlimit" }, [165] = { 2, HANDLER(syscall_default_handler), "getrusage" }, [166] = { 1, HANDLER(syscall_default_handler), "umask" }, [167] = { 5, HANDLER(syscall_default_handler), "prctl" }, [168] = { 3, HANDLER(syscall_default_handler), "getcpu" }, [169] = { 2, HANDLER(syscall_default_handler), "gettimeofday" }, [170] = { 2, HANDLER(syscall_default_handler), "settimeofday" }, [171] = { 1, HANDLER(syscall_default_handler), "adjtimex" }, [172] = { 0, HANDLER(syscall_default_handler), "getpid" }, [173] = { 0, HANDLER(syscall_default_handler), "getppid" }, [174] = { 0, HANDLER(syscall_default_handler), "getuid" }, [175] = { 0, HANDLER(syscall_default_handler), "geteuid" }, [176] = { 0, HANDLER(syscall_default_handler), "getgid" }, [177] = { 0, HANDLER(syscall_default_handler), "getegid" }, [178] = { 0, HANDLER(syscall_default_handler), "gettid" }, [179] = { 1, HANDLER(syscall_default_handler), "sysinfo" }, [180] = { 4, HANDLER(syscall_default_handler), "mq_open" }, [181] = { 1, HANDLER(syscall_default_handler), "mq_unlink" }, [182] = { 5, HANDLER(syscall_default_handler), "mq_timedsend" }, [183] = { 5, HANDLER(syscall_default_handler), "mq_timedreceive" }, [184] = { 2, HANDLER(syscall_default_handler), "mq_notify" }, [185] = { 3, HANDLER(syscall_default_handler), "mq_getsetattr" }, [186] = { 2, HANDLER(syscall_default_handler), "msgget" }, [187] = { 3, HANDLER(syscall_default_handler), "msgctl" }, [188] = { 5, HANDLER(syscall_default_handler), "msgrcv" }, [189] = { 4, HANDLER(syscall_default_handler), "msgsnd" }, [190] = { 3, HANDLER(syscall_default_handler), "semget" }, [191] = { 4, HANDLER(syscall_default_handler), "semctl" }, [192] = { 4, HANDLER(syscall_default_handler), "semtimedop" }, [193] = { 3, HANDLER(syscall_default_handler), "semop" }, [194] = { 3, HANDLER(syscall_default_handler), "shmget" }, [195] = { 3, HANDLER(syscall_default_handler), "shmctl" }, [196] = { 3, HANDLER(syscall_default_handler), "shmat" }, [197] = { 1, HANDLER(syscall_default_handler), "shmdt" }, [198] = { 3, HANDLER(syscall_default_handler), "socket" }, [199] = { 4, HANDLER(syscall_default_handler), "socketpair" }, [200] = { 3, HANDLER(syscall_default_handler), "bind" }, [201] = { 2, HANDLER(syscall_default_handler), "listen" }, [202] = { 3, HANDLER(syscall_default_handler), "accept" }, [203] = { 3, HANDLER(syscall_default_handler), "connect" }, [204] = { 3, HANDLER(syscall_default_handler), "getsockname" }, [205] = { 3, HANDLER(syscall_default_handler), "getpeername" }, [206] = { 6, HANDLER(syscall_default_handler), "sendto" }, [207] = { 6, HANDLER(syscall_default_handler), "recvfrom" }, [208] = { 5, HANDLER(syscall_default_handler), "setsockopt" }, [209] = { 5, HANDLER(syscall_default_handler), "getsockopt" }, [210] = { 2, HANDLER(syscall_default_handler), "shutdown" }, [211] = { 3, HANDLER(syscall_default_handler), "sendmsg" }, [212] = { 3, HANDLER(syscall_default_handler), "recvmsg" }, [213] = { 3, HANDLER(syscall_default_handler), "readahead" }, [214] = { 1, HANDLER(do_sys_brk), "brk" }, [215] = { 2, HANDLER(syscall_default_handler), "munmap" }, [216] = { 5, HANDLER(syscall_default_handler), "mremap" }, [217] = { 5, HANDLER(syscall_default_handler), "add_key" }, [218] = { 4, HANDLER(syscall_default_handler), "request_key" }, [219] = { 5, HANDLER(syscall_default_handler), "keyctl" }, [220] = { 5, HANDLER(syscall_default_handler), "clone" }, [221] = { 3, HANDLER(syscall_default_handler), "execve" }, [222] = { 6, HANDLER(do_sys_mmap), "mmap" }, [223] = { 4, HANDLER(syscall_default_handler), "fadvise64" }, [224] = { 2, HANDLER(syscall_default_handler), "swapon" }, [225] = { 1, HANDLER(syscall_default_handler), "swapoff" }, [226] = { 3, HANDLER(syscall_default_handler), "mprotect" }, [227] = { 3, HANDLER(syscall_default_handler), "msync" }, [228] = { 2, HANDLER(syscall_default_handler), "mlock" }, [229] = { 2, HANDLER(syscall_default_handler), "munlock" }, [230] = { 1, HANDLER(syscall_default_handler), "mlockall" }, [231] = { 0, HANDLER(syscall_default_handler), "munlockall" }, [232] = { 3, HANDLER(syscall_default_handler), "mincore" }, [233] = { 3, HANDLER(syscall_default_handler), "madvise" }, [234] = { 5, HANDLER(syscall_default_handler), "remap_file_pages" }, [235] = { 6, HANDLER(syscall_default_handler), "mbind" }, [236] = { 5, HANDLER(syscall_default_handler), "get_mempolicy" }, [237] = { 3, HANDLER(syscall_default_handler), "set_mempolicy" }, [238] = { 4, HANDLER(syscall_default_handler), "migrate_pages" }, [239] = { 6, HANDLER(syscall_default_handler), "move_pages" }, [240] = { 4, HANDLER(syscall_default_handler), "rt_tgsigqueueinfo" }, [241] = { 5, HANDLER(syscall_default_handler), "perf_event_open" }, [242] = { 4, HANDLER(syscall_default_handler), "accept4" }, [243] = { 5, HANDLER(syscall_default_handler), "recvmmsg" }, // this array has to be contiguous because C++. #undef HANDLER ================================================ FILE: src/os_emulation/target_errno.h ================================================ #ifndef TARGET_ERRNO_H #define TARGET_ERRNO_H #define TARGET_EPERM 1 #define TARGET_ENOENT 2 #define TARGET_ESRCH 3 #define TARGET_EINTR 4 #define TARGET_EIO 5 #define TARGET_ENXIO 6 #define TARGET_E2BIG 7 #define TARGET_ENOEXEC 8 #define TARGET_EBADF 9 #define TARGET_ECHILD 10 #define TARGET_EAGAIN 11 #define TARGET_ENOMEM 12 #define TARGET_EACCES 13 #define TARGET_EFAULT 14 #define TARGET_ENOTBLK 15 #define TARGET_EBUSY 16 #define TARGET_EEXIST 17 #define TARGET_EXDEV 18 #define TARGET_ENODEV 19 #define TARGET_ENOTDIR 20 #define TARGET_EISDIR 21 #define TARGET_EINVAL 22 #define TARGET_ENFILE 23 #define TARGET_EMFILE 24 #define TARGET_ENOTTY 25 #define TARGET_ETXTBSY 26 #define TARGET_EFBIG 27 #define TARGET_ENOSPC 28 #define TARGET_ESPIPE 29 #define TARGET_EROFS 30 #define TARGET_EMLINK 31 #define TARGET_EPIPE 32 #define TARGET_EDOM 33 #define TARGET_ERANGE 34 #define TARGET_ENOMSG 35 #define TARGET_EIDRM 36 #define TARGET_ECHRNG 37 #define TARGET_EL2NSYNC 38 #define TARGET_EL3HLT 39 #define TARGET_EL3RST 40 #define TARGET_ELNRNG 41 #define TARGET_EUNATCH 42 #define TARGET_ENOCSI 43 #define TARGET_EL2HLT 44 #define TARGET_EDEADLK 45 #define TARGET_ENOLCK 46 #define TARGET_EBADE 50 #define TARGET_EBADR 51 #define TARGET_EXFULL 52 #define TARGET_ENOANO 53 #define TARGET_EBADRQC 54 #define TARGET_EBADSLT 55 #define TARGET_EDEADLOCK 56 #define TARGET_EBFONT 59 #define TARGET_ENOSTR 60 #define TARGET_ENODATA 61 #define TARGET_ETIME 62 #define TARGET_ENOSR 63 #define TARGET_ENONET 64 #define TARGET_ENOPKG 65 #define TARGET_EREMOTE 66 #define TARGET_ENOLINK 67 #define TARGET_EADV 68 #define TARGET_ESRMNT 69 #define TARGET_ECOMM 70 #define TARGET_EPROTO 71 #define TARGET_EDOTDOT 73 #define TARGET_EMULTIHOP 74 #define TARGET_EBADMSG 77 #define TARGET_ENAMETOOLONG 78 #define TARGET_EOVERFLOW 79 #define TARGET_ENOTUNIQ 80 #define TARGET_EBADFD 81 #define TARGET_EREMCHG 82 #define TARGET_ELIBACC 83 #define TARGET_ELIBBAD 84 #define TARGET_ELIBSCN 85 #define TARGET_ELIBMAX 86 #define TARGET_ELIBEXEC 87 #define TARGET_EILSEQ 88 #define TARGET_ENOSYS 89 #define TARGET_ELOOP 90 #define TARGET_ERESTART 91 #define TARGET_ESTRPIPE 92 #define TARGET_ENOTEMPTY 93 #define TARGET_EUSERS 94 #define TARGET_ENOTSOCK 95 #define TARGET_EDESTADDRREQ 96 #define TARGET_EMSGSIZE 97 #define TARGET_EPROTOTYPE 98 #define TARGET_ENOPROTOOPT 99 #define TARGET_EPROTONOSUPPORT 120 #define TARGET_ESOCKTNOSUPPORT 121 #define TARGET_EOPNOTSUPP 122 #define TARGET_ENOTSUP EOPNOTSUPP #define TARGET_EPFNOSUPPORT 123 #define TARGET_EAFNOSUPPORT 124 #define TARGET_EADDRINUSE 125 #define TARGET_EADDRNOTAVAIL 126 #define TARGET_ENETDOWN 127 #define TARGET_ENETUNREACH 128 #define TARGET_ENETRESET 129 #define TARGET_ECONNABORTED 130 #define TARGET_ECONNRESET 131 #define TARGET_ENOBUFS 132 #define TARGET_EISCONN 133 #define TARGET_ENOTCONN 134 #define TARGET_EUCLEAN 135 #define TARGET_ENOTNAM 137 #define TARGET_ENAVAIL 138 #define TARGET_EISNAM 139 #define TARGET_EREMOTEIO 140 #define TARGET_ESHUTDOWN 143 #define TARGET_ETOOMANYREFS 144 #define TARGET_ETIMEDOUT 145 #define TARGET_ECONNREFUSED 146 #define TARGET_EHOSTDOWN 147 #define TARGET_EHOSTUNREACH 148 #define TARGET_EWOULDBLOCK EAGAIN #define TARGET_EALREADY 149 #define TARGET_EINPROGRESS 150 #define TARGET_ESTALE 151 #define TARGET_ECANCELED 158 #define TARGET_ENOMEDIUM 159 #define TARGET_EMEDIUMTYPE 160 #define TARGET_ENOKEY 161 #define TARGET_EKEYEXPIRED 162 #define TARGET_EKEYREVOKED 163 #define TARGET_EKEYREJECTED 164 #define TARGET_EOWNERDEAD 165 #define TARGET_ENOTRECOVERABLE 166 #define TARGET_ERFKILL 167 #define TARGET_EHWPOISON 168 #define TARGET_EDQUOT 1133 #endif /*TARGET_ERRNO_H*/ ================================================ FILE: src/project_info.h.in ================================================ /** * Transfers project information form CMake to C++. * @file */ #ifndef QTRVSIM_PROJECT_INFO_H_IN #define QTRVSIM_PROJECT_INFO_H_IN constexpr const char *COPYRIGHT_HTML = "@COPYRIGHT_HTML@"; constexpr const char *LICENCE_HTML = "@LICENCE_SHORT_HTML@"; #endif // QTRVSIM_PROJECT_INFO_H_IN ================================================ FILE: tests/cli/asm_error/program.S ================================================ THIS IS INVALID ASM CODE ================================================ FILE: tests/cli/modifiers/program.S ================================================ lw x0, 0(x0) lw x0, fail(x0) lw x0, %lo(label)(x0) addi x0, x0, %lo(0xDEA) li x1, label lui x2, %hi(label) addi x2, x2, %lo(label) bne x1, x2, fail ebreak nop nop fail: .data .word 0 .org 0xABCDE123 label: ================================================ FILE: tests/cli/modifiers/stdout.txt ================================================ Machine stopped on BREAK exception. ================================================ FILE: tests/cli/modifiers-pcrel/program.S ================================================ label: nop nop la x1, label auipc x2, %pcrel_hi(label) addi x2, x2, %pcrel_lo(label) bne x1, x1, fail ebreak nop nop fail: .data .word 0 ================================================ FILE: tests/cli/modifiers-pcrel/stdout.txt ================================================ Machine stopped on BREAK exception. ================================================ FILE: tests/cli/stalls/program.S ================================================ .text _start: loop: addi x1, x0, 0x11 addi x2, x0, 0x22 addi x3, x0, 0x33 lw x4, 0x44(x0) addi x5, x4, 0x55 beq x0, x0, tgt1 addi x11, x0, 0x11 addi x12, x0, 0x22 addi x13, x0, 0x33 addi x14, x0, 0x44 addi x15, x0, 0x55 tgt1: addi x21, x0, 0x11 addi x22, x0, 0x22 addi x23, x0, 0x33 addi x24, x0, 0x44 addi x25, x0, 0x55 ebreak ================================================ FILE: tests/cli/stalls/stdout.txt ================================================ Machine stopped on BREAK exception. Machine state report: PC:0x00000244 R0:0x00000000 R1:0x00000011 R2:0x00000022 R3:0x00000033 R4:0x00000000 R5:0x00000055 R6:0x00000000 R7:0x00000000 R8:0x00000000 R9:0x00000000 R10:0x00000000 R11:0x00000000 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000011 R22:0x00000022 R23:0x00000033 R24:0x00000044 R25:0x00000055 R26:0x00000000 R27:0x00000000 R28:0x00000000 R29:0x00000000 R30:0x00000000 R31:0x00000000 cycle: 0x0000000c mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000000 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0x00000240 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x0000000c minstret: 0x0000000b sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x00000000 ================================================ FILE: tests/cli/virtual_memory/dtlb/program.S ================================================ // D-TLB test — map data pages at MAP_VA; write 'A'..'H' to the first byte of eight pages and read/print them. .globl _start .option norelax // Serial port/terminal registers .equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region .equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG .equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG .equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG .equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte .equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG .equ ROOT_PT_PHYS, 0x1000 .equ MAP_VA, 0xC4000000 // PTE flags: 0xCF = 11001111 // bit0 V = 1 (Valid) // bit1 R = 1 (Readable) // bit2 W = 1 Writable) // bit3 X = 1 (Executable) // bit4 U = 0 (NOT user-accessible) // bit5 G = 0 (NOT global) // bit6 A = 1 (Accessed) // bit7 D = 1 (Dirty) .equ PTE_FLAGS_FULL, 0xCF // mstatus MPP manipulation masks (for preparing mret to change privilege) .equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) .equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) .equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) .org 0x00000200 .text _start: // t0 = physical address of root page table li t0, ROOT_PT_PHYS // t4 = virtual address we want to map (MAP_VA) li t4, MAP_VA // Build leaf PTE srli t1, t4, 12 slli t1, t1, 10 li t6, PTE_FLAGS_FULL or t1, t1, t6 srli t5, t4, 22 slli t2, t5, 2 add t3, t0, t2 sw t1, 0(t3) fence // Second-level page table physical address (must be page-aligned) li s0, 0x2000 // root PTE => pointer to second level table (non-leaf PTE: V=1, R/W/X = 0) // compute ppn of second-level table = 0x2000 >> 12 = 2 li t1, 2 slli t1, t1, 10 // place ppn at bits [31:10] li t6, 1 // V flag only (non-leaf) or t1, t1, t6 // root PTE pointing to second-level table li t4, SERIAL_PORT_BASE // serial VA = 0xffffc000 srli t5, t4, 22 // vpn1 for serial VA (0x3ff) slli t2, t5, 2 add t3, t0, t2 // address of root PTE slot for serial region sw t1, 0(t3) fence // Now create the level-0 (leaf) PTE inside the second-level page table // leaf PTE: PPN = device_phys >> 12, flags = PTE_FLAGS_FULL // compute leaf PTE value (maps the single 4KB page) srli t1, t4, 12 // t1 = device_phys >> 12 (we want identity mapping) slli t1, t1, 10 // place ppn li t6, PTE_FLAGS_FULL or t1, t1, t6 // t1 = leaf PTE // compute vpn0 index and byte offset in second-level table srli t5, t4, 12 andi t5, t5, 0x3FF // vpn0 = (va>>12) & 0x3FF slli t5, t5, 2 // byte offset = vpn0*4 add t3, s0, t5 // second-level-table-phys (s0) + offset sw t1, 0(t3) fence // Ensure satp is cleared before setting new value (flush previous translations) li t0, 0 csrw satp, t0 // Enable the MMU by writing SATP; this switches address translation on li t0, SATP_ENABLE csrw satp, t0 fence // Prepare mstatus MPP so that mret will return to Supervisor mode: // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). li t0, MSTATUS_MPP_CLEAR csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP li t0, MSTATUS_MPP_SET csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP // Set mepc to the virtual address of vm_entry and return from machine mode to // the prepared privilege level (Supervisor) using mret. la t0, vm_entry // load address of vm_entry (virtual address after mapping) csrw mepc, t0 mret .org 0xC4000000 .text vm_entry: li t0, SERIAL_PORT_BASE la t1, MAP_VA // pointer to start of mapped virtual region li t2, 0 // page counter li t3, 8 // number of pages to write/read (A..H) li t4, 65 // ASCII 'A' li t5, 0x1000 // page size (4KB) // write_pages_loop: write one byte (A..H) at the start of each mapped page. write_pages_loop: sb t4, 0(t1) add t1, t1, t5 addi t4, t4, 1 addi t2, t2, 1 blt t2, t3, write_pages_loop // Reset pointer and counter to read back and print the first byte of each page. la t1, MAP_VA li t2, 0 read_print_loop: lb t6, 0(t1) // load the byte stored at start of current page // wait_tx: poll transmitter status until ready, then write byte to TX data reg. wait_tx: lw t4, SERP_TX_ST_REG_o(t0) andi t4, t4, SERP_TX_ST_REG_READY_m beq t4, zero, wait_tx sw t6, SERP_TX_DATA_REG_o(t0) add t1, t1, t5 addi t2, t2, 1 blt t2, t3, read_print_loop ebreak 1: auipc t0, 0 jalr zero, 0(t0) ================================================ FILE: tests/cli/virtual_memory/dtlb/stdout.txt ================================================ Machine stopped on BREAK exception. Machine state report: PC:0xc4000078 R0:0x00000000 R1:0x00000000 R2:0xbfffff00 R3:0x00000000 R4:0x00000000 R5:0xffffffffffffc000 R6:0xffffffffc4008000 R7:0x00000008 R8:0x00002000 R9:0x00000000 R10:0x00000000 R11:0x00000000 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0x00000008 R29:0x00000001 R30:0x00001000 R31:0x00000048 cycle: 0x000000b2 mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000080 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0xc4000074 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x000000b2 minstret: 0x000000b1 sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 ================================================ FILE: tests/cli/virtual_memory/exec/program.S ================================================ // Place a tiny function in the mapped virtual page and jump to it (tests X bit). .globl _start .option norelax // Serial port/terminal registers .equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region .equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG .equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG .equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG .equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte .equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG .equ ROOT_PT_PHYS, 0x1000 .equ MAP_VA, 0xC4000000 // PTE flags: 0xCF = 11001111 // bit0 V = 1 (Valid) // bit1 R = 1 (Readable) // bit2 W = 1 Writable) // bit3 X = 1 (Executable) // bit4 U = 0 (NOT user-accessible) // bit5 G = 0 (NOT global) // bit6 A = 1 (Accessed) // bit7 D = 1 (Dirty) .equ PTE_FLAGS_FULL, 0xCF // mstatus MPP manipulation masks (for preparing mret to change privilege) .equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) .equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) .equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) .org 0x00000200 .text _start: // t0 = physical address of root page table li t0, ROOT_PT_PHYS // t4 = virtual address we want to map (MAP_VA) li t4, MAP_VA // Build leaf PTE srli t1, t4, 12 slli t1, t1, 10 li t6, PTE_FLAGS_FULL or t1, t1, t6 srli t5, t4, 22 slli t2, t5, 2 add t3, t0, t2 sw t1, 0(t3) fence // Second-level page table physical address (must be page-aligned) li s0, 0x2000 // root PTE => pointer to second level table (non-leaf PTE: V=1, R/W/X = 0) // compute ppn of second-level table = 0x2000 >> 12 = 2 li t1, 2 slli t1, t1, 10 // place ppn at bits [31:10] li t6, 1 // V flag only (non-leaf) or t1, t1, t6 // root PTE pointing to second-level table li t4, SERIAL_PORT_BASE // serial VA = 0xffffc000 srli t5, t4, 22 // vpn1 for serial VA (0x3ff) slli t2, t5, 2 add t3, t0, t2 // address of root PTE slot for serial region sw t1, 0(t3) fence // Now create the level-0 (leaf) PTE inside the second-level page table // leaf PTE: PPN = device_phys >> 12, flags = PTE_FLAGS_FULL // compute leaf PTE value (maps the single 4KB page) srli t1, t4, 12 // t1 = device_phys >> 12 (we want identity mapping) slli t1, t1, 10 // place ppn li t6, PTE_FLAGS_FULL or t1, t1, t6 // t1 = leaf PTE // compute vpn0 index and byte offset in second-level table srli t5, t4, 12 andi t5, t5, 0x3FF // vpn0 = (va>>12) & 0x3FF slli t5, t5, 2 // byte offset = vpn0*4 add t3, s0, t5 // second-level-table-phys (s0) + offset sw t1, 0(t3) fence // Ensure satp is cleared before setting new value (flush previous translations) li t0, 0 csrw satp, t0 // Enable the MMU by writing SATP; this switches address translation on li t0, SATP_ENABLE csrw satp, t0 fence // Prepare mstatus MPP so that mret will return to Supervisor mode: // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). li t0, MSTATUS_MPP_CLEAR csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP li t0, MSTATUS_MPP_SET csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP // Set mepc to the virtual address of vm_entry and return from machine mode to // the prepared privilege level (Supervisor) using mret. la t0, vm_entry // load address of vm_entry (virtual address after mapping) csrw mepc, t0 mret .org 0xC4000000 .text vm_entry: li a0, SERIAL_PORT_BASE // Call the function placed in the same mapped page la t0, mapped_function jalr zero, 0(t0) ebreak // small function placed in the mapped page .org 0xC4000100 mapped_function: li a0, SERIAL_PORT_BASE li t1, 88 wait_tx: lw t0, SERP_TX_ST_REG_o(a0) andi t0, t0, SERP_TX_ST_REG_READY_m beq t0, zero, wait_tx sw t1, SERP_TX_DATA_REG_o(a0) ebreak ================================================ FILE: tests/cli/virtual_memory/exec/stdout.txt ================================================ Machine stopped on BREAK exception. Machine state report: PC:0xc4000124 R0:0x00000000 R1:0x00000000 R2:0xbfffff00 R3:0x00000000 R4:0x00000000 R5:0x00000001 R6:0x00000058 R7:0x00000ffc R8:0x00002000 R9:0x00000000 R10:0xffffffffffffc000 R11:0x00000000 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0x00002ff0 R29:0xffffffffffffc000 R30:0x00000ff0 R31:0x000000cf cycle: 0x00000047 mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000080 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0xc4000120 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x00000047 minstret: 0x00000046 sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 ================================================ FILE: tests/cli/virtual_memory/itlb/program.S ================================================ // I-TLB test — map code pages at MAP_VA and execute tiny functions placed on eight // pages to print A–H, exercising instruction fetch from different pages. .globl _start .option norelax // Serial port/terminal registers .equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region .equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG .equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG .equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG .equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte .equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG .equ ROOT_PT_PHYS, 0x1000 .equ MAP_VA, 0xC4000000 // PTE flags: 0xCF = 11001111 // bit0 V = 1 (Valid) // bit1 R = 1 (Readable) // bit2 W = 1 Writable) // bit3 X = 1 (Executable) // bit4 U = 0 (NOT user-accessible) // bit5 G = 0 (NOT global) // bit6 A = 1 (Accessed) // bit7 D = 1 (Dirty) .equ PTE_FLAGS_FULL, 0xCF // mstatus MPP manipulation masks (for preparing mret to change privilege) .equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) .equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) .equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) .org 0x00000200 .text _start: // t0 = physical address of root page table li t0, ROOT_PT_PHYS // t4 = virtual address we want to map (MAP_VA) li t4, MAP_VA // Build leaf PTE srli t1, t4, 12 slli t1, t1, 10 li t6, PTE_FLAGS_FULL or t1, t1, t6 srli t5, t4, 22 slli t2, t5, 2 add t3, t0, t2 sw t1, 0(t3) fence // Second-level page table physical address (must be page-aligned) li s0, 0x2000 // root PTE => pointer to second level table (non-leaf PTE: V=1, R/W/X = 0) // compute ppn of second-level table = 0x2000 >> 12 = 2 li t1, 2 slli t1, t1, 10 // place ppn at bits [31:10] li t6, 1 // V flag only (non-leaf) or t1, t1, t6 // root PTE pointing to second-level table li t4, SERIAL_PORT_BASE // serial VA = 0xffffc000 srli t5, t4, 22 // vpn1 for serial VA (0x3ff) slli t2, t5, 2 add t3, t0, t2 // address of root PTE slot for serial region sw t1, 0(t3) fence // Now create the level-0 (leaf) PTE inside the second-level page table // leaf PTE: PPN = device_phys >> 12, flags = PTE_FLAGS_FULL // compute leaf PTE value (maps the single 4KB page) srli t1, t4, 12 // t1 = device_phys >> 12 (we want identity mapping) slli t1, t1, 10 // place ppn li t6, PTE_FLAGS_FULL or t1, t1, t6 // t1 = leaf PTE // compute vpn0 index and byte offset in second-level table srli t5, t4, 12 andi t5, t5, 0x3FF // vpn0 = (va>>12) & 0x3FF slli t5, t5, 2 // byte offset = vpn0*4 add t3, s0, t5 // second-level-table-phys (s0) + offset sw t1, 0(t3) fence // Ensure satp is cleared before setting new value (flush previous translations) li t0, 0 csrw satp, t0 // Enable the MMU by writing SATP; this switches address translation on li t0, SATP_ENABLE csrw satp, t0 fence // Prepare mstatus MPP so that mret will return to Supervisor mode: // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). li t0, MSTATUS_MPP_CLEAR csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP li t0, MSTATUS_MPP_SET csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP // Set mepc to the virtual address of vm_entry and return from machine mode to // the prepared privilege level (Supervisor) using mret. la t0, vm_entry // load address of vm_entry (virtual address after mapping) csrw mepc, t0 mret .org 0xC4007000 .text vm_entry: li t0, SERIAL_PORT_BASE li t4, 65 // 'A' // print_self: print current char, then step through executing code on other mapped pages. print_self: lw t6, SERP_TX_ST_REG_o(t0) andi t6, t6, SERP_TX_ST_REG_READY_m beq t6, zero, print_self sw t4, SERP_TX_DATA_REG_o(t0) addi t4, t4, 1 // Execute code placed in separate mapped pages. Each page contains a tiny // function that prints the current character then returns using the address // stored in t3. The sequence tests I-TLB / instruction fetch of different pages. la t1, page_func_0 la t3, resume_0 jalr zero, 0(t1) resume_0: addi t4, t4, 1 la t1, page_func_1 la t3, resume_1 jalr zero, 0(t1) resume_1: addi t4, t4, 1 la t1, page_func_2 la t3, resume_2 jalr zero, 0(t1) resume_2: addi t4, t4, 1 la t1, page_func_3 la t3, resume_3 jalr zero, 0(t1) resume_3: addi t4, t4, 1 la t1, page_func_4 la t3, resume_4 jalr zero, 0(t1) resume_4: addi t4, t4, 1 la t1, page_func_5 la t3, resume_5 jalr zero, 0(t1) resume_5: addi t4, t4, 1 la t1, page_func_6 la t3, resume_6 jalr zero, 0(t1) resume_6: addi t4, t4, 1 ebreak .org 0xC4000000 page_func_0: // Each page_func_* polls transmitter, writes current char (t4) then jumps // back to the resume address stored in t3. These functions live on // separate mapped pages to exercise instruction fetch from different pages. lw t6, SERP_TX_ST_REG_o(t0) andi t6, t6, SERP_TX_ST_REG_READY_m beq t6, zero, page_func_0 sw t4, SERP_TX_DATA_REG_o(t0) jalr zero, 0(t3) .org 0xC4001000 page_func_1: lw t6, SERP_TX_ST_REG_o(t0) andi t6, t6, SERP_TX_ST_REG_READY_m beq t6, zero, page_func_1 sw t4, SERP_TX_DATA_REG_o(t0) jalr zero, 0(t3) .org 0xC4002000 page_func_2: lw t6, SERP_TX_ST_REG_o(t0) andi t6, t6, SERP_TX_ST_REG_READY_m beq t6, zero, page_func_2 sw t4, SERP_TX_DATA_REG_o(t0) jalr zero, 0(t3) .org 0xC4003000 page_func_3: lw t6, SERP_TX_ST_REG_o(t0) andi t6, t6, SERP_TX_ST_REG_READY_m beq t6, zero, page_func_3 sw t4, SERP_TX_DATA_REG_o(t0) jalr zero, 0(t3) .org 0xC4004000 page_func_4: lw t6, SERP_TX_ST_REG_o(t0) andi t6, t6, SERP_TX_ST_REG_READY_m beq t6, zero, page_func_4 sw t4, SERP_TX_DATA_REG_o(t0) jalr zero, 0(t3) .org 0xC4005000 page_func_5: lw t6, SERP_TX_ST_REG_o(t0) andi t6, t6, SERP_TX_ST_REG_READY_m beq t6, zero, page_func_5 sw t4, SERP_TX_DATA_REG_o(t0) jalr zero, 0(t3) .org 0xC4006000 page_func_6: lw t6, SERP_TX_ST_REG_o(t0) andi t6, t6, SERP_TX_ST_REG_READY_m beq t6, zero, page_func_6 sw t4, SERP_TX_DATA_REG_o(t0) jalr zero, 0(t3) ================================================ FILE: tests/cli/virtual_memory/itlb/stdout.txt ================================================ Machine stopped on BREAK exception. Machine state report: PC:0xc40070d0 R0:0x00000000 R1:0x00000000 R2:0xbfffff00 R3:0x00000000 R4:0x00000000 R5:0xffffffffffffc000 R6:0xffffffffc4006000 R7:0x00000ffc R8:0x00002000 R9:0x00000000 R10:0x00000000 R11:0x00000000 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0xffffffffc40070c8 R29:0x00000049 R30:0x00000ff0 R31:0x00000001 cycle: 0x00000090 mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000080 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0xc40070cc mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x00000090 minstret: 0x0000008f sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 ================================================ FILE: tests/cli/virtual_memory/memrw/program.S ================================================ // Write ASCII bytes into the mapped virtual page and read them back printing to verify. .globl _start .option norelax // Serial port/terminal registers .equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region .equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG .equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG .equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG .equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte .equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG .equ ROOT_PT_PHYS, 0x1000 .equ MAP_VA, 0xC4000000 // PTE flags: 0xCF = 11001111 // bit0 V = 1 (Valid) // bit1 R = 1 (Readable) // bit2 W = 1 Writable) // bit3 X = 1 (Executable) // bit4 U = 0 (NOT user-accessible) // bit5 G = 0 (NOT global) // bit6 A = 1 (Accessed) // bit7 D = 1 (Dirty) .equ PTE_FLAGS_FULL, 0xCF // mstatus MPP manipulation masks (for preparing mret to change privilege) .equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) .equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) .equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) .org 0x00000200 .text _start: // t0 = physical address of root page table li t0, ROOT_PT_PHYS // t4 = virtual address we want to map (MAP_VA) li t4, MAP_VA // Build leaf PTE srli t1, t4, 12 slli t1, t1, 10 li t6, PTE_FLAGS_FULL or t1, t1, t6 srli t5, t4, 22 slli t2, t5, 2 add t3, t0, t2 sw t1, 0(t3) fence // Second-level page table physical address (must be page-aligned) li s0, 0x2000 // root PTE => pointer to second level table (non-leaf PTE: V=1, R/W/X = 0) // compute ppn of second-level table = 0x2000 >> 12 = 2 li t1, 2 slli t1, t1, 10 // place ppn at bits [31:10] li t6, 1 // V flag only (non-leaf) or t1, t1, t6 // root PTE pointing to second-level table li t4, SERIAL_PORT_BASE // serial VA = 0xffffc000 srli t5, t4, 22 // vpn1 for serial VA (0x3ff) slli t2, t5, 2 add t3, t0, t2 // address of root PTE slot for serial region sw t1, 0(t3) fence // Now create the level-0 (leaf) PTE inside the second-level page table // leaf PTE: PPN = device_phys >> 12, flags = PTE_FLAGS_FULL // compute leaf PTE value (maps the single 4KB page) srli t1, t4, 12 // t1 = device_phys >> 12 (we want identity mapping) slli t1, t1, 10 // place ppn li t6, PTE_FLAGS_FULL or t1, t1, t6 // t1 = leaf PTE // compute vpn0 index and byte offset in second-level table srli t5, t4, 12 andi t5, t5, 0x3FF // vpn0 = (va>>12) & 0x3FF slli t5, t5, 2 // byte offset = vpn0*4 add t3, s0, t5 // second-level-table-phys (s0) + offset sw t1, 0(t3) fence // Ensure satp is cleared before setting new value (flush previous translations) li t0, 0 csrw satp, t0 // Enable the MMU by writing SATP; this switches address translation on li t0, SATP_ENABLE csrw satp, t0 fence // Prepare mstatus MPP so that mret will return to Supervisor mode: // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). li t0, MSTATUS_MPP_CLEAR csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP li t0, MSTATUS_MPP_SET csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP // Set mepc to the virtual address of vm_entry and return from machine mode to // the prepared privilege level (Supervisor) using mret. la t0, vm_entry // load address of vm_entry (virtual address after mapping) csrw mepc, t0 mret .org 0xC4000000 .text vm_entry: li a0, SERIAL_PORT_BASE // pointer to mapped virtual page la a1, MAP_VA // write ASCII letters A..H into the first 8 bytes li t0, 65 sb t0, 0(a1) li t0, 66 sb t0, 1(a1) li t0, 67 sb t0, 2(a1) li t0, 68 sb t0, 3(a1) li t0, 69 sb t0, 4(a1) li t0, 70 sb t0, 5(a1) li t0, 71 sb t0, 6(a1) li t0, 72 sb t0, 7(a1) // Now read back and print each byte li t5, 0 read_print_loop: lb t1, 0(a1) // print t1 wait_tx2: lw t0, SERP_TX_ST_REG_o(a0) andi t0, t0, SERP_TX_ST_REG_READY_m beq t0, zero, wait_tx2 sw t1, SERP_TX_DATA_REG_o(a0) addi a1, a1, 1 addi t5, t5, 1 li t6, 8 blt t5, t6, read_print_loop ebreak ================================================ FILE: tests/cli/virtual_memory/memrw/stdout.txt ================================================ Machine stopped on BREAK exception. Machine state report: PC:0xc40000a4 R0:0x00000000 R1:0x00000000 R2:0xbfffff00 R3:0x00000000 R4:0x00000000 R5:0x00000001 R6:0x00000048 R7:0x00000ffc R8:0x00002000 R9:0x00000000 R10:0xffffffffffffc000 R11:0xffffffffc4000008 R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0x00002ff0 R29:0xffffffffffffc000 R30:0x00000008 R31:0x00000008 cycle: 0x000000a8 mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000080 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0xc40000a0 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x000000a8 minstret: 0x000000a7 sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 ================================================ FILE: tests/cli/virtual_memory/template/program.S ================================================ // Test template: Sets up a page table, enables virtual memory, and prints "Hello world" via serial port. .globl _start .option norelax // Serial port/terminal registers .equ SERIAL_PORT_BASE, 0xffffc000 // base address of serial port region .equ SERP_RX_ST_REG_o, 0x0000 // Offset of RX_ST_REG .equ SERP_RX_DATA_REG_o, 0x0004 // Offset of RX_DATA_REG .equ SERP_TX_ST_REG_o, 0x0008 // Offset of TX_ST_REG .equ SERP_TX_ST_REG_READY_m,0x1 // Transmitter can accept next byte .equ SERP_TX_DATA_REG_o, 0x000c // Offset of TX_DATA_REG .equ ROOT_PT_PHYS, 0x1000 .equ MAP_VA, 0xC4000000 // PTE flags: 0xCF = 11001111 // bit0 V = 1 (Valid) // bit1 R = 1 (Readable) // bit2 W = 1 Writable) // bit3 X = 1 (Executable) // bit4 U = 0 (NOT user-accessible) // bit5 G = 0 (NOT global) // bit6 A = 1 (Accessed) // bit7 D = 1 (Dirty) .equ PTE_FLAGS_FULL, 0xCF // mstatus MPP manipulation masks (for preparing mret to change privilege) .equ MSTATUS_MPP_CLEAR, 0x1000 // mask to clear MPP[12] (set bit 12 -> will be cleared via csrrc) .equ MSTATUS_MPP_SET, 0x800 // mask to set MPP[11] (set bit 11 -> will be set via csrrs) .equ SATP_ENABLE, 0x80000001 // satp value to enable paging (implementation-specific) .org 0x00000200 .text _start: // t0 = physical address of root page table li t0, ROOT_PT_PHYS // t4 = virtual address we want to map (MAP_VA) li t4, MAP_VA // Build leaf PTE srli t1, t4, 12 slli t1, t1, 10 li t6, PTE_FLAGS_FULL or t1, t1, t6 srli t5, t4, 22 slli t2, t5, 2 add t3, t0, t2 sw t1, 0(t3) fence // Second-level page table physical address (must be page-aligned) li s0, 0x2000 // root PTE => pointer to second level table (non-leaf PTE: V=1, R/W/X = 0) // compute ppn of second-level table = 0x2000 >> 12 = 2 li t1, 2 slli t1, t1, 10 // place ppn at bits [31:10] li t6, 1 // V flag only (non-leaf) or t1, t1, t6 // root PTE pointing to second-level table li t4, SERIAL_PORT_BASE // serial VA = 0xffffc000 srli t5, t4, 22 // vpn1 for serial VA (0x3ff) slli t2, t5, 2 add t3, t0, t2 // address of root PTE slot for serial region sw t1, 0(t3) fence // Now create the level-0 (leaf) PTE inside the second-level page table // leaf PTE: PPN = device_phys >> 12, flags = PTE_FLAGS_FULL // compute leaf PTE value (maps the single 4KB page) srli t1, t4, 12 // t1 = device_phys >> 12 (we want identity mapping) slli t1, t1, 10 // place ppn li t6, PTE_FLAGS_FULL or t1, t1, t6 // t1 = leaf PTE // compute vpn0 index and byte offset in second-level table srli t5, t4, 12 andi t5, t5, 0x3FF // vpn0 = (va>>12) & 0x3FF slli t5, t5, 2 // byte offset = vpn0*4 add t3, s0, t5 // second-level-table-phys (s0) + offset sw t1, 0(t3) fence // Ensure satp is cleared before setting new value (flush previous translations) li t0, 0 csrw satp, t0 // Enable the MMU by writing SATP; this switches address translation on li t0, SATP_ENABLE csrw satp, t0 fence // Prepare mstatus MPP so that mret will return to Supervisor mode: // Clear MPP[12] bit then set MPP[11] bit (resulting MPP=01 => Supervisor). li t0, MSTATUS_MPP_CLEAR csrrc zero, mstatus, t0 // clear bit 12 of mstatus.MPP li t0, MSTATUS_MPP_SET csrrs zero, mstatus, t0 // set bit 11 of mstatus.MPP // Set mepc to the virtual address of vm_entry and return from machine mode to // the prepared privilege level (Supervisor) using mret. la t0, vm_entry // load address of vm_entry (virtual address after mapping) csrw mepc, t0 mret .org 0xC4000000 .text vm_entry: li a0, SERIAL_PORT_BASE la a1, hello_str print_next_char: // Load next byte from string; if zero (end), branch to done lb t1, 0(a1) beq t1, zero, print_done addi a1, a1, 1 // advance to next character wait_tx_ready: // Poll transmit status register until TX ready bit is set lw t0, SERP_TX_ST_REG_o(a0) andi t0, t0, SERP_TX_ST_REG_READY_m beq t0, zero, wait_tx_ready // Write byte to transmit-data register and loop for next char sw t1, SERP_TX_DATA_REG_o(a0) jal zero, print_next_char print_done: ebreak 1: auipc t0, 0 jalr zero, 0(t0) .data .org 0xC4000100 hello_str: .asciz "Hello world.\n" ================================================ FILE: tests/cli/virtual_memory/template/stdout.txt ================================================ Machine stopped on BREAK exception. Machine state report: PC:0xc4000034 R0:0x00000000 R1:0x00000000 R2:0xbfffff00 R3:0x00000000 R4:0x00000000 R5:0x00000001 R6:0x00000000 R7:0x00000ffc R8:0x00002000 R9:0x00000000 R10:0xffffffffffffc000 R11:0xffffffffc400010d R12:0x00000000 R13:0x00000000 R14:0x00000000 R15:0x00000000 R16:0x00000000 R17:0x00000000 R18:0x00000000 R19:0x00000000 R20:0x00000000 R21:0x00000000 R22:0x00000000 R23:0x00000000 R24:0x00000000 R25:0x00000000 R26:0x00000000 R27:0x00000000 R28:0x00002ff0 R29:0xffffffffffffc000 R30:0x00000ff0 R31:0x000000cf cycle: 0x000000a8 mvendorid: 0x00000000 marchid: 0x00000000 mimpid: 0x00000000 mhardid: 0x00000000 mstatus: 0x00000080 misa: 0x40001111 mie: 0x00000000 mtvec: 0x00000000 mscratch: 0x00000000 mepc: 0xc4000030 mcause: 0x00000003 mtval: 0x00000000 mip: 0x00000000 mtinst: 0x00000000 mtval2: 0x00000000 mcycle: 0x000000a8 minstret: 0x000000a7 sstatus: 0x00000000 stvec: 0x00000000 sscratch: 0x00000000 sepc: 0x00000000 scause: 0x00000000 stval: 0x00000000 satp: 0x80000001 ================================================ FILE: tests/riscv-official/.gitignore ================================================ ./isa/elf ./isa/dump ./isa/selftests/elf ./code/__pychache__ ================================================ FILE: tests/riscv-official/README.md ================================================ # qtrvsim-testing Adaptation of official RISC-V tests for QtRvSim. ## Dependencies - qtrvsim-cli - riscv64-unknown-elf-gcc OR clang - riscv64-unknown-elf-binutils OR llvm - python (>= 3.4) Note: with gcc make sure it supports all the used march options. ## Usage ```shell python qtrvsim_tester.py /path/to/qtrvsim-cli ``` - For more information use: `python qtrvsim_tester.py -h` ## Clang To use clang instead of gcc set those environment variables: ```shell export RISCV_COMPILER=clang export USE_CLANG_OPTS=true export RISCV_OBJDUMP_CMD=llvm-objdump ``` ================================================ FILE: tests/riscv-official/code/constants.py ================================================ ISA_PATH = "isa" ELF_PATH = "isa/elf/" SELF_PATH = "isa/selftests" SELF_ELF_PATH = SELF_PATH + "/elf" CACHE_SETTINGS = ["--d-cache", "lru,2,2,2,wb", "--i-cache", "lru,2,2,2"] ================================================ FILE: tests/riscv-official/code/helpers.py ================================================ def max_str_list(list): if (len(list) > 0): return len(max(list, key=lambda x: len(x))) return 0 # Decorative method, if output of the qtrvsim-cli is changed this will probably break!!! def print_formated_output(reg_dump): stdout = reg_dump.stdout.decode("utf-8") stderr = reg_dump.stderr.decode("utf-8") print(stderr) print(stdout) def res_translate(res): if (res == 0): return "FAIL" elif (res == 1): return "PASS" else: return "ERROR" def res_print(test, test_res, test_reg_dump, params): if (test_res == 1): if (not params.nopass): if (not params.fileprt): print(test + ": " + '\033[92m' + "PASS" + '\033[0m') else: print(test+":PASS") if (params.dregs): print_formated_output(test_reg_dump) return 1 elif (test_res == 0): if (not params.fileprt): print(test + ": " + '\033[93m' + "FAIL" + '\033[0m') else: print(test+":FAIL") if (not params.nodump or params.dregs): print_formated_output(test_reg_dump) return 0 else: if (not params.fileprt): print(test + ": " + '\033[91m' + "ERROR" + '\033[0m') else: print(test+":ERROR") if (not params.nodump or params.dregs): print_formated_output(test_reg_dump) return 0 def get_RVxx(tests, isa): isa = str(isa).lower() tests32 = list(filter(lambda t: str(t).__contains__("32"+isa), tests)) tests64 = list(filter(lambda t: str(t).__contains__("64"+isa), tests)) tests32.sort() tests64.sort() return tests32, tests64 ================================================ FILE: tests/riscv-official/code/myparse.py ================================================ import argparse def init_parser(): parser = argparse.ArgumentParser( description="Defeault tests are RV32UI and RV64UI. No pipeline and cache.") parser.add_argument('qtrvsim_cli', default='', help="Qtrvsim_cli to run tests. (RVxxUI is default)") # Test selection bsel = parser.add_argument_group( "bit lenght selection for official tests:") bsel.add_argument("--no-32", action="count", default=0, dest="no32", help="Disables 32-bit tests. (Only for official tests)") bsel.add_argument("--no-64", action="count", default=0, dest="no64", help="Disables 64-bit tests. (Only for official tests)") # Additional tests tsel = parser.add_argument_group("additional set of tests:") tsel.add_argument("-E", "--external", nargs=1, action="store", default="", help="Path to additional tests. Required to adhere to PASS,FAIL behavior of edited test enviroment.") tsel.add_argument("-M", "--multiply", action="count", default=0, help="Additional set of tests for multiplication.") tsel.add_argument("-A", "--atomic", action="count", default=0, help="Additional set of tests for atomic instructions.") tsel.add_argument("--CSR", action="count", default=0, help="Additional set of tests for control and status registers.") # QtRVSim_cli settings opts = parser.add_argument_group("QtRVSim options:") opts.add_argument("--pipeline", action="count", default=0, help="Simulator runs in pipelined configuration.") opts.add_argument("--cache", action="count", default=0, help="Simulator runs with d-cache and i-cache implemented. (d lru,2,2,2,wb ; i lru,2,2,2)") # Test aplication output options eout = parser.add_argument_group("output print options:") eout.add_argument("--no-pass", action="count", default=0, dest="nopass", help="Prints only failed tests for readability.") eout.add_argument("--no-dump", action="count", default=0, dest="nodump", help="Omits printing of registers in failed test.") eout.add_argument("--d-regs", action="count", default=0, dest="dregs", help="Prints output of qtrvsim --d-regs param. (Higher priority than other output options.)") eout.add_argument("--file", action="count", default=0, dest="fileprt", help="Removes most graphical output features.") # Test aplication good to have features gth = parser.add_argument_group("good to have:") gth.add_argument("-R", "--rebuild", action="count", default=0, help="Deletes tests and buidls them anew.") gth.add_argument("-S", "--selftest", action="count", default=0, dest="selftest", help="Enable simple tests to check if tester behaves properly.") gth.add_argument("--clean", action="count", default=0, dest="clean", help="Deletes all official and seftest elf files.") return parser ================================================ FILE: tests/riscv-official/code/selftesting.py ================================================ import testing as ts import constants as cn def self_test(sim_bin, params, src_path, tests): sh_pass = [] sh_fail = [] error = [] for test in tests: test_res, test_reg_dump = ts.check_reg_dump( ts.run_test(sim_bin, params, src_path + cn.SELF_ELF_PATH+"/", test)) if (test_res == 1 and str(test).__contains__("fail")): sh_fail.append(test) if (test_res == 0 and str(test).__contains__("pass")): sh_pass.append(test) if(test_res == 2): error.append(test) print("Test passed but was expected to fail:{0}".format(sh_fail)) print("Test failed but was expected to pass:{0}".format(sh_pass)) print("Test ended in error :{0}\n".format(error)) ================================================ FILE: tests/riscv-official/code/testing.py ================================================ import os import sys import pathlib import subprocess import helpers as hp import constants as cn def test_sim_bin(sim_bin): try: sim_test = subprocess.run( [sim_bin, "-h"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except: parser.print_help() sys.exit(1) if (sim_test.returncode == 0): return sim_bin, True return "", False def load_filenames(dir_path, rebuild): test_files = list(filter( lambda x: not str(x).startswith("."), os.listdir(dir_path+"/elf"))) if (len(test_files) != 0 and not rebuild): return test_files, True else: try: if (rebuild): print("Building {0}".format(dir_path)) else: print("No tests found. Trying to build tests.") tests_built = subprocess.run( ["make", "-C", dir_path], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) return load_filenames(dir_path, False) except: print("Failed to build tests. {0} ; {1}".format( dir_path, test_files)) sys.exit(1) # If output of the qtrvsim-cli is changed this will probably break !!! def check_reg_dump(reg_dump): res = 2 stdout = str(reg_dump.stdout) if (stdout.__contains__("R11:")): index = stdout.find("R11:") if (stdout[index:index+18].__contains__("600d")): res = 1 elif (stdout[index:index+18].__contains__("bad")): res = 0 return res, reg_dump def run_test(sim_bin, params, dir_path, filename): test_path = dir_path + filename try: param_bin = [sim_bin, test_path, "--d-regs"] if (params.pipeline): param_bin.append("--pipelined") if (params.cache): param_bin.extend(cn.CACHE_SETTINGS) return subprocess.run(param_bin, capture_output=True) except subprocess.CalledProcessError as err: print(err) sys.exit(1) def run_tests(sim_bin, params, dir_path, tests): succ = 0 max_width = 0 if (not params.fileprt): max_width = hp.max_str_list(tests) for test in tests: reg_dump = run_test(sim_bin, params, dir_path, test) test = test.ljust(max_width) test_res, test_reg_dump = check_reg_dump(reg_dump) succ += hp.res_print(test, test_res, test_reg_dump, params) if (not params.fileprt): print(str(succ) + "/" + str(len(tests)) + " tests succesfull.\n") def run_official_tests(sim_bin, params, src_path, tests): if (not params.no32): if (not params.fileprt): print("--- 32 bit register tests ---") run_tests(sim_bin, params, src_path + cn.ELF_PATH, tests[0]) if (not params.no64): if (not params.fileprt): print("--- 64 bit register tests ---") run_tests(sim_bin, params, src_path + cn.ELF_PATH, tests[1]) def test_selector(sim_bin, params, src_path, tests): if (params.pipeline): print("Simulator runs in pipelined mode.") if (params.cache): print("Simulator runs in cache mode.") if (params.fileprt): line = "" else: line = "-+-+-+-+-+-+-+-+-" print(line+"RVxxUI"+line) run_official_tests(sim_bin, params, src_path, hp.get_RVxx(tests, "ui")) if (params.multiply): print(line+"RVxxUM"+line) run_official_tests(sim_bin, params, src_path, hp.get_RVxx(tests, "um")) if (params.atomic): print(line+"RVxxUA"+line) run_official_tests(sim_bin, params, src_path, hp.get_RVxx(tests, "ua")) if (params.CSR): print(line+"RVxxSI"+line) run_official_tests(sim_bin, params, src_path, hp.get_RVxx(tests, "si")) print(line+"RVxxMI"+line) run_official_tests(sim_bin, params, src_path, hp.get_RVxx(tests, "mi")) if (len(params.external) > 0): print(line+"External Tests"+line) dir_path = params.external[0] test_files = os.listdir(dir_path) test_files = list( filter(lambda x: not str(x).startswith("."), test_files)) run_tests(sim_bin, params, dir_path, test_files) def delete_elf(src_path): try: subprocess.run(["make", "-C", src_path + cn.ISA_PATH, "clean"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) subprocess.run(["make", "-C", src_path + cn.SELF_PATH, "clean"], stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL) except subprocess.CalledProcessError as err: print("Unable to delete elf files.") print(err) ================================================ FILE: tests/riscv-official/env/p/BackUp/riscv_test.h ================================================ #define _ENV_PHYSICAL_SINGLE_CORE_H #include "../../riscv-tests/env/encoding.h" //----------------------------------------------------------------------- // Begin Macro //----------------------------------------------------------------------- #define RVTEST_RV64U \ .macro init; \ .endm #define RVTEST_RV32U \ .macro init; \ .endm #if __riscv_xlen == 64 # define CHECK_XLEN li a0, 1; slli a0, a0, 31; bgez a0, 1f; RVTEST_PASS; 1: #else # define CHECK_XLEN li a0, 1; slli a0, a0, 31; bltz a0, 1f; RVTEST_PASS; 1: #endif #define INIT_XREG \ li x1, 0; \ li x2, 0; \ li x3, 0; \ li x4, 0; \ li x5, 0; \ li x6, 0; \ li x7, 0; \ li x8, 0; \ li x9, 0; \ li x10, 0; \ li x11, 0; \ li x12, 0; \ li x13, 0; \ li x14, 0; \ li x15, 0; \ li x16, 0; \ li x17, 0; \ li x18, 0; \ li x19, 0; \ li x20, 0; \ li x21, 0; \ li x22, 0; \ li x23, 0; \ li x24, 0; \ li x25, 0; \ li x26, 0; \ li x27, 0; \ li x28, 0; \ li x29, 0; \ li x30, 0; \ li x31, 0; #define RVTEST_CODE_BEGIN \ .section .text.init; \ .align 6; \ .globl _start; \ _start: \ INIT_XREG; \ li TESTNUM, 0; \ CHECK_XLEN; \ .align 2; //----------------------------------------------------------------------- // End Macro //----------------------------------------------------------------------- #define RVTEST_CODE_END //----------------------------------------------------------------------- // Pass/Fail Macro //----------------------------------------------------------------------- #define RVTEST_PASS \ fence; \ li TESTNUM, 1; \ li a7, 93; \ li a1, 0x60; \ slli a1, a1, 8; \ addi a1, a1, 0xd; \ slli a1, a1, 12; \ ecall #define TESTNUM gp #define RVTEST_FAIL \ fence; \ 1: beqz TESTNUM, 1b; \ sll TESTNUM, TESTNUM, 1; \ or TESTNUM, TESTNUM, 1; \ li a7, 93; \ addi a0, TESTNUM, 0; \ addi a1,a1,0xBA; \ slli a1,a1,4; \ addi a1, a1, 0xD; \ slli a1, a1, 16; \ ecall //----------------------------------------------------------------------- // Data Section Macro //----------------------------------------------------------------------- #define EXTRA_DATA #define RVTEST_DATA_BEGIN \ // EXTRA_DATA \ .pushsection .tohost,"aw",@progbits; \ .align 6; .global tohost; tohost: .dword 0; \ .align 6; .global fromhost; fromhost: .dword 0; \ .popsection; \ .align 4; .global begin_signature; begin_signature: #define RVTEST_DATA_END //.align 4; .global end_signature; end_signature: ================================================ FILE: tests/riscv-official/env/p/link.ld ================================================ OUTPUT_ARCH( "riscv" ) ENTRY(_start) SECTIONS { . = 0x80000000; .text.init : { *(.text.init) } . = ALIGN(0x1000); .tohost : { *(.tohost) } . = ALIGN(0x1000); .text : { *(.text) } . = ALIGN(0x1000); .data : { *(.data) } .bss : { *(.bss) } _end = .; } ================================================ FILE: tests/riscv-official/env/p/riscv_test.h ================================================ #define _ENV_PHYSICAL_SINGLE_CORE_H #include "../../riscv-tests/env/encoding.h" //----------------------------------------------------------------------- // Begin Macro //----------------------------------------------------------------------- #define RVTEST_RV64U \ .macro init; \ .endm #define RVTEST_RV32U \ .macro init; \ .endm #define RVTEST_RV64M \ .macro init; \ RVTEST_ENABLE_MACHINE; \ .endm #define RVTEST_RV32M \ .macro init; \ RVTEST_ENABLE_MACHINE; \ .endm #define RVTEST_RV64S \ .macro init; \ RVTEST_ENABLE_SUPERVISOR; \ .endm #define RVTEST_RV32S \ .macro init; \ RVTEST_ENABLE_SUPERVISOR; \ .endm #if __riscv_xlen == 64 # define CHECK_XLEN li a0, 1; slli a0, a0, 31; bgez a0, 1f; RVTEST_ARCH_CHECK_FAIL; 1: #else # define CHECK_XLEN li a0, 1; slli a0, a0, 31; bltz a0, 1f; RVTEST_ARCH_CHECK_FAIL; 1: #endif #define INIT_XREG \ li x1, 0; \ li x2, 0; \ li x3, 0; \ li x4, 0; \ li x5, 0; \ li x6, 0; \ li x7, 0; \ li x8, 0; \ li x9, 0; \ li x10, 0; \ li x11, 0; \ li x12, 0; \ li x13, 0; \ li x14, 0; \ li x15, 0; \ li x16, 0; \ li x17, 0; \ li x18, 0; \ li x19, 0; \ li x20, 0; \ li x21, 0; \ li x22, 0; \ li x23, 0; \ li x24, 0; \ li x25, 0; \ li x26, 0; \ li x27, 0; \ li x28, 0; \ li x29, 0; \ li x30, 0; \ li x31, 0; #define RVTEST_CODE_BEGIN \ .section .text.init; \ .align 6; \ .globl _start; \ _start: \ INIT_XREG; \ CHECK_XLEN; \ .align 2; //----------------------------------------------------------------------- // End Macro //----------------------------------------------------------------------- #define RVTEST_CODE_END //----------------------------------------------------------------------- // Pass/Fail Macro //----------------------------------------------------------------------- #define RVTEST_PASS \ li a1, 0x60; \ slli a1, a1, 8; \ addi a1, a1, 0xd; \ slli a1, a1, 12; \ addi a0, zero, 0; \ addi a7, zero, 93; \ ecall #define TESTNUM gp #define RVTEST_FAIL \ addi a1, a1, 0xBA; \ slli a1, a1, 4; \ addi a1, a1, 0xD; \ slli a1, a1, 16; \ addi a0, TESTNUM, -1; \ sra a0, a0, 31; \ or a0, a0, TESTNUM; \ addi a7, zero, 93; \ ecall #define RVTEST_ARCH_CHECK_FAIL \ addi a1,a1,0xAC; \ slli a1,a1,4; \ addi a1, a1, 0xF; \ slli a1, a1, 16; \ addi a0, zero, 2000; \ addi a7, zero, 93; \ ecall //----------------------------------------------------------------------- // Data Section Macro //----------------------------------------------------------------------- #define EXTRA_DATA #define RVTEST_DATA_BEGIN #define RVTEST_DATA_END ================================================ FILE: tests/riscv-official/isa/.gitignore ================================================ rv*-* ================================================ FILE: tests/riscv-official/isa/Makefile ================================================ #======================================================================= # Makefile for riscv-tests/isa #----------------------------------------------------------------------- XLEN ?= 64 src_dir := . isa_dir := ../riscv-tests/isa ifeq ($(XLEN),64) include $(isa_dir)/rv64ui/Makefrag include $(isa_dir)/rv64ua/Makefrag include $(isa_dir)/rv64si/Makefrag include $(isa_dir)/rv64mi/Makefrag include $(isa_dir)/rv64um/Makefrag endif include $(isa_dir)/rv32ui/Makefrag include $(isa_dir)/rv32ua/Makefrag include $(isa_dir)/rv32si/Makefrag include $(isa_dir)/rv32mi/Makefrag include $(isa_dir)/rv32um/Makefrag default: clean all #-------------------------------------------------------------------- # Build rules #-------------------------------------------------------------------- include toolchain_setup vpath %.S $(isa_dir) $(self_test) #------------------------------------------------------------ # Build assembly tests %.dump: % $(RISCV_OBJDUMP) elf/$< > dump/$@ %.out: % $(RISCV_SIM) --isa=rv64gc $< 2> $@ %.out32: % $(RISCV_SIM) --isa=rv32gc $< 2> $@ define compile_template $$($(1)_p_tests): $(1)-p-%: $(1)/%.S $$(RISCV_COMPILER) $(2) $$(RISCV_COMPILER_OPTS) -I$(src_dir)/../env/p -I$(isa_dir)/macros/scalar -T$(src_dir)/../env/p/link.ld $$< -o elf/$$@ $(1)_tests += $$($(1)_p_tests) $(1)_tests_dump = $$(addsuffix .dump, $$($(1)_tests)) $(1): $$($(1)_tests_dump) .PHONY: $(1) COMPILER_SUPPORTS_$(1) := $$(shell $$(RISCV_COMPILER) $(2) -c -x c /dev/null -o /dev/null 2> /dev/null; echo $$$$?) ifeq ($$(COMPILER_SUPPORTS_$(1)),0) tests += $$($(1)_tests) endif endef $(eval $(call compile_template,rv32ui, $(MARCH_OPTS_32))) $(eval $(call compile_template,rv32um, $(MARCH_OPTS_32))) $(eval $(call compile_template,rv32ua, $(MARCH_OPTS_32))) $(eval $(call compile_template,rv32si, $(MARCH_OPTS_32))) $(eval $(call compile_template,rv32mi, $(MARCH_OPTS_32))) ifeq ($(XLEN),64) $(eval $(call compile_template,rv64ui, $(MARCH_OPTS_64))) $(eval $(call compile_template,rv64um, $(MARCH_OPTS_64))) $(eval $(call compile_template,rv64ua, $(MARCH_OPTS_64))) $(eval $(call compile_template,rv64si, $(MARCH_OPTS_64))) $(eval $(call compile_template,rv64mi, $(MARCH_OPTS_64))) endif tests_dump = $(addsuffix .dump, $(tests)) tests_hex = $(addsuffix .hex, $(tests)) tests_out = $(addsuffix .out, $(filter rv64%,$(tests))) tests32_out = $(addsuffix .out32, $(filter rv32%,$(tests))) run: $(tests_out) $(tests32_out) junk += $(tests) $(tests_dump) $(tests_hex) $(tests_out) $(tests32_out) #------------------------------------------------------------ self-tests: cd selftests && make all: $(tests_dump) self-tests #------------------------------------------------------------ # Clean up clean: rm -rf $(junk) rm -fr elf/* rm -fr dump/* rm -fr $(self_test)/elf/* ================================================ FILE: tests/riscv-official/isa/selftests/Makefile ================================================ #======================================================================= # Makefile for riscv-tests/isa #----------------------------------------------------------------------- XLEN ?= 64 src_dir := ../. isa_dir := ../../riscv-tests/isa self_test := . self_tests_t := $(wildcard tests/*.S) self_tests := $(self_tests_t:tests/%=%) elf_tests := $(self_tests:.S=.elf) default: clean self #-------------------------------------------------------------------- # Build rules #-------------------------------------------------------------------- include ../toolchain_setup vpath %.S $(self_test) #------------------------------------------------------------ # Build assembly tests self: $(elf_tests) %32.elf: tests/%32.S $(RISCV_COMPILER) $(MARCH_OPTS_32) $(RISCV_COMPILER_OPTS) -I$(src_dir)/../env/p -I$(isa_dir)/macros/scalar -T$(src_dir)/../env/p/link.ld $< -o $(self_test)/elf/$*32 %64.elf: tests/%64.S $(RISCV_COMPILER) $(MARCH_OPTS_64) $(RISCV_COMPILER_OPTS) -I$(src_dir)/../env/p -I$(isa_dir)/macros/scalar -T$(src_dir)/../env/p/link.ld $< -o $(self_test)/elf/$*64 #------------------------------------------------------------ # Clean up clean: rm -fr elf/* ================================================ FILE: tests/riscv-official/isa/selftests/options_test.sh ================================================ #!/bin/bash TESTER=$1 BIN=$2 EXTERNAL=$3 NULL=/dev/null # Test without required cli bin python $TESTER 1> $NULL 2> $NULL RES1=$? # Test default python $TESTER $BIN 1> $NULL 2> $NULL TMP=$? if [ $TMP -eq 0 ]; then echo "default passed" else echo "default failed" fi # Test help python $TESTER $BIN 1> $NULL 2> $NULL -h TMP=$? if [ $TMP -eq 0 ]; then echo "help passed" else echo "help failed" fi # Test no-32 python $TESTER $BIN 1> $NULL 2> $NULL --no-32 TMP=$? if [ $TMP -eq 0 ]; then echo "no-32 passed" else echo "no-32 failed" fi # Test no-64 python $TESTER $BIN 1> $NULL 2> $NULL --no-64 TMP=$? if [ $TMP -eq 0 ]; then echo "no-64 passed" else echo "no-64 failed" fi # Test external python $TESTER $BIN 1> $NULL 2> $NULL -E $EXTERNAL TMP=$? if [ $TMP -eq 0 ]; then echo "external passed" else echo "external failed" fi # Test multiply python $TESTER $BIN 1> $NULL 2> $NULL -M TMP=$? if [ $TMP -eq 0 ]; then echo "multiply passed" else echo "multiply failed" fi # Test atomic python $TESTER $BIN 1> $NULL 2> $NULL -A TMP=$? if [ $TMP -eq 0 ]; then echo "atomic passed" else echo "atomic failed" fi # Test CSR python $TESTER $BIN 1> $NULL 2> $NULL --CSR TMP=$? if [ $TMP -eq 0 ]; then echo "CSR passed" else echo "CSR failed" fi # Test pipeline python $TESTER $BIN 1> $NULL 2> $NULL --pipeline TMP=$? if [ $TMP -eq 0 ]; then echo "pipeline passed" else echo "pipeline failed" fi # Test cache python $TESTER $BIN 1> $NULL 2> $NULL --cache TMP=$? if [ $TMP -eq 0 ]; then echo "cache passed" else echo "cache failed" fi # Test no-pass python $TESTER $BIN 1> $NULL 2> $NULL --no-pass TMP=$? if [ $TMP -eq 0 ]; then echo "no-pass passed" else echo "no-pass failed" fi # Test no-dump python $TESTER $BIN 1> $NULL 2> $NULL --no-dump TMP=$? if [ $TMP -eq 0 ]; then echo "no-dump passed" else echo "no-dump failed" fi # Test d-regs python $TESTER $BIN 1> $NULL 2> $NULL --d-regs TMP=$? if [ $TMP -eq 0 ]; then echo "d-regs passed" else echo "d-regs failed" fi # Test file python $TESTER $BIN 1> $NULL 2> $NULL --file TMP=$? if [ $TMP -eq 0 ]; then echo "file passed" else echo "file failed" fi # Test rebuild python $TESTER $BIN 1> $NULL 2> $NULL -R TMP=$? if [ $TMP -eq 0 ]; then echo "rebuild passed" else echo "rebuild failed" fi # Test seltest python $TESTER $BIN 1> $NULL 2> $NULL -S TMP=$? if [ $TMP -eq 0 ]; then echo "seltest passed" else echo "seltest failed" fi # Test clean python $TESTER $BIN 1> $NULL 2> $NULL --clean TMP=$? if [ $TMP -eq 0 ]; then echo "clean passed" else echo "clean failed" fi ================================================ FILE: tests/riscv-official/isa/selftests/tests/addi-fail32.S ================================================ # See LICENSE for license details. #include "riscv_test.h" #undef RVTEST_RV64U #define RVTEST_RV64U RVTEST_RV32U #include "addi-fail64.S" ================================================ FILE: tests/riscv-official/isa/selftests/tests/addi-fail64.S ================================================ # See LICENSE for license details. #***************************************************************************** # addi.S #----------------------------------------------------------------------------- # # Test addi instruction. # #include "riscv_test.h" #include "test_macros.h" RVTEST_RV64U RVTEST_CODE_BEGIN #------------------------------------------------------------- # Arithmetic tests #------------------------------------------------------------- TEST_IMM_OP( 2, addi, 0x00000000, 0x00000000, 0xfff ); # <--- here is the mistake, it's on purpose TEST_IMM_OP( 3, addi, 0x00000002, 0x00000001, 0x001 ); TEST_IMM_OP( 4, addi, 0x0000000a, 0x00000003, 0x007 ); TEST_IMM_OP( 5, addi, 0xfffffffffffff800, 0x0000000000000000, 0x800 ); TEST_IMM_OP( 6, addi, 0xffffffff80000000, 0xffffffff80000000, 0x000 ); TEST_IMM_OP( 7, addi, 0xffffffff7ffff800, 0xffffffff80000000, 0x800 ); TEST_IMM_OP( 8, addi, 0x00000000000007ff, 0x00000000, 0x7ff ); TEST_IMM_OP( 9, addi, 0x000000007fffffff, 0x7fffffff, 0x000 ); TEST_IMM_OP( 10, addi, 0x00000000800007fe, 0x7fffffff, 0x7ff ); TEST_IMM_OP( 11, addi, 0xffffffff800007ff, 0xffffffff80000000, 0x7ff ); TEST_IMM_OP( 12, addi, 0x000000007ffff7ff, 0x000000007fffffff, 0x800 ); TEST_IMM_OP( 13, addi, 0xffffffffffffffff, 0x0000000000000000, 0xfff ); TEST_IMM_OP( 14, addi, 0x0000000000000000, 0xffffffffffffffff, 0x001 ); TEST_IMM_OP( 15, addi, 0xfffffffffffffffe, 0xffffffffffffffff, 0xfff ); TEST_IMM_OP( 16, addi, 0x0000000080000000, 0x7fffffff, 0x001 ); #------------------------------------------------------------- # Source/Destination tests #------------------------------------------------------------- TEST_IMM_SRC1_EQ_DEST( 17, addi, 24, 13, 11 ); #------------------------------------------------------------- # Bypassing tests #------------------------------------------------------------- TEST_IMM_DEST_BYPASS( 18, 0, addi, 24, 13, 11 ); TEST_IMM_DEST_BYPASS( 19, 1, addi, 23, 13, 10 ); TEST_IMM_DEST_BYPASS( 20, 2, addi, 22, 13, 9 ); TEST_IMM_SRC1_BYPASS( 21, 0, addi, 24, 13, 11 ); TEST_IMM_SRC1_BYPASS( 22, 1, addi, 23, 13, 10 ); TEST_IMM_SRC1_BYPASS( 23, 2, addi, 22, 13, 9 ); TEST_IMM_ZEROSRC1( 24, addi, 32, 32 ); TEST_IMM_ZERODEST( 25, addi, 33, 50 ); TEST_PASSFAIL RVTEST_CODE_END .data RVTEST_DATA_BEGIN TEST_DATA RVTEST_DATA_END ================================================ FILE: tests/riscv-official/isa/selftests/tests/simple-fail32.S ================================================ # See LICENSE for license details. #include "riscv_test.h" #undef RVTEST_RV64U #define RVTEST_RV64U RVTEST_RV32U #include "simple-fail64.S" ================================================ FILE: tests/riscv-official/isa/selftests/tests/simple-fail64.S ================================================ # See LICENSE for license details. #***************************************************************************** # simple.S #----------------------------------------------------------------------------- # # This is the most basic self checking test. If your simulator does not # pass thiss then there is little chance that it will pass any of the # more complicated self checking tests. # #include "riscv_test.h" #include "test_macros.h" RVTEST_RV64U RVTEST_CODE_BEGIN RVTEST_FAIL # <--- here is the mistake, it's on purpose RVTEST_CODE_END .data RVTEST_DATA_BEGIN TEST_DATA RVTEST_DATA_END ================================================ FILE: tests/riscv-official/isa/selftests/tests/simple-pass32.S ================================================ # See LICENSE for license details. #include "riscv_test.h" #undef RVTEST_RV64U #define RVTEST_RV64U RVTEST_RV32U #include "simple-pass64.S" ================================================ FILE: tests/riscv-official/isa/selftests/tests/simple-pass64.S ================================================ # See LICENSE for license details. #***************************************************************************** # simple.S #----------------------------------------------------------------------------- # # This is the most basic self checking test. If your simulator does not # pass thiss then there is little chance that it will pass any of the # more complicated self checking tests. # #include "riscv_test.h" #include "test_macros.h" RVTEST_RV64U RVTEST_CODE_BEGIN RVTEST_PASS # <--- here is the mistake, it's on purpose RVTEST_CODE_END .data RVTEST_DATA_BEGIN TEST_DATA RVTEST_DATA_END ================================================ FILE: tests/riscv-official/isa/toolchain_setup ================================================ RISCV_PREFIX ?= riscv$(XLEN)-unknown-elf- RISCV_COMPILER ?= $(RISCV_PREFIX)gcc RISCV_COMPILER_OPTS ?= -static -mcmodel=medany -fvisibility=hidden -nostdlib -nostartfiles RISCV_OBJDUMP_CMD ?= $(RISCV_PREFIX)objdump RISCV_OBJDUMP ?= $(RISCV_OBJDUMP_CMD) --disassemble-all --disassemble-zeroes --section=.text --section=.text.startup --section=.text.init --section=.data RISCV_SIM ?= spike # Clang used different options regarding march USE_CLANG_OPTS ?= false ifeq ($(USE_CLANG_OPTS), true) MARCH_OPTS_32 = -march=rv32g -mabi=ilp32 --target=riscv32 -mno-relax -fuse-ld=lld MARCH_OPTS_64 = -march=rv64g -mabi=lp64 --target=riscv64 -mno-relax -fuse-ld=lld else MARCH_OPTS_32 = -march=rv32g -mabi=ilp32 MARCH_OPTS_64 = -march=rv64g -mabi=lp64 endif ================================================ FILE: tests/riscv-official/qtrvsim_tester.py ================================================ import sys import os FILENAME = "qtrvsim_tester.py" SRC_DIR = os.path.realpath(__file__).replace(FILENAME, "") sys.path.append(SRC_DIR+"code") import constants as cn import myparse as mp import testing as ts import selftesting as sts parser = mp.init_parser() params = parser.parse_args() if (params.dregs): params.nodump = 0 params.nopass = 0 if(params.clean): ts.delete_elf(SRC_DIR) sys.exit(0) sim_bin, bin_check = ts.test_sim_bin(params.qtrvsim_cli) if(not bin_check): print("Problem with qtrvsim_cli binary!") sys.exit(1) self_files, s_file_check = ts.load_filenames( SRC_DIR + cn.SELF_PATH, bool(params.rebuild)) test_files, t_file_check = ts.load_filenames( SRC_DIR + cn.ISA_PATH, bool(params.rebuild)) if (params.selftest): sts.self_test(sim_bin, params, SRC_DIR, self_files) ts.test_selector(sim_bin, params, SRC_DIR, test_files) sys.exit(0) ================================================ FILE: tests/stud-support/build_tests.py ================================================ #!/usr/bin/env python3 """ Build stud-support ELFs for integration testing This script compiles selected stud-support examples into RISC-V ELFs using Clang/LLVM toolchain or GCC. """ import argparse import os import shutil import subprocess import sys import glob # List of tests to build: (output_name, source_directory, [optional: single_file]) TESTS_TO_BUILD = [ ("selection_sort", "seminaries/qtrvsim/selection-sort"), ("vect_add", "seminaries/qtrvsim/vect-add"), ("vect_add2", "seminaries/qtrvsim/vect-add2"), ("vect_inc", "seminaries/qtrvsim/vect-inc"), ("branchpred_1", "seminaries/qtrvsim/branchpred-1"), ("ffs_as_log2", "seminaries/qtrvsim/ffs-as-log2"), ("uart_echo_irq", "seminaries/qtrvsim/uart-echo-irq"), ("call_clobber", "seminaries/qtrvsim/call-syscall", "lec10-01-call4-clobber.S"), ("call_save", "seminaries/qtrvsim/call-syscall", "lec10-02-call4-save.S"), ("fact_buggy", "seminaries/qtrvsim/call-syscall", "lec10-03-fact-buggy.S"), ("fact_ok", "seminaries/qtrvsim/call-syscall", "lec10-04-fact-ok.S"), ("call_10args", "seminaries/qtrvsim/call-syscall", "lec10-05-call-10args.S"), ("linus_hello", "seminaries/qtrvsim/call-syscall", "lec10-06-linus-hello.S"), ] def get_toolchain_config(use_clang=False): """Get toolchain configuration""" arch = os.getenv("ARCH", "riscv64-unknown-elf") # Prefer GCC unless Clang is forced or GCC is missing and Clang is present if use_clang or (not shutil.which(f"{arch}-gcc") and shutil.which("clang")): cc = os.getenv("CC", "clang") cflags = os.getenv("CFLAGS", "--target=riscv32 -march=rv32g -nostdlib -static -fuse-ld=lld") return { "name": "Clang/LLVM", "cc": cc, "cflags": cflags, "ld": cc, "ldflags": cflags, "objcopy": "llvm-objcopy", "required": [cc, "ld.lld"] } cc = os.getenv("CC", f"{arch}-gcc") return { "name": "GNU", "cc": cc, "cflags": os.getenv("CFLAGS", ""), "ld": f"{arch}-ld", "ldflags": "", "objcopy": f"{arch}-objcopy", "required": [cc] } def check_toolchain(config): """Check if required tools are available""" for tool in config["required"]: if not shutil.which(tool): return False, f"Tool not found: {tool}" return True, "OK" def build_test(source_dir, output_name, toolchain, stud_support_root, output_dir, verbose=False, single_file=None): """Build a single test""" full_source_dir = os.path.join(stud_support_root, source_dir) output_elf = os.path.join(output_dir, f"{output_name}.elf") print(f"Building {output_name}...") if not os.path.isdir(full_source_dir): print(f" ERROR: Source directory not found: {full_source_dir}") return False, "source not found" try: if single_file: source_file = os.path.join(full_source_dir, single_file) if not os.path.isfile(source_file): print(f" ERROR: Source file not found: {source_file}") return False, "source file not found" _, ext = os.path.splitext(source_file) cmd = [toolchain["cc"]] if ext in ['.S', '.s']: cmd.append("-D__ASSEMBLY__") if toolchain["cflags"]: cmd.extend(toolchain["cflags"].split()) elif toolchain["name"] == "GNU": # Default flags for GCC if not specified cmd.extend(["-march=rv32im", "-mabi=ilp32", "-nostdlib", "-static"]) cmd.extend(["-o", output_elf, source_file]) result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) if result.returncode != 0: print(f" ERROR: Build failed with return code {result.returncode}") print(" Output:") print(result.stdout) print(" Error:") print(result.stderr) return False, "build error" print(f" SUCCESS: {output_elf}") return True, None else: # Makefile build subprocess.run(["make", "clean"], cwd=full_source_dir, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, check=False) make_vars = [ f"CC={toolchain['cc']}", f"AS={toolchain['cc']}", f"CXX={toolchain['cc']}", f"LD={toolchain['ld']}", f"OBJCOPY={toolchain['objcopy']}", f"LOADLIBES=" ] if toolchain['cflags']: make_vars.extend([ f"CFLAGS={toolchain['cflags']}", f"AFLAGS={toolchain['cflags']}", f"CXXFLAGS={toolchain['cflags']}" ]) if toolchain['ldflags']: make_vars.append(f"LDFLAGS={toolchain['ldflags']}") result = subprocess.run(["make"] + make_vars, cwd=full_source_dir, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True) if result.returncode != 0: print(f" ERROR: Build failed with return code {result.returncode}") print(" Output:") print(result.stdout) print(" Error:") print(result.stderr) return False, "build error" # Find built ELF candidates = [os.path.join(full_source_dir, os.path.basename(source_dir))] + \ glob.glob(os.path.join(full_source_dir, "*.elf")) for candidate in candidates: if os.path.isfile(candidate) and os.access(candidate, os.X_OK): # Simple check if it looks like an ELF (starts with \x7fELF) with open(candidate, 'rb') as f: if f.read(4) == b'\x7fELF': shutil.copy2(candidate, output_elf) print(f" SUCCESS: {output_elf}") return True, None print(f" ERROR: Built ELF not found in {full_source_dir}") return False, "ELF not found" except Exception as e: print(f" ERROR: Exception: {e}") return False, f"exception: {e}" def main(): parser = argparse.ArgumentParser(description="Build stud-support ELFs") parser.add_argument("--verbose", "-v", action="store_true", help="Show detailed output") parser.add_argument("--use-gcc", action="store_true", help="Force GNU toolchain") parser.add_argument("--use-clang", action="store_true", help="Force Clang toolchain") parser.add_argument("--output-dir", help="Output directory") parser.add_argument("--stud-support-path", required=True, help="Path to stud-support repo") args = parser.parse_args() output_dir = args.output_dir or os.path.join(os.path.dirname(os.path.realpath(__file__)), "elfs") if not os.path.isdir(args.stud_support_path): print(f"Error: stud-support not found at {args.stud_support_path}") return 1 toolchain = get_toolchain_config(args.use_clang) if args.use_gcc and toolchain["name"] != "GNU": # User forced GCC but we defaulted to Clang (shouldn't happen with current logic but good to be safe) toolchain = get_toolchain_config(False) available, msg = check_toolchain(toolchain) if not available: print(f"Error: {msg}\nPlease install riscv64-unknown-elf-gcc or clang/lld.") return 1 os.makedirs(output_dir, exist_ok=True) print(f"Using toolchain: {toolchain['name']} ({toolchain['cc']})") print(f"Output: {output_dir}\n") results = [build_test(t[1], t[0], toolchain, args.stud_support_path, output_dir, args.verbose, t[2] if len(t)>2 else None) for t in TESTS_TO_BUILD] success_count = sum(1 for r in results if r[0]) print(f"\nBuild Summary: {success_count}/{len(TESTS_TO_BUILD)} successful") if success_count < len(TESTS_TO_BUILD): print("Failed tests:") for i, (success, reason) in enumerate(results): if not success: print(f" - {TESTS_TO_BUILD[i][0]} ({reason})") return 1 return 0 if __name__ == "__main__": sys.exit(main()) ================================================ FILE: tests/stud-support/run_tests.py ================================================ #!/usr/bin/env python3 """ stud-support Integration Test Runner for QtRvSim """ import argparse import os import re import subprocess import sys # Define all stud-support tests TESTS = [ { "name": "selection_sort", "elf_path": "elfs/selection_sort.elf", "description": "Selection sort algorithm test", "expected_registers": {"s0": 60, "s1": 60}, "min_cycles": 500, "max_cycles": 5000, }, { "name": "vect_add", "elf_path": "elfs/vect_add.elf", "description": "Vector addition test", "expected_registers": {"x8": 0}, "expected_memory": [(0x400 + 64, 0x11), (0x400 + 68, 0x22), (0x400 + 72, 0x33)], "min_cycles": 100, "max_cycles": 2000, }, { "name": "vect_add2", "elf_path": "elfs/vect_add2.elf", "description": "Vector addition variant test", "expected_registers": {"t3": 0}, "min_cycles": 100, "max_cycles": 2000, }, { "name": "vect_inc", "elf_path": "elfs/vect_inc.elf", "description": "Vector increment test", "expected_registers": {"t3": 0}, "min_cycles": 100, "max_cycles": 2000, }, { "name": "branchpred_1", "elf_path": "elfs/branchpred_1.elf", "description": "Branch prediction test", "expected_registers": {"s2": 20, "s0": 4, "s1": 5}, "min_cycles": 50, "max_cycles": 500, }, { "name": "ffs_as_log2", "elf_path": "elfs/ffs_as_log2.elf", "description": "Find first set as log2 test", "expected_registers": {"a0": 7, "t1": 7}, "min_cycles": 20, "max_cycles": 200, }, { "name": "uart_echo_irq", "elf_path": "elfs/uart_echo_irq.elf", "description": "UART echo with interrupts test (hits BREAK early)", "min_cycles": 5, "max_cycles": 50, }, { "name": "call_clobber", "elf_path": "elfs/call_clobber.elf", "description": "Function call with register clobbering", "expected_registers": {"a0": 0xfffffffffffffffc}, "min_cycles": 15, "max_cycles": 200, }, { "name": "call_save", "elf_path": "elfs/call_save.elf", "description": "Function call with register saving", "expected_registers": {"a0": 0xfffffffffffffffc}, "min_cycles": 15, "max_cycles": 200, }, { "name": "fact_ok", "elf_path": "elfs/fact_ok.elf", "description": "Correct factorial implementation", "expected_registers": {"t0": 24, "a0": 24}, "min_cycles": 50, "max_cycles": 500, }, { "name": "call_10args", "elf_path": "elfs/call_10args.elf", "description": "Function call with 10 arguments", "expected_registers": {"a0": 55}, "min_cycles": 30, "max_cycles": 300, }, { "name": "linus_hello", "elf_path": "elfs/linus_hello.elf", "description": "Linux-style hello world with syscalls (hits ECALL early)", "min_cycles": 5, "max_cycles": 50, }, ] # RISC-V ABI name to register number mapping ABI_TO_NUM = { "zero": 0, "ra": 1, "sp": 2, "gp": 3, "tp": 4, "t0": 5, "t1": 6, "t2": 7, "s0": 8, "fp": 8, "s1": 9, "a0": 10, "a1": 11, "a2": 12, "a3": 13, "a4": 14, "a5": 15, "a6": 16, "a7": 17, "s2": 18, "s3": 19, "s4": 20, "s5": 21, "s6": 22, "s7": 23, "s8": 24, "s9": 25, "s10": 26, "s11": 27, "t3": 28, "t4": 29, "t5": 30, "t6": 31 } def get_register_value(registers, reg_name): """Get register value by name, supporting multiple naming conventions""" if reg_name in registers: return registers[reg_name] # Try as "R" + number format r_name = f"R{reg_name}" if not reg_name.startswith('R') else reg_name if r_name in registers: return registers[r_name] # Try converting ABI name to register number if reg_name in ABI_TO_NUM: r_name = f"R{ABI_TO_NUM[reg_name]}" if r_name in registers: return registers[r_name] # Try as "x" + number format if reg_name.startswith('x'): try: r_name = f"R{int(reg_name[1:])}" if r_name in registers: return registers[r_name] except ValueError: pass return None def verify_result(test, result, verbose): """Verify test result against expectations""" if result.returncode != 0: return False, f"Exit code {result.returncode} (expected 0)" + (f"\nStdout: {result.stdout}" if verbose else "") # Parse output registers = {m.group(1): int(m.group(2), 16) for m in re.finditer(r'([A-Za-z0-9_]+):0x([0-9a-fA-F]+)', result.stdout)} cycles_match = re.search(r'cycles:\s*(\d+)', result.stdout) cycles = int(cycles_match.group(1)) if cycles_match else None # Verify registers for reg, expected in test.get("expected_registers", {}).items(): actual = get_register_value(registers, reg) if actual is None: return False, f"Register {reg} not found" + (f"\nAvailable: {list(registers.keys())}" if verbose else "") if actual != expected: return False, f"Register {reg}: expected 0x{expected:x}, got 0x{actual:x}" + (f"\nAll: {registers}" if verbose else "") # Verify cycles if cycles is not None: if cycles < test.get("min_cycles", 0): return False, f"Cycles {cycles} < min {test['min_cycles']}" if test.get("max_cycles") and cycles > test["max_cycles"]: return False, f"Cycles {cycles} > max {test['max_cycles']}" # Verify marker if test.get("marker") and test["marker"] not in result.stdout: return False, f"Marker '{test['marker']}' not found" + (f"\nStdout: {result.stdout}" if verbose else "") return True, f"PASS ({cycles} cycles)" if cycles else "PASS" def run_test(qtrvsim_cli, test, elf_dir, verbose=False, pipeline=False, cache=False): """Run a single test""" elf_path = os.path.join(elf_dir, os.path.basename(test["elf_path"])) if not os.path.exists(elf_path): return False, f"ELF not found: {elf_path}" cmd = [qtrvsim_cli, elf_path, "--dump-registers", "--dump-cycles"] if pipeline: cmd.append("--pipelined") if cache: cmd.extend(["--d-cache", "lru,2,2,2", "--i-cache", "lru,2,2,2"]) try: result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, text=True, timeout=30) return verify_result(test, result, verbose) except subprocess.TimeoutExpired: return False, "Test timed out (30s)" except Exception as e: return False, f"Exception: {e}" def main(): parser = argparse.ArgumentParser(description="Run stud-support integration tests") parser.add_argument("--build-dir", required=True, help="Directory containing test ELFs") parser.add_argument("--qtrvsim-cli", required=True, help="Path to qtrvsim_cli executable") parser.add_argument("--stud-support-path", required=True, help="stud-support repo path") parser.add_argument("-v", "--verbose", action="store_true", help="Verbose output") parser.add_argument("-f", "--filter", help="Filter tests") parser.add_argument("--pipeline", action="store_true", help="Pipelined mode") parser.add_argument("--cache", action="store_true", help="Cache enabled") args = parser.parse_args() elf_dir = args.build_dir def is_exe(fpath): return os.path.isfile(fpath) and (os.name == 'nt' or os.access(fpath, os.X_OK)) qtrvsim_cli = args.qtrvsim_cli if not is_exe(qtrvsim_cli): # Try appending .exe for Windows if not present if os.name == 'nt' and not qtrvsim_cli.endswith('.exe') and is_exe(qtrvsim_cli + '.exe'): qtrvsim_cli += '.exe' else: print(f"Error: qtrvsim_cli not found or not executable at {qtrvsim_cli}") return 1 tests = [t for t in TESTS if not args.filter or args.filter in t["name"]] if not tests: print(f"No tests match filter: {args.filter}") return 1 mode = "pipelined+cached" if args.pipeline and args.cache else "pipelined" if args.pipeline else "cached" if args.cache else "single-cycle" print(f"Running {len(tests)} tests ({mode})\nUsing: {qtrvsim_cli}\nELFs: {elf_dir}\n" + "="*70) results = [run_test(qtrvsim_cli, t, elf_dir, args.verbose, args.pipeline, args.cache) for t in tests] passed = sum(1 for r in results if r[0]) for i, (success, msg) in enumerate(results): status = "[OK] " if success else "[FAIL]" print(f"{status} {tests[i]['name']:40s} {msg}") print("="*70 + f"\nResults: {passed}/{len(tests)} passed") if passed < len(tests): print("\nFailed tests:") for i, (success, _) in enumerate(results): if not success: print(f" - {tests[i]['name']}") return 1 if passed < len(tests) else 0 if __name__ == "__main__": sys.exit(main())