├── wiki ├── The Registers.md ├── _Footer.md ├── 6502 Basic Architecture.md ├── _Sidebar.md └── Home.md ├── tests ├── __init__.py ├── test_cpu_ins_clv.py ├── test_cpu_ins_sec.py ├── test_cpu_ins_clc.py ├── test_cpu_ins_sed.py ├── test_cpu_ins_sei.py ├── test_cpu_ins_nop.py ├── test_cpu_ins_cli.py ├── test_cpu_ins_pha.py ├── test_cpu_ins_cld.py ├── test_cpu_ins_php.py ├── test_cpu_ins_pla.py ├── test_cpu_ins_plp.py ├── test_cpu_ins_txs.py ├── test_memory.py ├── test_cpu_ins_inx.py ├── test_cpu_ins_iny.py ├── test_cpu_ins_dex.py ├── test_cpu_ins_dey.py ├── test_cpu_ins_tax.py ├── test_cpu_ins_tay.py ├── test_cpu_ins_txa.py ├── test_cpu_ins_tya.py ├── test_cpu_ins_tsx.py ├── test_cpu_ins_sty.py ├── test_cpu_ins_stx.py ├── test_cpu_ins_ldx.py ├── test_cpu_ins_ldy.py ├── test_cpu_ins_dec.py ├── test_cpu_ins_inc.py ├── test_cpu.py ├── test_cpu_ins_sta.py └── test_cpu_ins_lda.py ├── m6502 ├── __init__.py ├── memory.py └── processor.py ├── .yamllint.yml ├── .github ├── settings.yml ├── dependabot.yml └── workflows │ ├── dependency-review.yml │ ├── wiki.yml │ ├── ci.yml │ └── codeql.yml ├── .editorconfig ├── LICENSE ├── setup.cfg ├── .devcontainer └── devcontainer.json ├── .gitignore └── README.md /wiki/The Registers.md: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /wiki/_Footer.md: -------------------------------------------------------------------------------- 1 | Andrew John Jacobs 2 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | """M6502 Test Module.""" 2 | -------------------------------------------------------------------------------- /m6502/__init__.py: -------------------------------------------------------------------------------- 1 | """M6502 Module.""" 2 | from .memory import Memory 3 | from .processor import Processor 4 | -------------------------------------------------------------------------------- /.yamllint.yml: -------------------------------------------------------------------------------- 1 | --- 2 | extends: default 3 | 4 | rules: 5 | braces: 6 | max-spaces-inside: 1 7 | level: error 8 | brackets: 9 | max-spaces-inside: 1 10 | level: error 11 | line-length: disable 12 | truthy: disable 13 | -------------------------------------------------------------------------------- /.github/settings.yml: -------------------------------------------------------------------------------- 1 | --- 2 | repository: 3 | description: 6502 emulator in Python 4 | homepage: https://dailystuff.nl/articles/2022/writing-a-6502-emulator-in-python 5 | topics: python, emulator, 6502 6 | delete_branch_on_merge: true 7 | has_wiki: false 8 | # private: false 9 | -------------------------------------------------------------------------------- /wiki/6502 Basic Architecture.md: -------------------------------------------------------------------------------- 1 | The 6502 microprocessor is a relative simple 8-bit CPU with only a few internal registers capable of addressing at most 64Kb of memory via its 16-bit address bus. The processor is little endian and expects addresses to be stored in memory least significant byte first. -------------------------------------------------------------------------------- /wiki/_Sidebar.md: -------------------------------------------------------------------------------- 1 | - [Introduction](https://github.com/hspaans/python-6502-emulator/wiki/Home) 2 | - [6502 Basic Architecture](https://github.com/hspaans/python-6502-emulator/wiki/6502_Basic_Architecture) 3 | - [The Registers](https://github.com/hspaans/python-6502-emulator/wiki/The_Registers) 4 | - [The Instruction Set]() 5 | - [Addressing Modes]() 6 | - [Coding Algorithms]() 7 | - [Instruction Reference]() 8 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | --- 2 | version: 2 3 | updates: 4 | - package-ecosystem: github-actions 5 | directory: / 6 | schedule: 7 | interval: weekly 8 | day: saturday 9 | groups: 10 | GitHub-Actions: 11 | patterns: 12 | - "*" 13 | 14 | - package-ecosystem: pip 15 | directory: / 16 | schedule: 17 | interval: weekly 18 | day: saturday 19 | groups: 20 | Python: 21 | patterns: 22 | - "*" 23 | -------------------------------------------------------------------------------- /.github/workflows/dependency-review.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: Dependency Review 3 | 4 | on: 5 | pull_request: 6 | branches: 7 | - master 8 | 9 | permissions: 10 | contents: read 11 | 12 | jobs: 13 | dependency-review: 14 | name: Dependency Review 15 | runs-on: ubuntu-latest 16 | 17 | steps: 18 | - name: Checkout Code 19 | uses: actions/checkout@v6 20 | 21 | - name: Dependency Review 22 | uses: actions/dependency-review-action@v4 23 | with: 24 | fail-on-severity: high 25 | deny-licenses: AGPL-1.0-or-later 26 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # https://editorconfig.org/ 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | end_of_line = lf 10 | charset = utf-8 11 | 12 | # Docstrings and comments use max_line_length = 79 13 | [*.py] 14 | insert_final_newline = true 15 | max_line_length = 127 16 | 17 | # Use 2 spaces for the HTML files 18 | [*.html] 19 | indent_size = 2 20 | 21 | # Use 2 spaces for the Markdown files 22 | [*.md] 23 | indent_size = 2 24 | insert_final_newline = true 25 | 26 | # The JSON files contain newlines inconsistently 27 | [*.json] 28 | indent_size = 2 29 | 30 | # Makefiles always use tabs for indentation 31 | [Makefile] 32 | indent_style = tab 33 | insert_final_newline = true 34 | 35 | # Batch files use tabs for indentation 36 | [*.bat] 37 | indent_style = tab 38 | insert_final_newline = true 39 | 40 | [docs/**.txt] 41 | insert_final_newline = true 42 | max_line_length = 79 43 | 44 | [*.yml] 45 | indent_size = 2 46 | insert_final_newline = true 47 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 Hans Spaans 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/wiki.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: GitHub Wiki upload 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | paths: [wiki/**, .github/workflows/wiki.yml] 9 | 10 | concurrency: 11 | group: wiki 12 | cancel-in-progress: true 13 | 14 | permissions: 15 | contents: write 16 | 17 | jobs: 18 | wiki: 19 | name: Publish to GitHub Wiki 20 | runs-on: ubuntu-latest 21 | steps: 22 | - name: Checkout Code 23 | uses: actions/checkout@v6 24 | with: 25 | repository: ${{github.repository}} 26 | path: ${{github.repository}} 27 | 28 | - name: Checkout Wiki 29 | uses: actions/checkout@v6 30 | with: 31 | repository: ${{github.repository}}.wiki 32 | path: ${{github.repository}}.wiki 33 | 34 | - name: Push to wiki 35 | run: | 36 | set -e 37 | cd $GITHUB_WORKSPACE/${{github.repository}}.wiki 38 | cp -r $GITHUB_WORKSPACE/${{github.repository}}/wiki/* . 39 | git config --local user.email "action@github.com" 40 | git config --local user.name "GitHub Action" 41 | git add . 42 | git diff-index --quiet HEAD || git commit -m "action: wiki sync" && git push 43 | -------------------------------------------------------------------------------- /wiki/Home.md: -------------------------------------------------------------------------------- 1 | Welcome to the python-6502-emulator wiki! 2 | 3 | [Python](https://www.python.org/) is a great language to learn because it is easy to learn and safe to write in. It is also available for Windows, Linux and Mac, and it is free. It is also available on [Raspberry Pi](https://www.raspberrypi.org/) which is targeted for education and testing purposes. This makes computer science easy accessible to everyone. Also for writing a basic emulator to learn how computers work and what they do. The [6502](https://en.m.wikipedia.org/wiki/6502) processor has a very simple design and a small instruction set that makes it easy to learn. 4 | 5 | Learning how processors work also gives the possibility to understand why certain applications are so slow and how to optimize them, but also how to start doing security research by writing a fuzzer to find vulnerabilities. Let's start with the basics and write a simple 6502 emulator before we start with the assembly language. 6 | 7 | The [Introduction](#introduction) chapter is mainly based on the **6502 Instruction Set Guide** by [Andrew John Jacobs aka BitWise](https://github.com/andrew-jacobs) and full credit goes to him. Sadly he passed away in 2021 and I copied his work as a reference as his website is no longer available. -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [flake8] 2 | max-line-length = 127 3 | ignore = 4 | E221, 5 | E241, 6 | F401, 7 | E501 8 | 9 | [pylint.DESIGN] 10 | disable = too-few-public-methods 11 | max-attributes = 16 12 | 13 | [tox:tox] 14 | min_version = 4.0 15 | no_package = true 16 | skipsdist = true 17 | env_list = 18 | py38 19 | py39 20 | py310 21 | py311 22 | py312 23 | flake8 24 | yamllint 25 | 26 | [testenv] 27 | deps = 28 | flake8 29 | flake8-annotations 30 | flake8-annotations-complexity 31 | flake8-bugbear 32 | flake8-deprecated 33 | flake8-docstrings>=1.3.1 34 | flake8-typing-imports>=1.1 35 | ; flake8-todo 36 | ; flake8-black 37 | flake8-isort 38 | flake8-print 39 | ; flake8-pylint 40 | flake8-builtins 41 | flake8-pytest-style 42 | pep8-naming 43 | pytest>=7,<8 44 | yamllint 45 | commands = pytest tests 46 | 47 | [testenv:flake8] 48 | basepython = python3.12 49 | skip_install = true 50 | commands = 51 | flake8 --doctests tests/ m6502/ 52 | 53 | [testenv:yamllint] 54 | basepython = python3.12 55 | skip_install = true 56 | commands = 57 | yamllint . --format github 58 | 59 | [gh] 60 | python = 61 | 3.6 = py36 62 | 3.7 = py37 63 | 3.8 = py38 64 | 3.9 = py39 65 | 3.10 = py310 66 | 3.11 = py311 67 | 3.12 = py312 68 | -------------------------------------------------------------------------------- /m6502/memory.py: -------------------------------------------------------------------------------- 1 | """Emulation of the MOT-6502 memory.""" 2 | 3 | 4 | class Memory: 5 | """Memory bank for MOT-6502 systems.""" 6 | 7 | def __init__(self: object, size: int = None) -> None: 8 | """ 9 | Initialize the memory. 10 | 11 | :param size: The size of the memory 12 | :return: None 13 | """ 14 | if size is None: 15 | size = 0xFFFF 16 | if size < 0x0200: 17 | raise ValueError("Memory size is not valid") 18 | if size > 0xFFFF: 19 | raise ValueError("Memory size is not valid") 20 | self.size = size 21 | self.memory = [0] * self.size 22 | 23 | def __getitem__(self: object, address: int) -> int: 24 | """ 25 | Get the value at the specified address. 26 | 27 | :param address: The address to read from 28 | :return: The value at the specified address 29 | """ 30 | if 0x0000 < address > 0xFFFF: 31 | raise ValueError("Memory address is not valid") 32 | return self.memory[address] 33 | 34 | def __setitem__(self: object, address: int, value: int) -> int: 35 | """ 36 | Set the value at the specified address. 37 | 38 | :param address: The address to write to 39 | :param value: The value to write to the address 40 | :return: None 41 | """ 42 | if 0x0000 < address > 0xFFFF: 43 | raise ValueError("Memory address is not valid") 44 | if value.bit_length() > 8: 45 | raise ValueError("Value too large") 46 | self.memory[address] = value 47 | return self.memory[address] 48 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_clv.py: -------------------------------------------------------------------------------- 1 | """ 2 | CLV - Clear Overflow Flag. 3 | 4 | V = 0 5 | 6 | Clears the overflow flag. 7 | 8 | +------+-------------------+--------------+ 9 | | Flag | Description | State | 10 | +======+===================+==============+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------+ 21 | | V | Overflow Flag | Set to 0 | 22 | +------+-------------------+--------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0xB8 | 1 | 2 | 30 | +-----------------+--------+-------+--------+ 31 | """ 32 | import m6502 33 | 34 | 35 | def test_cpu_ins_cld_imp() -> None: 36 | """ 37 | Clear Overflow Flag. 38 | 39 | return: None 40 | """ 41 | memory = m6502.Memory() 42 | cpu = m6502.Processor(memory) 43 | cpu.reset() 44 | cpu.flag_v = True 45 | memory[0xFCE2] = 0xB8 46 | cpu.execute(2) 47 | assert ( 48 | cpu.program_counter, 49 | cpu.stack_pointer, 50 | cpu.cycles, 51 | cpu.flag_v, 52 | ) == (0xFCE3, 0x01FD, 2, False) 53 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_sec.py: -------------------------------------------------------------------------------- 1 | """ 2 | SEC - Set Carry Flag. 3 | 4 | C = 1 5 | 6 | Set the carry flag to one. 7 | 8 | +------+-------------------+--------------+ 9 | | Flag | Description | State | 10 | +======+===================+==============+ 11 | | C | Carry Flag | Set to 1 | 12 | +------+-------------------+--------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0x38 | 1 | 2 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | See also: CLC 33 | """ 34 | import m6502 35 | 36 | 37 | def test_cpu_ins_sec_imp() -> None: 38 | """ 39 | Set Carry Flag. 40 | 41 | return: None 42 | """ 43 | memory = m6502.Memory() 44 | cpu = m6502.Processor(memory) 45 | cpu.reset() 46 | cpu.flag_c = False 47 | memory[0xFCE2] = 0x38 48 | cpu.execute(2) 49 | assert ( 50 | cpu.program_counter, 51 | cpu.stack_pointer, 52 | cpu.cycles, 53 | cpu.flag_c, 54 | ) == (0xFCE3, 0x01FD, 2, True) 55 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_clc.py: -------------------------------------------------------------------------------- 1 | """ 2 | CLC - Clear Carry Flag. 3 | 4 | C = 0 5 | 6 | Set the carry flag to zero. 7 | 8 | +------+-------------------+--------------+ 9 | | Flag | Description | State | 10 | +======+===================+==============+ 11 | | C | Carry Flag | Set to 0 | 12 | +------+-------------------+--------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0x18 | 1 | 2 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | See also: SEC 33 | """ 34 | import m6502 35 | 36 | 37 | def test_cpu_ins_clc_imp() -> None: 38 | """ 39 | CLC - Clear Carry Flag. 40 | 41 | return: None 42 | """ 43 | memory = m6502.Memory() 44 | cpu = m6502.Processor(memory) 45 | cpu.reset() 46 | cpu.flag_c = True 47 | memory[0xFCE2] = 0x18 48 | cpu.execute(2) 49 | assert ( 50 | cpu.program_counter, 51 | cpu.stack_pointer, 52 | cpu.cycles, 53 | cpu.flag_c, 54 | ) == (0xFCE3, 0x01FD, 2, False) 55 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_sed.py: -------------------------------------------------------------------------------- 1 | """ 2 | SED - Set Decimal Flag. 3 | 4 | D = 1 5 | 6 | Sets the decimal mode flag to one. 7 | 8 | +------+-------------------+--------------+ 9 | | Flag | Description | State | 10 | +======+===================+==============+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------+ 17 | | D | Decimal Mode Flag | Set to 1 | 18 | +------+-------------------+--------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0xF8 | 1 | 2 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | See also: CLD 33 | """ 34 | import m6502 35 | 36 | 37 | def test_cpu_ins_sed_imp() -> None: 38 | """ 39 | Set Decimal Mode. 40 | 41 | return: None 42 | """ 43 | memory = m6502.Memory() 44 | cpu = m6502.Processor(memory) 45 | cpu.reset() 46 | cpu.flag_d = False 47 | memory[0xFCE2] = 0xF8 48 | cpu.execute(2) 49 | assert ( 50 | cpu.program_counter, 51 | cpu.stack_pointer, 52 | cpu.cycles, 53 | cpu.flag_d, 54 | ) == (0xFCE3, 0x01FD, 2, True) 55 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_sei.py: -------------------------------------------------------------------------------- 1 | """ 2 | SEI - Set Interrupt Disable. 3 | 4 | I = 1 5 | 6 | Sets the interrupt disable flag to zero. 7 | 8 | +------+-------------------+--------------+ 9 | | Flag | Description | State | 10 | +======+===================+==============+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------+ 15 | | I | Interrupt Disable | Set to 1 | 16 | +------+-------------------+--------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0x78 | 1 | 2 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | See also: CLI 33 | """ 34 | import m6502 35 | 36 | 37 | def test_cpu_ins_sei_imp() -> None: 38 | """ 39 | Set Interrupt Disable. 40 | 41 | return: None 42 | """ 43 | memory = m6502.Memory() 44 | cpu = m6502.Processor(memory) 45 | cpu.reset() 46 | cpu.flag_i = False 47 | memory[0xFCE2] = 0x78 48 | cpu.execute(2) 49 | assert ( 50 | cpu.program_counter, 51 | cpu.stack_pointer, 52 | cpu.cycles, 53 | cpu.flag_i, 54 | ) == (0xFCE3, 0x01FD, 2, True) 55 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_nop.py: -------------------------------------------------------------------------------- 1 | """ 2 | NOP - No Operation. 3 | 4 | The NOP instruction causes no changes to the processor other than the normal 5 | incrementing of the program counter to the next instruction. 6 | 7 | Processor Status after use: 8 | 9 | +------+-------------------+--------------+ 10 | | Flag | Description | State | 11 | +======+===================+==============+ 12 | | C | Carry Flag | Not affected | 13 | +------+-------------------+--------------+ 14 | | Z | Zero Flag | Not affected | 15 | +------+-------------------+--------------+ 16 | | I | Interrupt Disable | Not affected | 17 | +------+-------------------+--------------+ 18 | | D | Decimal Mode Flag | Not affected | 19 | +------+-------------------+--------------+ 20 | | B | Break Command | Not affected | 21 | +------+-------------------+--------------+ 22 | | V | Overflow Flag | Not affected | 23 | +------+-------------------+--------------+ 24 | | N | Negative Flag | Not affected | 25 | +------+-------------------+--------------+ 26 | 27 | +-----------------+--------+-------+--------+ 28 | | Addressing Mode | Opcode | Bytes | Cycles | 29 | +=================+========+=======+========+ 30 | | Implied | 0xEA | 1 | 2 | 31 | +-----------------+--------+-------+--------+ 32 | """ 33 | import m6502 34 | 35 | 36 | def test_cpu_ins_nop() -> None: 37 | """ 38 | Do nothing for 1 computer cycle. 39 | 40 | return: None 41 | """ 42 | memory = m6502.Memory() 43 | cpu = m6502.Processor(memory) 44 | cpu.reset() 45 | memory[0xFCE2] = 0xEA 46 | cpu.execute(2) 47 | assert ( 48 | cpu.program_counter, 49 | cpu.stack_pointer, 50 | cpu.cycles, 51 | ) == (0xFCE3, 0x01FD, 2) 52 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_cli.py: -------------------------------------------------------------------------------- 1 | """ 2 | CLI - Clear Interrupt Disable. 3 | 4 | I = 0 5 | 6 | Clears the interrupt disable flag allowing normal interrupt requests to be 7 | serviced. 8 | 9 | +------+-------------------+--------------+ 10 | | Flag | Description | State | 11 | +======+===================+==============+ 12 | | C | Carry Flag | Not affected | 13 | +------+-------------------+--------------+ 14 | | Z | Zero Flag | Not affected | 15 | +------+-------------------+--------------+ 16 | | I | Interrupt Disable | Set to 0 | 17 | +------+-------------------+--------------+ 18 | | D | Decimal Mode Flag | Not affected | 19 | +------+-------------------+--------------+ 20 | | B | Break Command | Not affected | 21 | +------+-------------------+--------------+ 22 | | V | Overflow Flag | Not affected | 23 | +------+-------------------+--------------+ 24 | | N | Negative Flag | Not affected | 25 | +------+-------------------+--------------+ 26 | 27 | +-----------------+--------+-------+--------+ 28 | | Addressing Mode | Opcode | Bytes | Cycles | 29 | +=================+========+=======+========+ 30 | | Implied | 0x58 | 1 | 2 | 31 | +-----------------+--------+-------+--------+ 32 | 33 | See also: SEI 34 | """ 35 | import m6502 36 | 37 | 38 | def test_cpu_ins_cld_imp() -> None: 39 | """ 40 | Clear Interrupt Disable. 41 | 42 | return: None 43 | """ 44 | memory = m6502.Memory() 45 | cpu = m6502.Processor(memory) 46 | cpu.reset() 47 | cpu.flag_i = True 48 | memory[0xFCE2] = 0x58 49 | cpu.execute(2) 50 | assert ( 51 | cpu.program_counter, 52 | cpu.stack_pointer, 53 | cpu.cycles, 54 | cpu.flag_i, 55 | ) == (0xFCE3, 0x01FD, 2, False) 56 | -------------------------------------------------------------------------------- /.devcontainer/devcontainer.json: -------------------------------------------------------------------------------- 1 | { 2 | "image": "mcr.microsoft.com/devcontainers/base:debian", 3 | "features": { 4 | "ghcr.io/devcontainers/features/python:1": { 5 | "installTools": true, 6 | "version": "3.12" 7 | }, 8 | "ghcr.io/devcontainers/features/github-cli:1": { 9 | "installDirectlyFromGitHubRelease": true, 10 | "version": "latest" 11 | }, 12 | "ghcr.io/devcontainers-contrib/features/flake8:2": { 13 | "version": "latest", 14 | "plugins": "flake8-annotations flake8-annotations-complexity flake8-bugbear flake8-deprecated flake8-docstrings flake8-isort flake8-print flake8-pylint flake8-builtins flake8-pytest-style flake8-todo" 15 | }, 16 | "ghcr.io/devcontainers-contrib/features/yamllint:2": { 17 | "version": "latest" 18 | }, 19 | "ghcr.io/devcontainers-contrib/features/tox:2": { 20 | "version": "latest" 21 | }, 22 | "ghcr.io/hspaans/devcontainer-features/pytest:1": { 23 | "version": "latest" 24 | } 25 | }, 26 | "customizations": { 27 | "vscode": { 28 | "extensions": [ 29 | "EditorConfig.EditorConfig", 30 | "github.vscode-github-actions", 31 | "ms-python.autopep8", 32 | "ms-python.flake8", 33 | "ms-python.vscode-pylance", 34 | "ms-python.python" 35 | ], 36 | "[python]": { 37 | "editor.defaultFormatter": "ms-python.autopep8", 38 | "editor.formatOnSave": true 39 | }, 40 | "settings": { 41 | "python.formatting.provider": "flake8", 42 | "python.testing.pytestArgs": [ 43 | "tests" 44 | ], 45 | "python.testing.unittestEnabled": false, 46 | "python.testing.pytestEnabled": true, 47 | "python.analysis.inlayHints.pytestParameters": true 48 | } 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /tests/test_cpu_ins_pha.py: -------------------------------------------------------------------------------- 1 | """ 2 | PHA - Push Accumulator. 3 | 4 | Pushes a copy of the accumulator on to the stack 5 | 6 | Processor Status after use: 7 | 8 | +------+-------------------+--------------+ 9 | | Flag | Description | State | 10 | +======+===================+==============+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0x48 | 1 | 3 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | See also: PLA 33 | """ 34 | import m6502 35 | 36 | 37 | def test_cpu_ins_pha_imp() -> None: 38 | """ 39 | Push Accumulator, Implied. 40 | 41 | TODO: Add check to not cross page 42 | 43 | return: None 44 | """ 45 | memory = m6502.Memory() 46 | cpu = m6502.Processor(memory) 47 | cpu.reset() 48 | cpu.reg_a = 0xF0 49 | memory[0xFCE2] = 0x48 50 | memory[cpu.stack_pointer] = 0x00 51 | cpu.execute(3) 52 | assert ( 53 | cpu.program_counter, 54 | cpu.stack_pointer, 55 | cpu.cycles, 56 | memory[cpu.stack_pointer + 1], 57 | ) == (0xFCE3, 0x01FC, 3, 0xF0) 58 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_cld.py: -------------------------------------------------------------------------------- 1 | """ 2 | CLD - Clear Decimal Mode. 3 | 4 | D = 0 5 | 6 | Sets the decimal mode flag to zero. 7 | 8 | +------+-------------------+--------------+ 9 | | Flag | Description | State | 10 | +======+===================+==============+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------+ 17 | | D | Decimal Mode Flag | Set to 0 | 18 | +------+-------------------+--------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0xD8 | 1 | 2 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | NB: 33 | The state of the decimal flag is uncertain when the CPU is powered up and it 34 | is not reset when an interrupt is generated. In both cases you should include 35 | an explicit CLD to ensure that the flag is cleared before performing addition 36 | or subtraction. 37 | 38 | See also: SED 39 | """ 40 | import m6502 41 | 42 | 43 | def test_cpu_ins_cld_imp() -> None: 44 | """ 45 | Clear Decimal Mode. 46 | 47 | return: None 48 | """ 49 | memory = m6502.Memory() 50 | cpu = m6502.Processor(memory) 51 | cpu.reset() 52 | cpu.flag_d = True 53 | memory[0xFCE2] = 0xD8 54 | cpu.execute(2) 55 | assert ( 56 | cpu.program_counter, 57 | cpu.stack_pointer, 58 | cpu.cycles, 59 | cpu.flag_d, 60 | ) == (0xFCE3, 0x01FD, 2, False) 61 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_php.py: -------------------------------------------------------------------------------- 1 | """ 2 | PHP - Push Processor Status. 3 | 4 | Pushes a copy of the status flags on to the stack. 5 | 6 | Processor Status after use: 7 | 8 | +------+-------------------+--------------+ 9 | | Flag | Description | State | 10 | +======+===================+==============+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0x08 | 1 | 3 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | See also: PLP 33 | """ 34 | import pytest 35 | 36 | import m6502 37 | 38 | 39 | @pytest.mark.parametrize( 40 | ("value", "flag_n", "flag_z"), [ 41 | (0xAC, False, False), 42 | (0xEC, False, True), 43 | (0xAE, True, False), 44 | ]) 45 | def test_cpu_ins_php_imp(value: int, flag_n: bool, flag_z: bool) -> None: 46 | """ 47 | Push Processor Status, Implied. 48 | 49 | return: None 50 | """ 51 | memory = m6502.Memory() 52 | cpu = m6502.Processor(memory) 53 | cpu.reset() 54 | memory[0xFCE2] = 0x08 55 | cpu.flag_z = flag_z 56 | cpu.flag_n = flag_n 57 | cpu.execute(3) 58 | assert ( 59 | cpu.program_counter, 60 | cpu.stack_pointer, 61 | cpu.cycles, 62 | cpu.memory[cpu.stack_pointer + 1], 63 | ) == (0xFCE3, 0x01FC, 3, value) 64 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | --- 2 | name: CI 3 | 4 | on: 5 | push: 6 | branches: 7 | - master 8 | pull_request: 9 | branches: 10 | - master 11 | 12 | permissions: 13 | contents: read 14 | 15 | jobs: 16 | tox: 17 | name: Test on Python ${{ matrix.py }} - ${{ matrix.os }} 18 | runs-on: ${{ matrix.os }}-latest 19 | strategy: 20 | fail-fast: false 21 | matrix: 22 | os: 23 | - Ubuntu 24 | py: 25 | - "3.12" 26 | - "3.11" 27 | - "3.10" 28 | - "3.9" 29 | - "3.8" 30 | steps: 31 | - name: Setup python for test ${{ matrix.py }} 32 | uses: actions/setup-python@v6 33 | with: 34 | python-version: ${{ matrix.py }} 35 | 36 | - name: Checkout Code 37 | uses: actions/checkout@v6 38 | 39 | - name: Install tox-gh 40 | run: python -m pip install tox-gh 41 | 42 | - name: Setup test suite 43 | run: tox r -vv --notest 44 | 45 | - name: Run test suite 46 | run: tox r --skip-pkg-install 47 | env: 48 | PYTEST_ADDOPTS: "-vv --durations=10" 49 | 50 | lint: 51 | name: Linting Code Base 52 | runs-on: ubuntu-latest 53 | 54 | steps: 55 | - name: Checkout Code 56 | uses: actions/checkout@v6 57 | 58 | - name: Install dependencies 59 | run: | 60 | python -m pip install --upgrade pip 61 | python -m pip install flake8 flake8-bugbear flake8-docstrings flake8-github-annotations yamllint 62 | 63 | - name: Lint with yamllint 64 | run: | 65 | yamllint . --format github 66 | 67 | - name: Lint with flake8 68 | run: | 69 | # stop the build if there are Python syntax errors or undefined names 70 | flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics --format github 71 | # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide 72 | flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics --format github 73 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_pla.py: -------------------------------------------------------------------------------- 1 | """ 2 | PLA - Pull Accumulator. 3 | 4 | Pulls an 8 bit value from the stack and into the accumulator. The zero andi 5 | negative flags are set as appropriate. 6 | 7 | Processor Status after use: 8 | 9 | +------+-------------------+--------------------------+ 10 | | Flag | Description | State | 11 | +======+===================+==========================+ 12 | | C | Carry Flag | Not affected | 13 | +------+-------------------+--------------------------+ 14 | | Z | Zero Flag | Set is A = 0 | 15 | +------+-------------------+--------------------------+ 16 | | I | Interrupt Disable | Not affected | 17 | +------+-------------------+--------------------------+ 18 | | D | Decimal Mode Flag | Not affected | 19 | +------+-------------------+--------------------------+ 20 | | B | Break Command | Not affected | 21 | +------+-------------------+--------------------------+ 22 | | V | Overflow Flag | Not affected | 23 | +------+-------------------+--------------------------+ 24 | | N | Negative Flag | Set if bit 7 of A is set | 25 | +------+-------------------+--------------------------+ 26 | 27 | +-----------------+--------+-------+--------+ 28 | | Addressing Mode | Opcode | Bytes | Cycles | 29 | +=================+========+=======+========+ 30 | | Implied | 0x68 | 1 | 4 | 31 | +-----------------+--------+-------+--------+ 32 | 33 | See also: PHA 34 | """ 35 | import m6502 36 | 37 | 38 | def test_cpu_ins_pla_imp() -> None: 39 | """ 40 | Pull Accumulator, Implied. 41 | 42 | TODO: Add check to not cross page 43 | 44 | return: None 45 | """ 46 | memory = m6502.Memory() 47 | cpu = m6502.Processor(memory) 48 | cpu.reset() 49 | cpu.reg_a = 0x00 50 | cpu.stack_pointer = 0x01FB 51 | memory[0xFCE2] = 0x68 52 | memory[0x01FB] = 0xF0 53 | cpu.execute(2) 54 | assert ( 55 | cpu.program_counter, 56 | cpu.stack_pointer, 57 | cpu.cycles, 58 | cpu.reg_a, 59 | ) == (0xFCE3, 0x01FC, 2, 0xF0) 60 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_plp.py: -------------------------------------------------------------------------------- 1 | """ 2 | PLP - Pull Processor Status. 3 | 4 | Pulls an 8 bit value frm the stack and into the processor flags. The flags 5 | will take on new states as determined by value pulled. 6 | 7 | Processor Status after use: 8 | 9 | +------+-------------------+----------------+ 10 | | Flag | Description | State | 11 | +======+===================+================+ 12 | | C | Carry Flag | Set from stack | 13 | +------+-------------------+----------------+ 14 | | Z | Zero Flag | Set from stack | 15 | +------+-------------------+----------------+ 16 | | I | Interrupt Disable | Set from stack | 17 | +------+-------------------+----------------+ 18 | | D | Decimal Mode Flag | Set from stack | 19 | +------+-------------------+----------------+ 20 | | B | Break Command | Set from stack | 21 | +------+-------------------+----------------+ 22 | | V | Overflow Flag | Set from stack | 23 | +------+-------------------+----------------+ 24 | | N | Negative Flag | Set from stack | 25 | +------+-------------------+----------------+ 26 | 27 | +-----------------+--------+-------+--------+ 28 | | Addressing Mode | Opcode | Bytes | Cycles | 29 | +=================+========+=======+========+ 30 | | Implied | 0x28 | 1 | 4 | 31 | +-----------------+--------+-------+--------+ 32 | 33 | See also: PHP 34 | """ 35 | import pytest 36 | 37 | import m6502 38 | 39 | 40 | @pytest.mark.parametrize( 41 | ("value", "flag_n", "flag_z"), [ 42 | (0xAC, False, False), 43 | (0xEC, False, True), 44 | (0xAE, True, False), 45 | ]) 46 | def test_cpu_ins_plp_imp(value: int, flag_n: bool, flag_z: bool) -> None: 47 | """ 48 | Pull Processor Status. 49 | 50 | :return: None 51 | """ 52 | memory = m6502.Memory() 53 | cpu = m6502.Processor(memory) 54 | cpu.reset() 55 | cpu.memory[0xFCE2] = 0x28 56 | cpu.memory[cpu.stack_pointer] = value 57 | cpu.execute(4) 58 | assert ( 59 | cpu.program_counter, 60 | cpu.stack_pointer, 61 | cpu.cycles, 62 | cpu.flag_n, 63 | cpu.flag_z, 64 | ) == (0xFCE3, 0x01FE, 4, flag_n, flag_z) 65 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_txs.py: -------------------------------------------------------------------------------- 1 | """ 2 | TXS - Transfer Register X to Stack Pointer. 3 | 4 | S = X 5 | 6 | Copies the current contents of the X register into the stack register. 7 | 8 | Processor Status after use: 9 | 10 | +------+-------------------+--------------------------+ 11 | | Flag | Description | State | 12 | +======+===================+==========================+ 13 | | C | Carry Flag | Not affected | 14 | +------+-------------------+--------------------------+ 15 | | Z | Zero Flag | Not affected | 16 | +------+-------------------+--------------------------+ 17 | | I | Interrupt Disable | Not affected | 18 | +------+-------------------+--------------------------+ 19 | | D | Decimal Mode Flag | Not affected | 20 | +------+-------------------+--------------------------+ 21 | | B | Break Command | Not affected | 22 | +------+-------------------+--------------------------+ 23 | | V | Overflow Flag | Not affected | 24 | +------+-------------------+--------------------------+ 25 | | N | Negative Flag | Not affected | 26 | +------+-------------------+--------------------------+ 27 | 28 | +-----------------+--------+-------+--------+ 29 | | Addressing Mode | Opcode | Bytes | Cycles | 30 | +=================+========+=======+========+ 31 | | Implied | 0x9A | 1 | 2 | 32 | +-----------------+--------+-------+--------+ 33 | 34 | See also: TSX 35 | """ 36 | import pytest 37 | 38 | import m6502 39 | 40 | 41 | @pytest.mark.parametrize( 42 | "value", [ 43 | (0x0F), 44 | (0x00), 45 | (0xF0), 46 | ]) 47 | def test_cpu_ins_txs_imm(value: int) -> None: 48 | """ 49 | Transfer Register X to Stack Pointer, Implied. 50 | 51 | return: None 52 | """ 53 | memory = m6502.Memory() 54 | cpu = m6502.Processor(memory) 55 | cpu.reset() 56 | cpu.reg_x = value 57 | memory[0xFCE2] = 0x9A 58 | cpu.execute(2) 59 | assert ( 60 | cpu.program_counter, 61 | cpu.stack_pointer, 62 | cpu.cycles, 63 | cpu.memory[cpu.stack_pointer + 1], 64 | ) == (0xFCE3, 0x01FC, 2, value) 65 | -------------------------------------------------------------------------------- /tests/test_memory.py: -------------------------------------------------------------------------------- 1 | """Verifies that the memory class works as expected.""" 2 | import pytest 3 | 4 | import m6502 5 | 6 | 7 | @pytest.mark.parametrize( 8 | "size", [ 9 | 0x0200, 10 | 0xFFFF 11 | ]) 12 | def test_init_memory_size(size: int) -> None: 13 | """ 14 | Verify with invalid memory sizes. 15 | 16 | TODO: Verify correct memory sizes 17 | 18 | :param size: The size of the memory 19 | :return: None 20 | """ 21 | memory = m6502.Memory(size) # noqa: E302 F841 PLW0612 22 | assert len(memory.memory) == size 23 | 24 | 25 | @pytest.mark.parametrize( 26 | "size", [ 27 | 0x01FF, 28 | 0x010000 29 | ]) 30 | def test_init_memory_valueerror(size: int) -> None: 31 | """ 32 | Verify with invalid memory sizes. 33 | 34 | TODO: Verify correct memory sizes 35 | 36 | :param size: The size of the memory 37 | :return: None 38 | """ 39 | with pytest.raises(ValueError, match="Memory size is not valid"): 40 | memory = m6502.Memory(size) # noqa: E302 F841 PLW0612 41 | 42 | 43 | @pytest.mark.parametrize( 44 | "i", 45 | range(0x0000, 0x0100) 46 | ) 47 | def test_write_zero_page(i: int) -> None: 48 | """ 49 | Verify that the Zero Page memory can be written to and read from. 50 | 51 | :param i: The address to write to 52 | :return: None 53 | """ 54 | memory = m6502.Memory() 55 | memory[i] = 0xA5 56 | assert memory[i] == 0xA5 57 | 58 | 59 | @pytest.mark.parametrize( 60 | "i", 61 | range(0x0100, 0x0200) 62 | ) 63 | def test_write_stack(i: int) -> None: 64 | """ 65 | Verify that the Stack memory can be written to and read from. 66 | 67 | :param i: The address to write to 68 | :return: None 69 | """ 70 | memory = m6502.Memory() 71 | memory[i] = 0xA5 72 | assert memory[i] == 0xA5 73 | 74 | 75 | @pytest.mark.parametrize( 76 | "i", [ 77 | 0xFFFC, 78 | 0xFFFD 79 | ]) 80 | def test_write_vector(i: int) -> None: 81 | """ 82 | Verify that the C64 vector memory can be written to and read from. 83 | 84 | :param i: The address to write to 85 | :return: None 86 | """ 87 | memory = m6502.Memory() 88 | memory[i] = 0xA5 89 | assert memory[i] == 0xA5 90 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_inx.py: -------------------------------------------------------------------------------- 1 | """ 2 | INX - Increment X Register. 3 | 4 | X,Z,N = X+1 5 | 6 | Adds one to the X register setting the zero and negative flags as appropriate. 7 | 8 | +------+-------------------+--------------------------+ 9 | | Flag | Description | State | 10 | +======+===================+==========================+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------------------+ 13 | | Z | Zero Flag | Set if X is zero | 14 | +------+-------------------+--------------------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------------------+ 23 | | N | Negative Flag | Set if bit 7 of X is set | 24 | +------+-------------------+--------------------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0xE8 | 1 | 2 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | See also: INC, INY 33 | 34 | """ 35 | import pytest 36 | 37 | import m6502 38 | 39 | 40 | @pytest.mark.parametrize( 41 | ("value", "expected", "flag_z", "flag_n"), [ 42 | (-2, -1, False, True), 43 | (-1, 0, True, False), 44 | (0, 1, False, False), 45 | (1, 2, False, False) 46 | ]) 47 | def test_cpu_ins_inx_imp(value: int, expected: int, flag_z: bool, flag_n: bool) -> None: 48 | """ 49 | Increment X Register. 50 | 51 | return: None 52 | """ 53 | memory = m6502.Memory() 54 | cpu = m6502.Processor(memory) 55 | cpu.reset() 56 | cpu.reg_x = value 57 | memory[0xFCE2] = 0xE8 58 | cpu.execute(2) 59 | assert ( 60 | cpu.program_counter, 61 | cpu.stack_pointer, 62 | cpu.cycles, 63 | cpu.flag_z, 64 | cpu.flag_n, 65 | cpu.reg_x, 66 | ) == (0xFCE3, 0x01FD, 2, flag_z, flag_n, expected) 67 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_iny.py: -------------------------------------------------------------------------------- 1 | """ 2 | INY - Increment Y Register. 3 | 4 | Y,Z,N = Y+1 5 | 6 | Adds one to the Y register setting the zero and negative flags as appropriate. 7 | 8 | +------+-------------------+--------------------------+ 9 | | Flag | Description | State | 10 | +======+===================+==========================+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------------------+ 13 | | Z | Zero Flag | Set if Y is zero | 14 | +------+-------------------+--------------------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------------------+ 23 | | N | Negative Flag | Set if bit 7 of Y is set | 24 | +------+-------------------+--------------------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0xC8 | 1 | 2 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | See also: INC, INX 33 | 34 | """ 35 | import pytest 36 | 37 | import m6502 38 | 39 | 40 | @pytest.mark.parametrize( 41 | ("value", "expected", "flag_z", "flag_n"), [ 42 | (-2, -1, False, True), 43 | (-1, 0, True, False), 44 | (0, 1, False, False), 45 | (1, 2, False, False) 46 | ]) 47 | def test_cpu_ins_iny_imp(value: int, expected: int, flag_z: bool, flag_n: bool) -> None: 48 | """ 49 | Increment Y Register. 50 | 51 | return: None 52 | """ 53 | memory = m6502.Memory() 54 | cpu = m6502.Processor(memory) 55 | cpu.reset() 56 | cpu.reg_y = value 57 | memory[0xFCE2] = 0xC8 58 | cpu.execute(2) 59 | assert ( 60 | cpu.program_counter, 61 | cpu.stack_pointer, 62 | cpu.cycles, 63 | cpu.flag_z, 64 | cpu.flag_n, 65 | cpu.reg_y, 66 | ) == (0xFCE3, 0x01FD, 2, flag_z, flag_n, expected) 67 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_dex.py: -------------------------------------------------------------------------------- 1 | """ 2 | DEX - Decrement X Register. 3 | 4 | X,Z,N = X-1 5 | 6 | Subtracts one from the X register setting the zero and negative flags as appropriate. 7 | 8 | +------+-------------------+--------------------------+ 9 | | Flag | Description | State | 10 | +======+===================+==========================+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------------------+ 13 | | Z | Zero Flag | Set if X is zero | 14 | +------+-------------------+--------------------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------------------+ 23 | | N | Negative Flag | Set if bit 7 of X is set | 24 | +------+-------------------+--------------------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0xCA | 1 | 2 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | See also: DEC, DEY 33 | 34 | """ 35 | import pytest 36 | 37 | import m6502 38 | 39 | 40 | @pytest.mark.parametrize( 41 | ("value", "expected", "flag_z", "flag_n"), [ 42 | (-1, -2, False, True), 43 | (0, -1, False, True), 44 | (1, 0, True, False), 45 | (2, 1, False, False) 46 | ]) 47 | def test_cpu_ins_dex_imp(value: int, expected: int, flag_z: bool, flag_n: bool) -> None: 48 | """ 49 | Decrement X Register. 50 | 51 | return: None 52 | """ 53 | memory = m6502.Memory() 54 | cpu = m6502.Processor(memory) 55 | cpu.reset() 56 | cpu.reg_x = value 57 | memory[0xFCE2] = 0xCA 58 | cpu.execute(2) 59 | assert ( 60 | cpu.program_counter, 61 | cpu.stack_pointer, 62 | cpu.cycles, 63 | cpu.flag_z, 64 | cpu.flag_n, 65 | cpu.reg_x, 66 | ) == (0xFCE3, 0x01FD, 2, flag_z, flag_n, expected) 67 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_dey.py: -------------------------------------------------------------------------------- 1 | """ 2 | DEY - Decrement Y Register. 3 | 4 | Y,Z,N = Y-1 5 | 6 | Subtracts one from the Y register setting the zero and negative flags as appropriate. 7 | 8 | +------+-------------------+--------------------------+ 9 | | Flag | Description | State | 10 | +======+===================+==========================+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------------------+ 13 | | Z | Zero Flag | Set if Y is zero | 14 | +------+-------------------+--------------------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------------------+ 23 | | N | Negative Flag | Set if bit 7 of Y is set | 24 | +------+-------------------+--------------------------+ 25 | 26 | +-----------------+--------+-------+--------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+========+ 29 | | Implied | 0xC8 | 1 | 2 | 30 | +-----------------+--------+-------+--------+ 31 | 32 | See also: DEC, DEX 33 | 34 | """ 35 | import pytest 36 | 37 | import m6502 38 | 39 | 40 | @pytest.mark.parametrize( 41 | ("value", "expected", "flag_z", "flag_n"), [ 42 | (-1, -2, False, True), 43 | (0, -1, False, True), 44 | (1, 0, True, False), 45 | (2, 1, False, False) 46 | ]) 47 | def test_cpu_ins_dey_imp(value: int, expected: int, flag_z: bool, flag_n: bool) -> None: 48 | """ 49 | Decrement Y Register. 50 | 51 | return: None 52 | """ 53 | memory = m6502.Memory() 54 | cpu = m6502.Processor(memory) 55 | cpu.reset() 56 | cpu.reg_y = value 57 | memory[0xFCE2] = 0x88 58 | cpu.execute(2) 59 | assert ( 60 | cpu.program_counter, 61 | cpu.stack_pointer, 62 | cpu.cycles, 63 | cpu.flag_z, 64 | cpu.flag_n, 65 | cpu.reg_y, 66 | ) == (0xFCE3, 0x01FD, 2, flag_z, flag_n, expected) 67 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_tax.py: -------------------------------------------------------------------------------- 1 | """ 2 | TAX - Transfer Accumulator to X. 3 | 4 | X = A 5 | 6 | Copies the current contents of the accumulator into the X register and sets 7 | the zero and negative flags as appropriate. 8 | 9 | Processor Status after use: 10 | 11 | +------+-------------------+--------------------------+ 12 | | Flag | Description | State | 13 | +======+===================+==========================+ 14 | | C | Carry Flag | Not affected | 15 | +------+-------------------+--------------------------+ 16 | | Z | Zero Flag | Set is X = 0 | 17 | +------+-------------------+--------------------------+ 18 | | I | Interrupt Disable | Not affected | 19 | +------+-------------------+--------------------------+ 20 | | D | Decimal Mode Flag | Not affected | 21 | +------+-------------------+--------------------------+ 22 | | B | Break Command | Not affected | 23 | +------+-------------------+--------------------------+ 24 | | V | Overflow Flag | Not affected | 25 | +------+-------------------+--------------------------+ 26 | | N | Negative Flag | Set if bit 7 of X is set | 27 | +------+-------------------+--------------------------+ 28 | 29 | +-----------------+--------+-------+--------+ 30 | | Addressing Mode | Opcode | Bytes | Cycles | 31 | +=================+========+=======+========+ 32 | | Implied | 0xAA | 1 | 2 | 33 | +-----------------+--------+-------+--------+ 34 | 35 | See also: TXA 36 | """ 37 | import pytest 38 | 39 | import m6502 40 | 41 | 42 | @pytest.mark.parametrize( 43 | ("value", "flag_n", "flag_z"), [ 44 | (0x0F, False, False), 45 | (0x00, False, True), 46 | (0xF0, True, False), 47 | ]) 48 | def test_cpu_ins_tax_imm(value: int, flag_n: bool, flag_z: bool) -> None: 49 | """ 50 | Transfer Accumulator, Implied. 51 | 52 | return: None 53 | """ 54 | memory = m6502.Memory() 55 | cpu = m6502.Processor(memory) 56 | cpu.reset() 57 | cpu.reg_a = value 58 | cpu.reg_x = 0x00 59 | memory[0xFCE2] = 0xAA 60 | cpu.execute(2) 61 | assert ( 62 | cpu.program_counter, 63 | cpu.stack_pointer, 64 | cpu.cycles, 65 | cpu.flag_n, 66 | cpu.flag_z, 67 | cpu.reg_x, 68 | ) == (0xFCE3, 0x01FD, 2, flag_n, flag_z, value) 69 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_tay.py: -------------------------------------------------------------------------------- 1 | """ 2 | TAY - Transfer Accumulator to Y. 3 | 4 | Y = A 5 | 6 | Copies the current contents of the accumulator into the Y register and sets 7 | the zero and negative flags as appropriate. 8 | 9 | Processor Status after use: 10 | 11 | +------+-------------------+--------------------------+ 12 | | Flag | Description | State | 13 | +======+===================+==========================+ 14 | | C | Carry Flag | Not affected | 15 | +------+-------------------+--------------------------+ 16 | | Z | Zero Flag | Set is Y = 0 | 17 | +------+-------------------+--------------------------+ 18 | | I | Interrupt Disable | Not affected | 19 | +------+-------------------+--------------------------+ 20 | | D | Decimal Mode Flag | Not affected | 21 | +------+-------------------+--------------------------+ 22 | | B | Break Command | Not affected | 23 | +------+-------------------+--------------------------+ 24 | | V | Overflow Flag | Not affected | 25 | +------+-------------------+--------------------------+ 26 | | N | Negative Flag | Set if bit 7 of Y is set | 27 | +------+-------------------+--------------------------+ 28 | 29 | +-----------------+--------+-------+--------+ 30 | | Addressing Mode | Opcode | Bytes | Cycles | 31 | +=================+========+=======+========+ 32 | | Implied | 0xA8 | 1 | 2 | 33 | +-----------------+--------+-------+--------+ 34 | 35 | See also: TYA 36 | """ 37 | import pytest 38 | 39 | import m6502 40 | 41 | 42 | @pytest.mark.parametrize( 43 | ("value", "flag_n", "flag_z"), [ 44 | (0x0F, False, False), 45 | (0x00, False, True), 46 | (0xF0, True, False), 47 | ]) 48 | def test_cpu_ins_tay_imm(value: int, flag_n: bool, flag_z: bool) -> None: 49 | """ 50 | Transfer Accumulator, Implied. 51 | 52 | return: None 53 | """ 54 | memory = m6502.Memory() 55 | cpu = m6502.Processor(memory) 56 | cpu.reset() 57 | cpu.reg_a = value 58 | cpu.reg_y = 0x00 59 | memory[0xFCE2] = 0xA8 60 | cpu.execute(2) 61 | assert ( 62 | cpu.program_counter, 63 | cpu.stack_pointer, 64 | cpu.cycles, 65 | cpu.flag_n, 66 | cpu.flag_z, 67 | cpu.reg_y, 68 | ) == (0xFCE3, 0x01FD, 2, flag_n, flag_z, value) 69 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_txa.py: -------------------------------------------------------------------------------- 1 | """ 2 | TXA - Transfer Register X to Accumulator. 3 | 4 | A = X 5 | 6 | Copies the current contents of the X register into the accumulator and sets 7 | the zero and negative flags as appropriate. 8 | 9 | Processor Status after use: 10 | 11 | +------+-------------------+--------------------------+ 12 | | Flag | Description | State | 13 | +======+===================+==========================+ 14 | | C | Carry Flag | Not affected | 15 | +------+-------------------+--------------------------+ 16 | | Z | Zero Flag | Set is A = 0 | 17 | +------+-------------------+--------------------------+ 18 | | I | Interrupt Disable | Not affected | 19 | +------+-------------------+--------------------------+ 20 | | D | Decimal Mode Flag | Not affected | 21 | +------+-------------------+--------------------------+ 22 | | B | Break Command | Not affected | 23 | +------+-------------------+--------------------------+ 24 | | V | Overflow Flag | Not affected | 25 | +------+-------------------+--------------------------+ 26 | | N | Negative Flag | Set if bit 7 of A is set | 27 | +------+-------------------+--------------------------+ 28 | 29 | +-----------------+--------+-------+--------+ 30 | | Addressing Mode | Opcode | Bytes | Cycles | 31 | +=================+========+=======+========+ 32 | | Implied | 0x8A | 1 | 2 | 33 | +-----------------+--------+-------+--------+ 34 | 35 | See also: TAX 36 | """ 37 | import pytest 38 | 39 | import m6502 40 | 41 | 42 | @pytest.mark.parametrize( 43 | ("value", "flag_n", "flag_z"), [ 44 | (0x0F, False, False), 45 | (0x00, False, True), 46 | (0xF0, True, False), 47 | ]) 48 | def test_cpu_ins_txa_imm(value: int, flag_n: bool, flag_z: bool) -> None: 49 | """ 50 | Transfer Accumulator, Implied. 51 | 52 | return: None 53 | """ 54 | memory = m6502.Memory() 55 | cpu = m6502.Processor(memory) 56 | cpu.reset() 57 | cpu.reg_a = 0x00 58 | cpu.reg_x = value 59 | memory[0xFCE2] = 0x8A 60 | cpu.execute(2) 61 | assert ( 62 | cpu.program_counter, 63 | cpu.stack_pointer, 64 | cpu.cycles, 65 | cpu.flag_n, 66 | cpu.flag_z, 67 | cpu.reg_a, 68 | ) == (0xFCE3, 0x01FD, 2, flag_n, flag_z, value) 69 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_tya.py: -------------------------------------------------------------------------------- 1 | """ 2 | TYA - Transfer Register Y to Accumulator. 3 | 4 | A = Y 5 | 6 | Copies the current contents of the Y register into the accumulator and sets 7 | the zero and negative flags as appropriate. 8 | 9 | Processor Status after use: 10 | 11 | +------+-------------------+--------------------------+ 12 | | Flag | Description | State | 13 | +======+===================+==========================+ 14 | | C | Carry Flag | Not affected | 15 | +------+-------------------+--------------------------+ 16 | | Z | Zero Flag | Set is A = 0 | 17 | +------+-------------------+--------------------------+ 18 | | I | Interrupt Disable | Not affected | 19 | +------+-------------------+--------------------------+ 20 | | D | Decimal Mode Flag | Not affected | 21 | +------+-------------------+--------------------------+ 22 | | B | Break Command | Not affected | 23 | +------+-------------------+--------------------------+ 24 | | V | Overflow Flag | Not affected | 25 | +------+-------------------+--------------------------+ 26 | | N | Negative Flag | Set if bit 7 of A is set | 27 | +------+-------------------+--------------------------+ 28 | 29 | +-----------------+--------+-------+--------+ 30 | | Addressing Mode | Opcode | Bytes | Cycles | 31 | +=================+========+=======+========+ 32 | | Implied | 0x98 | 1 | 2 | 33 | +-----------------+--------+-------+--------+ 34 | 35 | See also: TAY 36 | """ 37 | import pytest 38 | 39 | import m6502 40 | 41 | 42 | @pytest.mark.parametrize( 43 | ("value", "flag_n", "flag_z"), [ 44 | (0x0F, False, False), 45 | (0x00, False, True), 46 | (0xF0, True, False), 47 | ]) 48 | def test_cpu_ins_tya_imm(value: int, flag_n: bool, flag_z: bool) -> None: 49 | """ 50 | Transfer Accumulator, Implied. 51 | 52 | return: None 53 | """ 54 | memory = m6502.Memory() 55 | cpu = m6502.Processor(memory) 56 | cpu.reset() 57 | cpu.reg_a = 0x00 58 | cpu.reg_y = value 59 | memory[0xFCE2] = 0x98 60 | cpu.execute(2) 61 | assert ( 62 | cpu.program_counter, 63 | cpu.stack_pointer, 64 | cpu.cycles, 65 | cpu.flag_n, 66 | cpu.flag_z, 67 | cpu.reg_a, 68 | ) == (0xFCE3, 0x01FD, 2, flag_n, flag_z, value) 69 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_tsx.py: -------------------------------------------------------------------------------- 1 | """ 2 | TSX - Transfer Stack Pointer to X. 3 | 4 | X = S 5 | 6 | Copies the current contents of the stack register into the X register and sets 7 | the zero and negative flags as appropriate. 8 | 9 | Processor Status after use: 10 | 11 | +------+-------------------+--------------------------+ 12 | | Flag | Description | State | 13 | +======+===================+==========================+ 14 | | C | Carry Flag | Not affected | 15 | +------+-------------------+--------------------------+ 16 | | Z | Zero Flag | Set is X = 0 | 17 | +------+-------------------+--------------------------+ 18 | | I | Interrupt Disable | Not affected | 19 | +------+-------------------+--------------------------+ 20 | | D | Decimal Mode Flag | Not affected | 21 | +------+-------------------+--------------------------+ 22 | | B | Break Command | Not affected | 23 | +------+-------------------+--------------------------+ 24 | | V | Overflow Flag | Not affected | 25 | +------+-------------------+--------------------------+ 26 | | N | Negative Flag | Set if bit 7 of X is set | 27 | +------+-------------------+--------------------------+ 28 | 29 | +-----------------+--------+-------+--------+ 30 | | Addressing Mode | Opcode | Bytes | Cycles | 31 | +=================+========+=======+========+ 32 | | Implied | 0xBA | 1 | 2 | 33 | +-----------------+--------+-------+--------+ 34 | 35 | See also: TXS 36 | """ 37 | import pytest 38 | 39 | import m6502 40 | 41 | 42 | @pytest.mark.parametrize( 43 | ("value", "flag_n", "flag_z"), [ 44 | (0x0F, False, False), 45 | (0x00, False, True), 46 | (0xF0, True, False), 47 | ]) 48 | def test_cpu_ins_tsx_imm(value: int, flag_n: bool, flag_z: bool) -> None: 49 | """ 50 | Transfer Stack Pointer to X, Implied. 51 | 52 | return: None 53 | """ 54 | memory = m6502.Memory() 55 | cpu = m6502.Processor(memory) 56 | cpu.reset() 57 | cpu.reg_x = 0x00 58 | memory[0xFCE2] = 0xBA 59 | memory[cpu.stack_pointer] = value 60 | cpu.execute(2) 61 | assert ( 62 | cpu.program_counter, 63 | cpu.stack_pointer, 64 | cpu.cycles, 65 | cpu.flag_n, 66 | cpu.flag_z, 67 | cpu.reg_x, 68 | ) == (0xFCE3, 0x01FE, 2, flag_n, flag_z, value) 69 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[codz] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_sty.py: -------------------------------------------------------------------------------- 1 | """ 2 | STY - Store Y Register. 3 | 4 | M = Y 5 | 6 | Stores the contents of the Y register into memory. 7 | 8 | +------+-------------------+--------------------------+ 9 | | Flag | Description | State | 10 | +======+===================+==========================+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------------------+ 25 | 26 | +-----------------+--------+-------+--------------------------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+==========================+ 29 | | Zero Page | 0x84 | 2 | 3 | 30 | +-----------------+--------+-------+--------------------------+ 31 | | Zero Page, X | 0x94 | 2 | 4 | 32 | +-----------------+--------+-------+--------------------------+ 33 | | Absolute | 0x8C | 3 | 4 | 34 | +-----------------+--------+-------+--------------------------+ 35 | 36 | See also: STY, STX 37 | """ 38 | import pytest 39 | 40 | import m6502 41 | 42 | 43 | def test_cpu_ins_sty_zp() -> None: 44 | """ 45 | Store Y Register, Zero Page. 46 | 47 | return: None 48 | """ 49 | memory = m6502.Memory() 50 | cpu = m6502.Processor(memory) 51 | cpu.reset() 52 | cpu.reg_y = 0xF0 53 | memory[0xFCE2] = 0x84 54 | memory[0xFCE3] = 0xFC 55 | memory[0xFC] = 0 56 | cpu.execute(3) 57 | assert ( 58 | cpu.program_counter, 59 | cpu.stack_pointer, 60 | cpu.cycles, 61 | memory[0xFC], 62 | ) == (0xFCE4, 0x01FD, 3, 0xF0) 63 | 64 | 65 | @pytest.mark.parametrize( 66 | ("reg_x", "memory_location"), [ 67 | (0x0F, 0x8F), 68 | (0xFF, 0x7F), 69 | ]) 70 | def test_cpu_ins_sty_zpx(reg_x: int, memory_location: int) -> None: 71 | """ 72 | Store Y Register, Zero Page, X. 73 | 74 | return: None 75 | """ 76 | memory = m6502.Memory() 77 | cpu = m6502.Processor(memory) 78 | cpu.reset() 79 | cpu.reg_y = 0xF0 80 | cpu.reg_x = reg_x 81 | memory[0xFCE2] = 0x94 82 | memory[0xFCE3] = 0x80 83 | memory[memory_location] = 0x00 84 | cpu.execute(4) 85 | assert ( 86 | cpu.program_counter, 87 | cpu.stack_pointer, 88 | cpu.cycles, 89 | memory[memory_location], 90 | ) == (0xFCE4, 0x01FD, 4, 0xF0) 91 | 92 | 93 | def test_cpu_ins_sty_abs() -> None: 94 | """ 95 | Store Y Register, Absolute. 96 | 97 | return: None 98 | """ 99 | memory = m6502.Memory() 100 | cpu = m6502.Processor(memory) 101 | cpu.reset() 102 | cpu.reg_y = 0xF0 103 | memory[0xFCE2] = 0x8C 104 | memory[0xFCE3] = 0xFA 105 | memory[0xFCE4] = 0xFA 106 | memory[0xFAFA] = 0x00 107 | cpu.execute(4) 108 | assert ( 109 | cpu.program_counter, 110 | cpu.stack_pointer, 111 | cpu.cycles, 112 | cpu.reg_y, 113 | ) == (0xFCE5, 0x01FD, 4, 0xF0) 114 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_stx.py: -------------------------------------------------------------------------------- 1 | """ 2 | STX - Store X Register. 3 | 4 | M = X 5 | 6 | Stores the contents of the X register into memory. 7 | 8 | +------+-------------------+--------------------------+ 9 | | Flag | Description | State | 10 | +======+===================+==========================+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------------------+ 25 | 26 | +-----------------+--------+-------+--------------------------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+==========================+ 29 | | Zero Page | 0x86 | 2 | 3 | 30 | +-----------------+--------+-------+--------------------------+ 31 | | Zero Page, Y | 0x96 | 2 | 4 | 32 | +-----------------+--------+-------+--------------------------+ 33 | | Absolute | 0x8E | 3 | 4 | 34 | +-----------------+--------+-------+--------------------------+ 35 | 36 | See also: STA, STY 37 | """ 38 | import pytest 39 | 40 | import m6502 41 | 42 | 43 | def test_cpu_ins_stx_zp() -> None: 44 | """ 45 | Store X Register, Zero Page. 46 | 47 | return: None 48 | """ 49 | memory = m6502.Memory() 50 | cpu = m6502.Processor(memory) 51 | cpu.reset() 52 | cpu.reg_x = 0xF0 53 | memory[0xFCE2] = 0x86 54 | memory[0xFCE3] = 0xFC 55 | memory[0xFC] = 0 56 | cpu.execute(3) 57 | assert ( 58 | cpu.program_counter, 59 | cpu.stack_pointer, 60 | cpu.cycles, 61 | memory[0xFC], 62 | ) == (0xFCE4, 0x01FD, 3, 0xF0) 63 | 64 | 65 | @pytest.mark.parametrize( 66 | ("reg_y", "memory_location"), [ 67 | (0x0F, 0x8F), 68 | (0xFF, 0x7F), 69 | ]) 70 | def test_cpu_ins_stx_zpy(reg_y: int, memory_location: int) -> None: 71 | """ 72 | Store X Register, Zero Page, Y. 73 | 74 | The Zero Page address may not exceed beyond 0xFF: 75 | 76 | - 0x80 + 0x0F => 0x8F 77 | - 0x80 + 0xFF => 0x7F (0x017F) 78 | 79 | return: None 80 | """ 81 | memory = m6502.Memory() 82 | cpu = m6502.Processor(memory) 83 | cpu.reset() 84 | cpu.reg_x = 0xF0 85 | cpu.reg_y = reg_y 86 | memory[0xFCE2] = 0x96 87 | memory[0xFCE3] = 0x80 88 | memory[memory_location] = 0x00 89 | cpu.execute(4) 90 | assert ( 91 | cpu.program_counter, 92 | cpu.stack_pointer, 93 | cpu.cycles, 94 | memory[memory_location], 95 | ) == (0xFCE4, 0x01FD, 4, 0xF0) 96 | 97 | 98 | def test_cpu_ins_stx_abs() -> None: 99 | """ 100 | Store X Register, Absolute. 101 | 102 | return: None 103 | """ 104 | memory = m6502.Memory() 105 | cpu = m6502.Processor(memory) 106 | cpu.reset() 107 | cpu.reg_x = 0xF0 108 | memory[0xFCE2] = 0x8E 109 | memory[0xFCE3] = 0xFA 110 | memory[0xFCE4] = 0xFA 111 | memory[0xFAFA] = 0x00 112 | cpu.execute(4) 113 | assert ( 114 | cpu.program_counter, 115 | cpu.stack_pointer, 116 | cpu.cycles, 117 | cpu.reg_x, 118 | ) == (0xFCE5, 0x01FD, 4, 0xF0) 119 | -------------------------------------------------------------------------------- /.github/workflows/codeql.yml: -------------------------------------------------------------------------------- 1 | # For most projects, this workflow file will not need changing; you simply need 2 | # to commit it to your repository. 3 | # 4 | # You may wish to alter this file to override the set of languages analyzed, 5 | # or to provide custom queries or build logic. 6 | # 7 | # ******** NOTE ******** 8 | # We have attempted to detect the languages in your repository. Please check 9 | # the `language` matrix defined below to confirm you have the correct set of 10 | # supported CodeQL languages. 11 | # 12 | --- 13 | name: "CodeQL Advanced" 14 | 15 | on: 16 | push: 17 | branches: [ "master" ] 18 | pull_request: 19 | branches: [ "master" ] 20 | schedule: 21 | - cron: '42 21 * * 6' 22 | 23 | jobs: 24 | analyze: 25 | name: Analyze (${{ matrix.language }}) 26 | # Runner size impacts CodeQL analysis time. To learn more, please see: 27 | # - https://gh.io/recommended-hardware-resources-for-running-codeql 28 | # - https://gh.io/supported-runners-and-hardware-resources 29 | # - https://gh.io/using-larger-runners (GitHub.com only) 30 | # Consider using larger runners or machines with greater resources for possible analysis time improvements. 31 | runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} 32 | permissions: 33 | # required for all workflows 34 | security-events: write 35 | 36 | # required to fetch internal or private CodeQL packs 37 | packages: read 38 | 39 | # only required for workflows in private repositories 40 | actions: read 41 | contents: read 42 | 43 | strategy: 44 | fail-fast: false 45 | matrix: 46 | include: 47 | - language: actions 48 | build-mode: none 49 | - language: python 50 | build-mode: none 51 | # CodeQL supports the following values keywords for 'language': 'actions', 'c-cpp', 'csharp', 'go', 'java-kotlin', 'javascript-typescript', 'python', 'ruby', 'rust', 'swift' 52 | # Use `c-cpp` to analyze code written in C, C++ or both 53 | # Use 'java-kotlin' to analyze code written in Java, Kotlin or both 54 | # Use 'javascript-typescript' to analyze code written in JavaScript, TypeScript or both 55 | # To learn more about changing the languages that are analyzed or customizing the build mode for your analysis, 56 | # see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/customizing-your-advanced-setup-for-code-scanning. 57 | # If you are analyzing a compiled language, you can modify the 'build-mode' for that language to customize how 58 | # your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages 59 | steps: 60 | - name: Checkout repository 61 | uses: actions/checkout@v6 62 | 63 | # Add any setup steps before running the `github/codeql-action/init` action. 64 | # This includes steps like installing compilers or runtimes (`actions/setup-node` 65 | # or others). This is typically only required for manual builds. 66 | # - name: Setup runtime (example) 67 | # uses: actions/setup-example@v1 68 | 69 | # Initializes the CodeQL tools for scanning. 70 | - name: Initialize CodeQL 71 | uses: github/codeql-action/init@v4 72 | with: 73 | languages: ${{ matrix.language }} 74 | build-mode: ${{ matrix.build-mode }} 75 | # If you wish to specify custom queries, you can do so here or in a config file. 76 | # By default, queries listed here will override any specified in a config file. 77 | # Prefix the list here with "+" to use these queries and those in the config file. 78 | 79 | # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs 80 | # queries: security-extended,security-and-quality 81 | 82 | # If the analyze step fails for one of the languages you are analyzing with 83 | # "We were unable to automatically build your code", modify the matrix above 84 | # to set the build mode to "manual" for that language. Then modify this step 85 | # to build your code. 86 | # ℹ️ Command-line programs to run using the OS shell. 87 | # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun 88 | - if: matrix.build-mode == 'manual' 89 | shell: bash 90 | run: | 91 | echo 'If you are using a "manual" build mode for one or more of the' \ 92 | 'languages you are analyzing, replace this with the commands to build' \ 93 | 'your code, for example:' 94 | echo ' make bootstrap' 95 | echo ' make release' 96 | exit 1 97 | 98 | - name: Perform CodeQL Analysis 99 | uses: github/codeql-action/analyze@v4 100 | with: 101 | category: "/language:${{matrix.language}}" 102 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_ldx.py: -------------------------------------------------------------------------------- 1 | """ 2 | LDX - Load X Register. 3 | 4 | X,Z,N = M 5 | 6 | Loads a byte of memory into the X register setting the zero and negative 7 | flags as appropriate. 8 | 9 | +------+-------------------+--------------------------+ 10 | | Flag | Description | State | 11 | +======+===================+==========================+ 12 | | C | Carry Flag | Not affected | 13 | +------+-------------------+--------------------------+ 14 | | Z | Zero Flag | Set is X = 0 | 15 | +------+-------------------+--------------------------+ 16 | | I | Interrupt Disable | Not affected | 17 | +------+-------------------+--------------------------+ 18 | | D | Decimal Mode Flag | Not affected | 19 | +------+-------------------+--------------------------+ 20 | | B | Break Command | Not affected | 21 | +------+-------------------+--------------------------+ 22 | | V | Overflow Flag | Not affected | 23 | +------+-------------------+--------------------------+ 24 | | N | Negative Flag | Set if bit 7 of X is set | 25 | +------+-------------------+--------------------------+ 26 | 27 | +-----------------+--------+-------+--------------------------+ 28 | | Addressing Mode | Opcode | Bytes | Cycles | 29 | +=================+========+=======+==========================+ 30 | | Immediate | 0xA2 | 2 | 2 | 31 | +-----------------+--------+-------+--------------------------+ 32 | | Zero Page | 0xA6 | 2 | 3 | 33 | +-----------------+--------+-------+--------------------------+ 34 | | Zero Page, Y | 0xB6 | 2 | 5 | 35 | +-----------------+--------+-------+--------------------------+ 36 | | Absolute | 0xAE | 3 | 4 | 37 | +-----------------+--------+-------+--------------------------+ 38 | | Absolute, Y | 0xBE | 3 | 4 (+1 if page crossed) | 39 | +-----------------+--------+-------+--------------------------+ 40 | 41 | See also: LDA, LDY 42 | """ 43 | import pytest 44 | 45 | import m6502 46 | 47 | 48 | def test_cpu_ins_ldx_imm() -> None: 49 | """ 50 | Load X Register, Immediate. 51 | 52 | return: None 53 | """ 54 | memory = m6502.Memory() 55 | cpu = m6502.Processor(memory) 56 | cpu.reset() 57 | cpu.reg_x = 0x00 58 | memory[0xFCE2] = 0xA2 59 | memory[0xFCE3] = 0xF0 60 | cpu.execute(2) 61 | assert ( 62 | cpu.program_counter, 63 | cpu.stack_pointer, 64 | cpu.cycles, 65 | cpu.reg_x, 66 | ) == (0xFCE4, 0x01FD, 2, 0xF0) 67 | 68 | 69 | def test_cpu_ins_ldx_zp() -> None: 70 | """ 71 | Load X Register, Zero Page. 72 | 73 | return: None 74 | """ 75 | memory = m6502.Memory() 76 | cpu = m6502.Processor(memory) 77 | cpu.reset() 78 | cpu.reg_x = 0x00 79 | memory[0xFCE2] = 0xA6 80 | memory[0xFCE3] = 0xFC 81 | memory[0xFC] = 0xF0 82 | cpu.execute(3) 83 | assert ( 84 | cpu.program_counter, 85 | cpu.stack_pointer, 86 | cpu.cycles, 87 | cpu.reg_x, 88 | ) == (0xFCE4, 0x01FD, 3, 0xF0) 89 | 90 | 91 | @pytest.mark.parametrize( 92 | ("reg_y", "memory_location"), [ 93 | (0x0F, 0x8F), 94 | (0xFF, 0x7F), 95 | ]) 96 | def test_cpu_ins_ldx_zpy(reg_y: int, memory_location: int) -> None: 97 | """ 98 | Load X Register, Zero Page, Y. 99 | 100 | The Zero Page address may not exceed beyond 0xFF: 101 | 102 | - 0x80 + 0x0F => 0x8F 103 | - 0x80 + 0xFF => 0x7F (0x017F) 104 | 105 | return: None 106 | """ 107 | memory = m6502.Memory() 108 | cpu = m6502.Processor(memory) 109 | cpu.reset() 110 | cpu.reg_x = 0x00 111 | cpu.reg_y = reg_y 112 | memory[0xFCE2] = 0xB6 113 | memory[0xFCE3] = 0x80 114 | memory[memory_location] = 0xF0 115 | cpu.execute(4) 116 | assert ( 117 | cpu.program_counter, 118 | cpu.stack_pointer, 119 | cpu.cycles, 120 | cpu.reg_x, 121 | ) == (0xFCE4, 0x01FD, 4, 0xF0) 122 | 123 | 124 | def test_cpu_ins_ldx_abs() -> None: 125 | """ 126 | Load X Register, Absolute. 127 | 128 | return: None 129 | """ 130 | memory = m6502.Memory() 131 | cpu = m6502.Processor(memory) 132 | cpu.reset() 133 | cpu.reg_x = 0x00 134 | memory[0xFCE2] = 0xAE 135 | memory[0xFCE3] = 0xFA 136 | memory[0xFCE4] = 0xFA 137 | memory[0xFAFA] = 0xF0 138 | cpu.execute(4) 139 | assert ( 140 | cpu.program_counter, 141 | cpu.stack_pointer, 142 | cpu.cycles, 143 | cpu.reg_x, 144 | ) == (0xFCE5, 0x01FD, 4, 0xF0) 145 | 146 | 147 | def test_cpu_ins_ldx_aby() -> None: 148 | """ 149 | Load X Register, Absolute, Y. 150 | 151 | TODO: This test doesn't test the page crossing. 152 | 153 | return: None 154 | """ 155 | memory = m6502.Memory() 156 | cpu = m6502.Processor(memory) 157 | cpu.reset() 158 | cpu.reg_x = 0x00 159 | cpu.reg_y = 1 160 | memory[0xFCE2] = 0xBE 161 | memory[0xFCE3] = 0xFA 162 | memory[0xFCE4] = 0xFA 163 | memory[0xFAFA + cpu.reg_y] = 0xF0 164 | cpu.execute(4) 165 | assert ( 166 | cpu.program_counter, 167 | cpu.stack_pointer, 168 | cpu.cycles, 169 | cpu.reg_x, 170 | ) == (0xFCE5, 0x01FD, 4, 0xF0) 171 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_ldy.py: -------------------------------------------------------------------------------- 1 | """ 2 | LDY - Load Y Register. 3 | 4 | Y,Z,N = M 5 | 6 | Loads a byte of memory into the Y register setting the zero and negative 7 | flags as appropriate. 8 | 9 | +------+-------------------+--------------------------+ 10 | | Flag | Description | State | 11 | +======+===================+==========================+ 12 | | C | Carry Flag | Not affected | 13 | +------+-------------------+--------------------------+ 14 | | Z | Zero Flag | Set is Y = 0 | 15 | +------+-------------------+--------------------------+ 16 | | I | Interrupt Disable | Not affected | 17 | +------+-------------------+--------------------------+ 18 | | D | Decimal Mode Flag | Not affected | 19 | +------+-------------------+--------------------------+ 20 | | B | Break Command | Not affected | 21 | +------+-------------------+--------------------------+ 22 | | V | Overflow Flag | Not affected | 23 | +------+-------------------+--------------------------+ 24 | | N | Negative Flag | Set if bit 7 of Y is set | 25 | +------+-------------------+--------------------------+ 26 | 27 | +-----------------+--------+-------+--------------------------+ 28 | | Addressing Mode | Opcode | Bytes | Cycles | 29 | +=================+========+=======+==========================+ 30 | | Immediate | 0xA0 | 2 | 2 | 31 | +-----------------+--------+-------+--------------------------+ 32 | | Zero Page | 0xA4 | 2 | 3 | 33 | +-----------------+--------+-------+--------------------------+ 34 | | Zero Page, X | 0xB4 | 2 | 5 | 35 | +-----------------+--------+-------+--------------------------+ 36 | | Absolute | 0xAC | 3 | 4 | 37 | +-----------------+--------+-------+--------------------------+ 38 | | Absolute, X | 0xBC | 3 | 4 (+1 if page crossed) | 39 | +-----------------+--------+-------+--------------------------+ 40 | 41 | See also: LDA, LDY 42 | """ 43 | import pytest 44 | 45 | import m6502 46 | 47 | 48 | def test_cpu_ins_ldy_imm() -> None: 49 | """ 50 | Load Y Register, Immediate. 51 | 52 | return: None 53 | """ 54 | memory = m6502.Memory() 55 | cpu = m6502.Processor(memory) 56 | cpu.reset() 57 | cpu.reg_y = 0x00 58 | memory[0xFCE2] = 0xA0 59 | memory[0xFCE3] = 0xF0 60 | cpu.execute(2) 61 | assert ( 62 | cpu.program_counter, 63 | cpu.stack_pointer, 64 | cpu.cycles, 65 | cpu.reg_y, 66 | ) == (0xFCE4, 0x01FD, 2, 0xF0) 67 | 68 | 69 | def test_cpu_ins_ldy_zp() -> None: 70 | """ 71 | Load Y Register, Zero Page. 72 | 73 | return: None 74 | """ 75 | memory = m6502.Memory() 76 | cpu = m6502.Processor(memory) 77 | cpu.reset() 78 | cpu.reg_y = 0x00 79 | memory[0xFCE2] = 0xA4 80 | memory[0xFCE3] = 0xFC 81 | memory[0xFC] = 0xF0 82 | cpu.execute(3) 83 | assert ( 84 | cpu.program_counter, 85 | cpu.stack_pointer, 86 | cpu.cycles, 87 | cpu.reg_y, 88 | ) == (0xFCE4, 0x01FD, 3, 0xF0) 89 | 90 | 91 | @pytest.mark.parametrize( 92 | ("reg_x", "memory_location"), [ 93 | (0x0F, 0x8F), 94 | (0xFF, 0x7F), 95 | ]) 96 | def test_cpu_ins_ldy_zpx(reg_x: int, memory_location: int) -> None: 97 | """ 98 | Load Y Register, Zero Page, X. 99 | 100 | The Zero Page address may not exceed beyond 0xFF: 101 | 102 | - 0x80 + 0x0F => 0x8F 103 | - 0x80 + 0xFF => 0x7F (0x017F) 104 | 105 | return: None 106 | """ 107 | memory = m6502.Memory() 108 | cpu = m6502.Processor(memory) 109 | cpu.reset() 110 | cpu.reg_y = 0x00 111 | cpu.reg_x = reg_x 112 | memory[0xFCE2] = 0xB4 113 | memory[0xFCE3] = 0x80 114 | memory[memory_location] = 0xF0 115 | cpu.execute(4) 116 | assert ( 117 | cpu.program_counter, 118 | cpu.stack_pointer, 119 | cpu.cycles, 120 | cpu.reg_y, 121 | ) == (0xFCE4, 0x01FD, 4, 0xF0) 122 | 123 | 124 | def test_cpu_ins_ldy_abs() -> None: 125 | """ 126 | Load Y Register, Absolute. 127 | 128 | return: None 129 | """ 130 | memory = m6502.Memory() 131 | cpu = m6502.Processor(memory) 132 | cpu.reset() 133 | cpu.reg_y = 0x00 134 | memory[0xFCE2] = 0xAC 135 | memory[0xFCE3] = 0xFA 136 | memory[0xFCE4] = 0xFA 137 | memory[0xFAFA] = 0xF0 138 | cpu.execute(4) 139 | assert ( 140 | cpu.program_counter, 141 | cpu.stack_pointer, 142 | cpu.cycles, 143 | cpu.reg_y, 144 | ) == (0xFCE5, 0x01FD, 4, 0xF0) 145 | 146 | 147 | def test_cpu_ins_ldy_abx() -> None: 148 | """ 149 | Load Y Register, Absolute, X. 150 | 151 | TODO: This test doesn't test the page crossing. 152 | 153 | return: None 154 | """ 155 | memory = m6502.Memory() 156 | cpu = m6502.Processor(memory) 157 | cpu.reset() 158 | cpu.reg_y = 0x00 159 | cpu.reg_x = 1 160 | memory[0xFCE2] = 0xBC 161 | memory[0xFCE3] = 0xFA 162 | memory[0xFCE4] = 0xFA 163 | memory[0xFAFA + cpu.reg_x] = 0xF0 164 | cpu.execute(4) 165 | assert ( 166 | cpu.program_counter, 167 | cpu.stack_pointer, 168 | cpu.cycles, 169 | cpu.reg_y, 170 | ) == (0xFCE5, 0x01FD, 4, 0xF0) 171 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_dec.py: -------------------------------------------------------------------------------- 1 | """ 2 | DEC - Decrement Memory. 3 | 4 | M,Z,N = M-1 5 | 6 | Subtracts one from the value held at a specified memory location setting the 7 | zero and negative flags as appropriate. 8 | 9 | +------+-------------------+-------------------------------+ 10 | | Flag | Description | State | 11 | +======+===================+===============================+ 12 | | C | Carry Flag | Not affected | 13 | +------+-------------------+-------------------------------+ 14 | | Z | Zero Flag | Set if result is zero | 15 | +------+-------------------+-------------------------------+ 16 | | I | Interrupt Disable | Not affected | 17 | +------+-------------------+-------------------------------+ 18 | | D | Decimal Mode Flag | Not affected | 19 | +------+-------------------+-------------------------------+ 20 | | B | Break Command | Not affected | 21 | +------+-------------------+-------------------------------+ 22 | | V | Overflow Flag | Not affected | 23 | +------+-------------------+-------------------------------+ 24 | | N | Negative Flag | Set if bit 7 of result is set | 25 | +------+-------------------+-------------------------------+ 26 | 27 | +-----------------+--------+-------+--------+ 28 | | Addressing Mode | Opcode | Bytes | Cycles | 29 | +=================+========+=======+========+ 30 | | Zero Page | 0xC6 | 2 | 5 | 31 | +-----------------+--------+-------+--------+ 32 | | Zero Page, X | 0xD6 | 2 | 6 | 33 | +-----------------+--------+-------+--------+ 34 | | Absolute | 0xCE | 3 | 6 | 35 | +-----------------+--------+-------+--------+ 36 | | Absolute, X | 0xDE | 3 | 7 | 37 | +-----------------+--------+-------+--------+ 38 | 39 | See also: DEX, DEY 40 | 41 | """ 42 | import pytest 43 | 44 | import m6502 45 | 46 | 47 | @pytest.mark.parametrize( 48 | ("value", "expected", "flag_z", "flag_n"), [ 49 | (-1, -2, False, True), 50 | (0, -1, False, True), 51 | (1, 0, True, False), 52 | (2, 1, False, False) 53 | ]) 54 | def test_cpu_ins_dec_zp(value: int, expected: int, flag_z: bool, flag_n: bool) -> None: 55 | """ 56 | Decrement Memory, Zero Page. 57 | 58 | return: None 59 | """ 60 | memory = m6502.Memory() 61 | cpu = m6502.Processor(memory) 62 | cpu.reset() 63 | memory[0xFCE2] = 0xC6 64 | memory[0xFCE3] = 0xFC 65 | memory[0xFC] = value 66 | cpu.execute(5) 67 | assert ( 68 | cpu.program_counter, 69 | cpu.stack_pointer, 70 | cpu.cycles, 71 | cpu.flag_z, 72 | cpu.flag_n, 73 | memory[0xFC], 74 | ) == (0xFCE4, 0x01FD, 5, flag_z, flag_n, expected) 75 | 76 | 77 | @pytest.mark.parametrize( 78 | ("value", "expected", "flag_z", "flag_n", "reg_x", "memory_location"), [ 79 | (-1, -2, False, True, 0x0F, 0x8F), 80 | (0, -1, False, True, 0x0F, 0x8F), 81 | (1, 0, True, False, 0x0F, 0x8F), 82 | (2, 1, False, False, 0x0F, 0x8F), 83 | (-1, -2, False, True, 0xFF, 0x7F), 84 | (0, -1, False, True, 0xFF, 0x7F), 85 | (1, 0, True, False, 0xFF, 0x7F), 86 | (2, 1, False, False, 0xFF, 0x7F) 87 | ]) 88 | def test_cpu_ins_dec_zpx(value: int, expected: int, flag_z: bool, flag_n: bool, reg_x: int, memory_location: int) -> None: 89 | """ 90 | Decrement Memory, Zero Page, X. 91 | 92 | The Zero Page address may not exceed beyond 0xFF: 93 | 94 | - 0x80 + 0x0F => 0x8F 95 | - 0x80 + 0xFF => 0x7F (0x017F) 96 | 97 | return: None 98 | """ 99 | memory = m6502.Memory() 100 | cpu = m6502.Processor(memory) 101 | cpu.reset() 102 | cpu.reg_x = reg_x 103 | memory[0xFCE2] = 0xD6 104 | memory[0xFCE3] = 0x80 105 | memory[memory_location] = value 106 | cpu.execute(6) 107 | assert ( 108 | cpu.program_counter, 109 | cpu.stack_pointer, 110 | cpu.cycles, 111 | cpu.flag_z, 112 | cpu.flag_n, 113 | memory[memory_location], 114 | ) == (0xFCE4, 0x01FD, 6, flag_z, flag_n, expected) 115 | 116 | 117 | @pytest.mark.parametrize( 118 | ("value", "expected", "flag_z", "flag_n"), [ 119 | (-1, -2, False, True), 120 | (0, -1, False, True), 121 | (1, 0, True, False), 122 | (2, 1, False, False) 123 | ]) 124 | def test_cpu_ins_dec_abs(value: int, expected: int, flag_z: bool, flag_n: bool) -> None: 125 | """ 126 | Decrement Memory, Absolute. 127 | 128 | return: None 129 | """ 130 | memory = m6502.Memory() 131 | cpu = m6502.Processor(memory) 132 | cpu.reset() 133 | memory[0xFCE2] = 0xCE 134 | memory[0xFCE3] = 0xFC 135 | memory[0xFCE4] = 0xFA 136 | memory[0xFAFC] = value 137 | cpu.execute(6) 138 | assert ( 139 | cpu.program_counter, 140 | cpu.stack_pointer, 141 | cpu.cycles, 142 | cpu.flag_z, 143 | cpu.flag_n, 144 | memory[0xFAFC], 145 | ) == (0xFCE5, 0x01FD, 6, flag_z, flag_n, expected) 146 | 147 | 148 | @pytest.mark.parametrize( 149 | ("value", "expected", "flag_z", "flag_n"), [ 150 | (-1, -2, False, True), 151 | (0, -1, False, True), 152 | (1, 0, True, False), 153 | (2, 1, False, False) 154 | ]) 155 | def test_cpu_ins_dec_abx(value: int, expected: int, flag_z: bool, flag_n: bool) -> None: 156 | """ 157 | Decrement Memory, Absolute, X. 158 | 159 | return: None 160 | """ 161 | memory = m6502.Memory() 162 | cpu = m6502.Processor(memory) 163 | cpu.reset() 164 | cpu.reg_x = 1 165 | memory[0xFCE2] = 0xDE 166 | memory[0xFCE3] = 0xFC 167 | memory[0xFCE4] = 0xFA 168 | memory[0xFAFC + cpu.reg_x] = value 169 | cpu.execute(7) 170 | assert ( 171 | cpu.program_counter, 172 | cpu.stack_pointer, 173 | cpu.cycles, 174 | cpu.flag_z, 175 | cpu.flag_n, 176 | memory[0xFAFC + cpu.reg_x], 177 | ) == (0xFCE5, 0x01FD, 7, flag_z, flag_n, expected) 178 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_inc.py: -------------------------------------------------------------------------------- 1 | """ 2 | INC - Increment Memory. 3 | 4 | M,Z,N = M+1 5 | 6 | Adds one to the value held at a specified memory location setting the zero and 7 | negative flags as appropriate. 8 | 9 | +------+-------------------+-------------------------------+ 10 | | Flag | Description | State | 11 | +======+===================+===============================+ 12 | | C | Carry Flag | Not affected | 13 | +------+-------------------+-------------------------------+ 14 | | Z | Zero Flag | Set if result is zero | 15 | +------+-------------------+-------------------------------+ 16 | | I | Interrupt Disable | Not affected | 17 | +------+-------------------+-------------------------------+ 18 | | D | Decimal Mode Flag | Not affected | 19 | +------+-------------------+-------------------------------+ 20 | | B | Break Command | Not affected | 21 | +------+-------------------+-------------------------------+ 22 | | V | Overflow Flag | Not affected | 23 | +------+-------------------+-------------------------------+ 24 | | N | Negative Flag | Set if bit 7 of result is set | 25 | +------+-------------------+-------------------------------+ 26 | 27 | +-----------------+--------+-------+--------+ 28 | | Addressing Mode | Opcode | Bytes | Cycles | 29 | +=================+========+=======+========+ 30 | | Zero Page | 0xE6 | 2 | 5 | 31 | +-----------------+--------+-------+--------+ 32 | | Zero Page, X | 0xF6 | 2 | 6 | 33 | +-----------------+--------+-------+--------+ 34 | | Absolute | 0xEE | 3 | 6 | 35 | +-----------------+--------+-------+--------+ 36 | | Absolute, X | 0xFE | 3 | 7 | 37 | +-----------------+--------+-------+--------+ 38 | 39 | See also: INX, INY 40 | 41 | """ 42 | import pytest 43 | 44 | import m6502 45 | 46 | 47 | @pytest.mark.parametrize( 48 | ("value", "expected", "flag_z", "flag_n"), [ 49 | (-2, -1, False, True), 50 | (-1, 0, True, False), 51 | (0, 1, False, False), 52 | (1, 2, False, False) 53 | ]) 54 | def test_cpu_ins_inc_zp(value: int, expected: int, flag_z: bool, flag_n: bool) -> None: 55 | """ 56 | Increment Memory, Zero Page. 57 | 58 | return: None 59 | """ 60 | memory = m6502.Memory() 61 | cpu = m6502.Processor(memory) 62 | cpu.reset() 63 | memory[0xFCE2] = 0xE6 64 | memory[0xFCE3] = 0xFC 65 | memory[0xFC] = value 66 | cpu.execute(5) 67 | assert ( 68 | cpu.program_counter, 69 | cpu.stack_pointer, 70 | cpu.cycles, 71 | cpu.flag_z, 72 | cpu.flag_n, 73 | memory[0xFC], 74 | ) == (0xFCE4, 0x01FD, 5, flag_z, flag_n, expected) 75 | 76 | 77 | @pytest.mark.parametrize( 78 | ("value", "expected", "flag_z", "flag_n", "reg_x", "memory_location"), [ 79 | (-2, -1, False, True, 0x0F, 0x8F), 80 | (-1, 0, True, False, 0x0F, 0x8F), 81 | (0, 1, False, False, 0x0F, 0x8F), 82 | (1, 2, False, False, 0x0F, 0x8F), 83 | (-2, -1, False, True, 0xFF, 0x7F), 84 | (-1, 0, True, False, 0xFF, 0x7F), 85 | (0, 1, False, False, 0xFF, 0x7F), 86 | (1, 2, False, False, 0xFF, 0x7F) 87 | ]) 88 | def test_cpu_ins_inc_zpx(value: int, expected: int, flag_z: bool, flag_n: bool, reg_x: int, memory_location: int) -> None: 89 | """ 90 | Increment Memory, Zero Page, X. 91 | 92 | The Zero Page address may not exceed beyond 0xFF: 93 | 94 | - 0x80 + 0x0F => 0x8F 95 | - 0x80 + 0xFF => 0x7F (0x017F) 96 | 97 | return: None 98 | """ 99 | memory = m6502.Memory() 100 | cpu = m6502.Processor(memory) 101 | cpu.reset() 102 | cpu.reg_x = reg_x 103 | memory[0xFCE2] = 0xF6 104 | memory[0xFCE3] = 0x80 105 | memory[memory_location] = value 106 | cpu.execute(6) 107 | assert ( 108 | cpu.program_counter, 109 | cpu.stack_pointer, 110 | cpu.cycles, 111 | cpu.flag_z, 112 | cpu.flag_n, 113 | memory[memory_location], 114 | ) == (0xFCE4, 0x01FD, 6, flag_z, flag_n, expected) 115 | 116 | 117 | @pytest.mark.parametrize( 118 | ("value", "expected", "flag_z", "flag_n"), [ 119 | (-2, -1, False, True), 120 | (-1, 0, True, False), 121 | (0, 1, False, False), 122 | (1, 2, False, False) 123 | ]) 124 | def test_cpu_ins_inc_abs(value: int, expected: int, flag_z: bool, flag_n: bool) -> None: 125 | """ 126 | Increment Memory, Absolute. 127 | 128 | return: None 129 | """ 130 | memory = m6502.Memory() 131 | cpu = m6502.Processor(memory) 132 | cpu.reset() 133 | memory[0xFCE2] = 0xEE 134 | memory[0xFCE3] = 0xFC 135 | memory[0xFCE4] = 0xFA 136 | memory[0xFAFC] = value 137 | cpu.execute(6) 138 | assert ( 139 | cpu.program_counter, 140 | cpu.stack_pointer, 141 | cpu.cycles, 142 | cpu.flag_z, 143 | cpu.flag_n, 144 | memory[0xFAFC], 145 | ) == (0xFCE5, 0x01FD, 6, flag_z, flag_n, expected) 146 | 147 | 148 | @pytest.mark.parametrize( 149 | ("value", "expected", "flag_z", "flag_n"), [ 150 | (-2, -1, False, True), 151 | (-1, 0, True, False), 152 | (0, 1, False, False), 153 | (1, 2, False, False) 154 | ]) 155 | def test_cpu_ins_inc_abx(value: int, expected: int, flag_z: bool, flag_n: bool) -> None: 156 | """ 157 | Increment Memory, Absolute, X. 158 | 159 | return: None 160 | """ 161 | memory = m6502.Memory() 162 | cpu = m6502.Processor(memory) 163 | cpu.reset() 164 | cpu.reg_x = 1 165 | memory[0xFCE2] = 0xFE 166 | memory[0xFCE3] = 0xFC 167 | memory[0xFCE4] = 0xFA 168 | memory[0xFAFC + cpu.reg_x] = value 169 | cpu.execute(7) 170 | assert ( 171 | cpu.program_counter, 172 | cpu.stack_pointer, 173 | cpu.cycles, 174 | cpu.flag_z, 175 | cpu.flag_n, 176 | memory[0xFAFC + cpu.reg_x], 177 | ) == (0xFCE5, 0x01FD, 7, flag_z, flag_n, expected) 178 | -------------------------------------------------------------------------------- /tests/test_cpu.py: -------------------------------------------------------------------------------- 1 | """Verifies that the processor class works as expected.""" 2 | import m6502 3 | 4 | 5 | def test_cpu_reset() -> None: 6 | """ 7 | Verify CPU state after CPU Reset. 8 | 9 | :return: None 10 | """ 11 | memory = m6502.Memory() 12 | cpu = m6502.Processor(memory) 13 | cpu.reset() 14 | assert ( 15 | cpu.program_counter, 16 | cpu.stack_pointer, 17 | cpu.cycles, 18 | cpu.flag_b, 19 | cpu.flag_d, 20 | cpu.flag_i, 21 | ) == (0xFCE2, 0x01FD, 0, True, False, True) 22 | 23 | 24 | def test_cpu_read_byte() -> None: 25 | """ 26 | Verify CPU can read a byte from memory. 27 | 28 | The cost of the read operation is 1 cycle, and the state of the CPU is 29 | not changed. 30 | 31 | :return: None 32 | """ 33 | memory = m6502.Memory() 34 | cpu = m6502.Processor(memory) 35 | cpu.reset() 36 | memory[0x0001] = 0xA5 37 | value = cpu.read_byte(0x0001) 38 | assert ( 39 | cpu.program_counter, 40 | cpu.stack_pointer, 41 | cpu.cycles, 42 | cpu.flag_b, 43 | cpu.flag_d, 44 | cpu.flag_i, 45 | value, 46 | ) == (0xFCE2, 0x01FD, 1, True, False, True, 0xA5) 47 | 48 | 49 | def test_cpu_read_word() -> None: 50 | """ 51 | Verify CPU can read a word from memory. 52 | 53 | The cost of the read operation is 2 cycles, and the state of the CPU is 54 | not changed. 55 | 56 | :return: None 57 | """ 58 | memory = m6502.Memory() 59 | cpu = m6502.Processor(memory) 60 | cpu.reset() 61 | memory[0x0001] = 0xA5 62 | memory[0x0002] = 0x5A 63 | value = cpu.read_word(0x0001) 64 | assert ( 65 | cpu.program_counter, 66 | cpu.stack_pointer, 67 | cpu.cycles, 68 | cpu.flag_b, 69 | cpu.flag_d, 70 | cpu.flag_i, 71 | value, 72 | ) == (0xFCE2, 0x01FD, 2, True, False, True, 0x5AA5) 73 | 74 | 75 | def test_cpu_write_byte() -> None: 76 | """ 77 | Verify CPU can write a byte to memory. 78 | 79 | The cost of the write operation is 1 cycle, and the state of the CPU is 80 | not changed. 81 | 82 | :return: None 83 | """ 84 | memory = m6502.Memory() 85 | cpu = m6502.Processor(memory) 86 | cpu.reset() 87 | cpu.write_byte(0x0001, 0xA5) 88 | assert ( 89 | cpu.program_counter, 90 | cpu.stack_pointer, 91 | cpu.cycles, 92 | cpu.flag_b, 93 | cpu.flag_d, 94 | cpu.flag_i, 95 | memory[0x0001], 96 | ) == (0xFCE2, 0x01FD, 1, True, False, True, 0xA5) 97 | 98 | 99 | def test_cpu_write_word() -> None: 100 | """ 101 | Verify CPU can write a byte to memory. 102 | 103 | The cost of the write operation is 1 cycle, and the state of the CPU is 104 | not changed. 105 | 106 | :return: None 107 | """ 108 | memory = m6502.Memory() 109 | cpu = m6502.Processor(memory) 110 | cpu.reset() 111 | cpu.write_word(0x0001, 0x5AA5) 112 | assert ( 113 | cpu.program_counter, 114 | cpu.stack_pointer, 115 | cpu.cycles, 116 | cpu.flag_b, 117 | cpu.flag_d, 118 | cpu.flag_i, 119 | memory[0x0001], 120 | memory[0x0002], 121 | ) == (0xFCE2, 0x01FD, 2, True, False, True, 0xA5, 0x5A) 122 | 123 | 124 | def test_cpu_read_write_byte() -> None: 125 | """ 126 | Verify CPU can read and write a byte from memory. 127 | 128 | The cost of the read operation is 1 cycle, and the state of the CPU is 129 | not changed. 130 | 131 | :return: None 132 | """ 133 | memory = m6502.Memory() 134 | cpu = m6502.Processor(memory) 135 | cpu.reset() 136 | cpu.write_byte(0x0001, 0xA5) 137 | value = cpu.read_byte(0x0001) 138 | assert ( 139 | cpu.program_counter, 140 | cpu.stack_pointer, 141 | cpu.cycles, 142 | cpu.flag_b, 143 | cpu.flag_d, 144 | cpu.flag_i, 145 | value, 146 | ) == (0xFCE2, 0x01FD, 2, True, False, True, 0xA5) 147 | 148 | 149 | def test_cpu_read_write_word() -> None: 150 | """ 151 | Verify CPU can read and write a byte from memory. 152 | 153 | The cost of the read operation is 1 cycle, and the state of the CPU is 154 | not changed. 155 | 156 | :return: None 157 | """ 158 | memory = m6502.Memory() 159 | cpu = m6502.Processor(memory) 160 | cpu.reset() 161 | cpu.write_word(0x0001, 0x5AA5) 162 | value = cpu.read_word(0x0001) 163 | assert ( 164 | cpu.program_counter, 165 | cpu.stack_pointer, 166 | cpu.cycles, 167 | cpu.flag_b, 168 | cpu.flag_d, 169 | cpu.flag_i, 170 | value, 171 | ) == (0xFCE2, 0x01FD, 4, True, False, True, 0x5AA5) 172 | 173 | 174 | def test_cpu_fetch_byte() -> None: 175 | """ 176 | Verify CPU can fetch a byte from memory. 177 | 178 | The cost of the fetch operation is 1 cycle, and increases the program 179 | counter by 1. The state of the CPU is not changed further. 180 | 181 | :return: None 182 | """ 183 | memory = m6502.Memory() 184 | cpu = m6502.Processor(memory) 185 | cpu.reset() 186 | memory[0xFCE2] = 0xA5 187 | value = cpu.fetch_byte() 188 | assert ( 189 | cpu.program_counter, 190 | cpu.stack_pointer, 191 | cpu.cycles, 192 | cpu.flag_b, 193 | cpu.flag_d, 194 | cpu.flag_i, 195 | value, 196 | ) == (0xFCE3, 0x01FD, 1, True, False, True, 0xA5) 197 | 198 | 199 | def test_cpu_fetch_word() -> None: 200 | """ 201 | Verify CPU can fetch a word from memory. 202 | 203 | The cost of the fetch operation is 2 cycle, and increases the program 204 | counter by 2. The state of the CPU is not changed further. 205 | 206 | :return: None 207 | """ 208 | memory = m6502.Memory() 209 | cpu = m6502.Processor(memory) 210 | cpu.reset() 211 | memory[0xFCE2] = 0xA5 212 | memory[0xFCE3] = 0x5A 213 | value = cpu.fetch_word() 214 | assert ( 215 | cpu.program_counter, 216 | cpu.stack_pointer, 217 | cpu.cycles, 218 | cpu.flag_b, 219 | cpu.flag_d, 220 | cpu.flag_i, 221 | value, 222 | ) == (0xFCE4, 0x01FD, 2, True, False, True, 0x5AA5) 223 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_sta.py: -------------------------------------------------------------------------------- 1 | """ 2 | STA - Store Accumulator. 3 | 4 | M = A 5 | 6 | Stores the contents of the accumulator into memory. 7 | 8 | +------+-------------------+--------------------------+ 9 | | Flag | Description | State | 10 | +======+===================+==========================+ 11 | | C | Carry Flag | Not affected | 12 | +------+-------------------+--------------------------+ 13 | | Z | Zero Flag | Not affected | 14 | +------+-------------------+--------------------------+ 15 | | I | Interrupt Disable | Not affected | 16 | +------+-------------------+--------------------------+ 17 | | D | Decimal Mode Flag | Not affected | 18 | +------+-------------------+--------------------------+ 19 | | B | Break Command | Not affected | 20 | +------+-------------------+--------------------------+ 21 | | V | Overflow Flag | Not affected | 22 | +------+-------------------+--------------------------+ 23 | | N | Negative Flag | Not affected | 24 | +------+-------------------+--------------------------+ 25 | 26 | +-----------------+--------+-------+--------------------------+ 27 | | Addressing Mode | Opcode | Bytes | Cycles | 28 | +=================+========+=======+==========================+ 29 | | Zero Page | 0x85 | 2 | 3 | 30 | +-----------------+--------+-------+--------------------------+ 31 | | Zero Page, X | 0x95 | 2 | 4 | 32 | +-----------------+--------+-------+--------------------------+ 33 | | Absolute | 0x8D | 3 | 4 | 34 | +-----------------+--------+-------+--------------------------+ 35 | | Absolute, X | 0x9D | 3 | 5 | 36 | +-----------------+--------+-------+--------------------------+ 37 | | Absolute, Y | 0x99 | 3 | 5 | 38 | +-----------------+--------+-------+--------------------------+ 39 | | (Indirect, X) | 0x81 | 2 | 6 | 40 | +-----------------+--------+-------+--------------------------+ 41 | | (Indirect), Y | 0x91 | 2 | 6 | 42 | +-----------------+--------+-------+--------------------------+ 43 | 44 | See also: STX, STY 45 | """ 46 | import pytest 47 | 48 | import m6502 49 | 50 | 51 | def test_cpu_ins_sta_zp() -> None: 52 | """ 53 | Store Accumulator, Zero Page. 54 | 55 | return: None 56 | """ 57 | memory = m6502.Memory() 58 | cpu = m6502.Processor(memory) 59 | cpu.reset() 60 | cpu.reg_a = 0xF0 61 | memory[0xFCE2] = 0x85 62 | memory[0xFCE3] = 0xFC 63 | memory[0xFC] = 0 64 | cpu.execute(3) 65 | assert ( 66 | cpu.program_counter, 67 | cpu.stack_pointer, 68 | cpu.cycles, 69 | memory[0xFC], 70 | ) == (0xFCE4, 0x01FD, 3, 0xF0) 71 | 72 | 73 | @pytest.mark.parametrize( 74 | ("reg_x", "memory_location"), [ 75 | (0x0F, 0x8F), 76 | (0xFF, 0x7F), 77 | ]) 78 | def test_cpu_ins_sta_zpx(reg_x: int, memory_location: int) -> None: 79 | """ 80 | Store Accumulator, Zero Page, X. 81 | 82 | return: None 83 | """ 84 | memory = m6502.Memory() 85 | cpu = m6502.Processor(memory) 86 | cpu.reset() 87 | cpu.reg_a = 0xF0 88 | cpu.reg_x = reg_x 89 | memory[0xFCE2] = 0x95 90 | memory[0xFCE3] = 0x80 91 | memory[memory_location] = 0x00 92 | cpu.execute(4) 93 | assert ( 94 | cpu.program_counter, 95 | cpu.stack_pointer, 96 | cpu.cycles, 97 | memory[memory_location], 98 | ) == (0xFCE4, 0x01FD, 4, 0xF0) 99 | 100 | 101 | def test_cpu_ins_sta_abs() -> None: 102 | """ 103 | Store Accumulator, Absolute. 104 | 105 | return: None 106 | """ 107 | memory = m6502.Memory() 108 | cpu = m6502.Processor(memory) 109 | cpu.reset() 110 | cpu.reg_a = 0xF0 111 | memory[0xFCE2] = 0x8D 112 | memory[0xFCE3] = 0xFA 113 | memory[0xFCE4] = 0xFA 114 | memory[0xFAFA] = 0x00 115 | cpu.execute(4) 116 | assert ( 117 | cpu.program_counter, 118 | cpu.stack_pointer, 119 | cpu.cycles, 120 | cpu.reg_a, 121 | ) == (0xFCE5, 0x01FD, 4, 0xF0) 122 | 123 | 124 | def test_cpu_ins_sta_abx() -> None: 125 | """ 126 | Store Accumulator, Absolute, X. 127 | 128 | return: None 129 | """ 130 | memory = m6502.Memory() 131 | cpu = m6502.Processor(memory) 132 | cpu.reset() 133 | cpu.reg_a = 0xF0 134 | cpu.reg_x = 0x01 135 | memory[0xFCE2] = 0x9D 136 | memory[0xFCE3] = 0xFA 137 | memory[0xFCE4] = 0xFA 138 | memory[0xFAFA + cpu.reg_x] = 0x00 139 | cpu.execute(5) 140 | assert ( 141 | cpu.program_counter, 142 | cpu.stack_pointer, 143 | cpu.cycles, 144 | cpu.reg_a, 145 | ) == (0xFCE5, 0x01FD, 5, 0xF0) 146 | 147 | 148 | def test_cpu_ins_sta_aby() -> None: 149 | """ 150 | Store Accumulator, Absolute, Y. 151 | 152 | return: None 153 | """ 154 | memory = m6502.Memory() 155 | cpu = m6502.Processor(memory) 156 | cpu.reset() 157 | cpu.reg_a = 0xF0 158 | cpu.reg_y = 0x01 159 | memory[0xFCE2] = 0x99 160 | memory[0xFCE3] = 0xFA 161 | memory[0xFCE4] = 0xFA 162 | memory[0xFAFA + cpu.reg_y] = 0x00 163 | cpu.execute(5) 164 | assert ( 165 | cpu.program_counter, 166 | cpu.stack_pointer, 167 | cpu.cycles, 168 | cpu.reg_a, 169 | ) == (0xFCE5, 0x01FD, 5, 0xF0) 170 | 171 | 172 | @pytest.mark.parametrize( 173 | ("reg_x", "mem_low", "mem_high"), [ 174 | (0x04, 0x0084, 0x0085), 175 | (0xFF, 0x007F, 0x0080), 176 | ]) 177 | def test_cpu_ins_sta_inx(reg_x: int, mem_low: int, mem_high: int) -> None: 178 | """ 179 | Store Accumulator, Indexed Indirect. 180 | 181 | return: None 182 | """ 183 | memory = m6502.Memory() 184 | cpu = m6502.Processor(memory) 185 | cpu.reset() 186 | cpu.reg_a = 0xF0 187 | cpu.reg_x = reg_x 188 | memory[0xFCE2] = 0x81 189 | memory[0xFCE3] = 0x80 190 | memory[mem_low] = 0x74 191 | memory[mem_high] = 0x20 192 | memory[0x2074] = 0x00 193 | cpu.execute(6) 194 | assert ( 195 | cpu.program_counter, 196 | cpu.stack_pointer, 197 | cpu.cycles, 198 | cpu.reg_a, 199 | ) == (0xFCE4, 0x01FD, 6, 0xF0) 200 | 201 | 202 | def test_cpu_ins_sta_iny() -> None: 203 | """ 204 | Load Accumulator, Indirect Indexed. 205 | 206 | TODO: This test doesn't test the page crossing. 207 | 208 | return: None 209 | """ 210 | memory = m6502.Memory() 211 | cpu = m6502.Processor(memory) 212 | cpu.reset() 213 | cpu.reg_a = 0xF0 214 | cpu.reg_y = 0x10 215 | memory[0xFCE2] = 0x91 216 | memory[0xFCE3] = 0x86 217 | memory[0x0086] = 0x28 218 | memory[0x0087] = 0x40 219 | memory[0x4038] = 0x00 220 | cpu.execute(6) 221 | assert ( 222 | cpu.program_counter, 223 | cpu.stack_pointer, 224 | cpu.cycles, 225 | cpu.reg_a, 226 | ) == (0xFCE4, 0x01FD, 6, 0xF0) 227 | -------------------------------------------------------------------------------- /tests/test_cpu_ins_lda.py: -------------------------------------------------------------------------------- 1 | """ 2 | LDA - Load Accumulator. 3 | 4 | A,Z,N = M 5 | 6 | Loads a byte of memory into the accumulator setting the zero and negative 7 | flags as appropriate. 8 | 9 | +------+-------------------+--------------------------+ 10 | | Flag | Description | State | 11 | +======+===================+==========================+ 12 | | C | Carry Flag | Not affected | 13 | +------+-------------------+--------------------------+ 14 | | Z | Zero Flag | Set is A = 0 | 15 | +------+-------------------+--------------------------+ 16 | | I | Interrupt Disable | Not affected | 17 | +------+-------------------+--------------------------+ 18 | | D | Decimal Mode Flag | Not affected | 19 | +------+-------------------+--------------------------+ 20 | | B | Break Command | Not affected | 21 | +------+-------------------+--------------------------+ 22 | | V | Overflow Flag | Not affected | 23 | +------+-------------------+--------------------------+ 24 | | N | Negative Flag | Set if bit 7 of A is set | 25 | +------+-------------------+--------------------------+ 26 | 27 | +-----------------+--------+-------+--------------------------+ 28 | | Addressing Mode | Opcode | Bytes | Cycles | 29 | +=================+========+=======+==========================+ 30 | | Immediate | 0xA9 | 2 | 2 | 31 | +-----------------+--------+-------+--------------------------+ 32 | | Zero Page | 0xA5 | 2 | 3 | 33 | +-----------------+--------+-------+--------------------------+ 34 | | Zero Page, X | 0xB5 | 2 | 5 | 35 | +-----------------+--------+-------+--------------------------+ 36 | | Absolute | 0xAD | 3 | 4 | 37 | +-----------------+--------+-------+--------------------------+ 38 | | Absolute, X | 0xBD | 3 | 4 (+1 if page crossed) | 39 | +-----------------+--------+-------+--------------------------+ 40 | | Absolute, Y | 0xB9 | 3 | 4 (+1 if page crossed) | 41 | +-----------------+--------+-------+--------------------------+ 42 | | (Indirect, X) | 0xA1 | 2 | 6 | 43 | +-----------------+--------+-------+--------------------------+ 44 | | (Indirect), Y | 0xB1 | 2 | 5 (+1 if page crossed) | 45 | +-----------------+--------+-------+--------------------------+ 46 | 47 | See also: LDX, LDY 48 | """ 49 | import pytest 50 | 51 | import m6502 52 | 53 | 54 | def test_cpu_ins_lda_imm() -> None: 55 | """ 56 | Load Accumulator, Immediate. 57 | 58 | return: None 59 | """ 60 | memory = m6502.Memory() 61 | cpu = m6502.Processor(memory) 62 | cpu.reset() 63 | cpu.reg_a = 0x00 64 | memory[0xFCE2] = 0xA9 65 | memory[0xFCE3] = 0xF0 66 | cpu.execute(2) 67 | assert ( 68 | cpu.program_counter, 69 | cpu.stack_pointer, 70 | cpu.cycles, 71 | cpu.reg_a, 72 | ) == (0xFCE4, 0x01FD, 2, 0xF0) 73 | 74 | 75 | def test_cpu_ins_lda_zp() -> None: 76 | """ 77 | Load Accumulator, Zero Page. 78 | 79 | return: None 80 | """ 81 | memory = m6502.Memory() 82 | cpu = m6502.Processor(memory) 83 | cpu.reset() 84 | cpu.reg_a = 0x00 85 | memory[0xFCE2] = 0xA5 86 | memory[0xFCE3] = 0x80 87 | memory[0x80] = 0xF0 88 | cpu.execute(3) 89 | assert ( 90 | cpu.program_counter, 91 | cpu.stack_pointer, 92 | cpu.cycles, 93 | cpu.reg_a, 94 | ) == (0xFCE4, 0x01FD, 3, 0xF0) 95 | 96 | 97 | @pytest.mark.parametrize( 98 | ("reg_x", "memory_location"), [ 99 | (0x0F, 0x8F), 100 | (0xFF, 0x7F), 101 | ]) 102 | def test_cpu_ins_lda_zpx(reg_x: int, memory_location: int) -> None: 103 | """ 104 | Load Accumulator, Zero Page, X. 105 | 106 | The Zero Page address may not exceed beyond 0xFF: 107 | 108 | - 0x80 + 0x0F => 0x8F 109 | - 0x80 + 0xFF => 0x7F (0x017F) 110 | 111 | return: None 112 | """ 113 | memory = m6502.Memory() 114 | cpu = m6502.Processor(memory) 115 | cpu.reset() 116 | cpu.reg_a = 0x00 117 | cpu.reg_x = reg_x 118 | memory[0xFCE2] = 0xB5 119 | memory[0xFCE3] = 0x80 120 | memory[memory_location] = 0xF0 121 | cpu.execute(4) 122 | assert ( 123 | cpu.program_counter, 124 | cpu.stack_pointer, 125 | cpu.cycles, 126 | cpu.reg_a, 127 | ) == (0xFCE4, 0x01FD, 4, 0xF0) 128 | 129 | 130 | def test_cpu_ins_lda_abs() -> None: 131 | """ 132 | Load Accumulator, Absolute. 133 | 134 | return: None 135 | """ 136 | memory = m6502.Memory() 137 | cpu = m6502.Processor(memory) 138 | cpu.reset() 139 | cpu.reg_a = 0x00 140 | memory[0xFCE2] = 0xAD 141 | memory[0xFCE3] = 0xFA 142 | memory[0xFCE4] = 0xFA 143 | memory[0xFAFA] = 0xF0 144 | cpu.execute(4) 145 | assert ( 146 | cpu.program_counter, 147 | cpu.stack_pointer, 148 | cpu.cycles, 149 | cpu.reg_a, 150 | ) == (0xFCE5, 0x01FD, 4, 0xF0) 151 | 152 | 153 | def test_cpu_ins_lda_abx() -> None: 154 | """ 155 | Load Accumulator, Absolute, X. 156 | 157 | TODO: This test doesn't test the page crossing. 158 | 159 | return: None 160 | """ 161 | memory = m6502.Memory() 162 | cpu = m6502.Processor(memory) 163 | cpu.reset() 164 | cpu.reg_a = 0x00 165 | cpu.reg_x = 0x01 166 | memory[0xFCE2] = 0xBD 167 | memory[0xFCE3] = 0xFA 168 | memory[0xFCE4] = 0xFA 169 | memory[0xFAFA + cpu.reg_x] = 0xF0 170 | cpu.execute(4) 171 | assert ( 172 | cpu.program_counter, 173 | cpu.stack_pointer, 174 | cpu.cycles, 175 | cpu.reg_a, 176 | ) == (0xFCE5, 0x01FD, 4, 0xF0) 177 | 178 | 179 | def test_cpu_ins_lda_aby() -> None: 180 | """ 181 | Load Accumulator, Absolute, Y. 182 | 183 | TODO: This test doesn't test the page crossing. 184 | 185 | return: None 186 | """ 187 | memory = m6502.Memory() 188 | cpu = m6502.Processor(memory) 189 | cpu.reset() 190 | cpu.reg_a = 0x00 191 | cpu.reg_y = 1 192 | memory[0xFCE2] = 0xB9 193 | memory[0xFCE3] = 0xFA 194 | memory[0xFCE4] = 0xFA 195 | memory[0xFAFA + cpu.reg_y] = 0xF0 196 | cpu.execute(4) 197 | assert ( 198 | cpu.program_counter, 199 | cpu.stack_pointer, 200 | cpu.cycles, 201 | cpu.reg_a, 202 | ) == (0xFCE5, 0x01FD, 4, 0xF0) 203 | 204 | 205 | @pytest.mark.parametrize( 206 | ("reg_x", "mem_low", "mem_high"), [ 207 | (0x04, 0x0084, 0x0085), 208 | (0xFF, 0x007F, 0x0080), 209 | ]) 210 | def test_cpu_ins_lda_inx(reg_x: int, mem_low: int, mem_high: int) -> None: 211 | """ 212 | Load Accumulator, Indexed Indirect. 213 | 214 | return: None 215 | """ 216 | memory = m6502.Memory() 217 | cpu = m6502.Processor(memory) 218 | cpu.reset() 219 | cpu.reg_a = 0x00 220 | cpu.reg_x = reg_x 221 | memory[0xFCE2] = 0xA1 222 | memory[0xFCE3] = 0x80 223 | memory[mem_low] = 0x74 224 | memory[mem_high] = 0x20 225 | memory[0x2074] = 0xF0 226 | cpu.execute(6) 227 | assert ( 228 | cpu.program_counter, 229 | cpu.stack_pointer, 230 | cpu.cycles, 231 | cpu.reg_a, 232 | ) == (0xFCE4, 0x01FD, 6, 0xF0) 233 | 234 | 235 | def test_cpu_ins_lda_iny() -> None: 236 | """ 237 | Load Accumulator, Indirect Indexed. 238 | 239 | TODO: This test doesn't test the page crossing. 240 | 241 | return: None 242 | """ 243 | memory = m6502.Memory() 244 | cpu = m6502.Processor(memory) 245 | cpu.reset() 246 | cpu.reg_a = 0x00 247 | cpu.reg_y = 0x10 248 | memory[0xFCE2] = 0xB1 249 | memory[0xFCE3] = 0x86 250 | memory[0x0086] = 0x28 251 | memory[0x0087] = 0x40 252 | memory[0x4038] = 0xF0 253 | cpu.execute(5) 254 | assert ( 255 | cpu.program_counter, 256 | cpu.stack_pointer, 257 | cpu.cycles, 258 | cpu.reg_a, 259 | ) == (0xFCE4, 0x01FD, 5, 0xF0) 260 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # CS: Writing a 6502 emulator in Python 2 | 3 | [Python] is a great language to learn because it is easy to learn and safe to write in. It is also available for Windows, Linux and Mac, and it is free. It is also available on [Raspberry Pi][Raspberry Pi] which is targetted for education and testing purposes. This makes computer science easy accessible to everyone. Also for writing a basic emulator to learn how computers work and what they do. The [6502][6502] processor has a very simple design and a small instruction set that makes it easy to learn. 4 | 5 | Learning how processors work also gives the possibility to understand why certain applications are so slow and how to optimize them, but also how to start doing security research by writing a fuzzer to find vulnerabilities. Lets start with the basics and write a simple 6502 emulator before we start with the assembly language. 6 | 7 | The [Introduction](#introduction) chapter is mainly based on the **6502 Instruction Set Guide** by [Andrew John Jacobs aka BitWise](https://github.com/andrew-jacobs) and full credit goes to him. Sadly he passed away in 2021 and I copied his work as a reference as his website is no longer available. 8 | 9 | ## Introduction 10 | 11 | ### The 6502 basic processor? 12 | 13 | ### The Registers 14 | 15 | #### Program Counter 16 | 17 | #### Stack Pointer 18 | 19 | #### Accumulator 20 | 21 | #### Index registers X and Y 22 | 23 | #### Processor Status 24 | 25 | As instructions are executed a set of processor flags are set or clear to record the results of the operation. This flags and some additional control flags are held in a special status register. Each flag has a single bit within the register. 26 | 27 | Instructions exist to test the values of the various bits, to set or clear some of them and to push or pull the entire set to or from the stack. 28 | 29 | * Carry Flag 30 | 31 | The carry flag is set if the last operation caused an overflow from bit 7 of the result or an underflow from bit 0. This condition is set during arithmetic, comparison and during logical shifts. It can be explicitly set using the ['Set Carry Flag' (SEC)](#set-carry-flag) instruction and cleared with ['Clear Carry Flag' (CLC)](#clear-carry-flag). 32 | 33 | * Zero Flag 34 | 35 | The zero flag is set of the result of the last operation as was zero. 36 | 37 | * Interrupt Disable 38 | 39 | The interrupt disable flag is set if the program has executed a ['Set Interrupt Disable' (SEI)](#sei---set-interrupt-disable) instruction. While this flag is set the processor will not respond to interrupts from devices until it is cleared by a ['Clear Interrupt Disable' (CLI)](#cli---clear-interrupt-disable) instruction. 40 | 41 | * Decimal Mode 42 | 43 | While the decimal mode flag is set the processor will obey the rules of Binary Coded Decimal (BCD) arithmetic during addition and subtraction. The flag can be explicitly set using ['Set Decimal Mode' (SED)](#sed---set-decimal-mode) and cleared with ['Clear Decimal Mode' (CLD)](#cld---clear-decimal-mode). 44 | > Note that only two instructions are affected by the D flag: ['Add with Carry' (ADC)](#adc---add-with-carry) and ['Subtract with Carry' (SBC)](#sbc---subtract-with-carry). 45 | 46 | * Break Command 47 | 48 | The break command bit is set when a [BRK](#brk) instruction has been executed and an interrupt has been generated to process it. 49 | 50 | * Overflow Flag 51 | 52 | The overflow flag is set during arithmetic operations if the result has yielded an ivalid 2's complement result (e.g. adding to positive numbers an dending up with a negative result: 64 + 64 => -128) It is determined by looking at the carry between bits 6 and 7 and between bit 7 and the carry flag. 53 | 54 | * Negative Flag 55 | 56 | The negative flag is set if the result of the last operation has bit 7 set to a one. 57 | 58 | ### The Instruction Set 59 | 60 | #### Load/Store Operations 61 | 62 | #### Register Transfer Operations 63 | 64 | #### Stack Operations 65 | 66 | #### Logical Operations 67 | 68 | #### Arithmetic Operations 69 | 70 | #### Increment/Decrement Operations 71 | 72 | #### Bitwise Operations 73 | 74 | #### Jump/Call Operations 75 | 76 | #### Branch Operations 77 | 78 | #### Status Flag Changes 79 | 80 | ##### CLC - Clear Carry Flag 81 | 82 | Set the carry flag to zero. 83 | 84 | | Flag | Description | State | 85 | | :---: | ----------------- | ------------ | 86 | | C | Carry Flag | Set to 0 | 87 | | Z | Zero Flag | Not affected | 88 | | I | Interrupt Disable | Not affected | 89 | | D | Decimal Mode Flag | Not affected | 90 | | B | Break Command | Not affected | 91 | | V | Overflow Flag | Not affected | 92 | | N | Negative Flag | Not affected | 93 | 94 | | Addressing Mode | Opcode | Bytes | Cycles | 95 | | --------------- | :----: | :---: | :----: | 96 | | Implied | 0x18 | 1 | 2 | 97 | 98 | See also [SEC](#sec---set-carry-flag). 99 | 100 | ##### CLD - Clear Decimal Mode 101 | 102 | Sets the decimal mode flag to zero. 103 | 104 | | Flag | Description | State | 105 | | :---: | ----------------- | ------------ | 106 | | C | Carry Flag | Not affected | 107 | | Z | Zero Flag | Not affected | 108 | | I | Interrupt Disable | Not affected | 109 | | D | Decimal Mode Flag | Set to 0 | 110 | | B | Break Command | Not affected | 111 | | V | Overflow Flag | Not affected | 112 | | N | Negative Flag | Not affected | 113 | 114 | | Addressing Mode | Opcode | Bytes | Cycles | 115 | | --------------- | :----: | :---: | :----: | 116 | | Implied | 0xD8 | 1 | 2 | 117 | 118 | > The state of the decimal flag is uncertain when the CPU is powered up and it 119 | > is not reset when an interrupt is generated. In both cases you should include 120 | > an explicit CLD to ensure that the flag is cleared before performing addition 121 | > or subtraction. 122 | 123 | See also [SED](#sed---set-decimal-mode). 124 | 125 | ##### CLI - Clear Interrupt Disable 126 | 127 | Clears the interrupt disable flag allowing normal interrupt requests to be 128 | serviced. 129 | 130 | | Flag | Description | State | 131 | | :---: | ----------------- | ------------ | 132 | | C | Carry Flag | Not affected | 133 | | Z | Zero Flag | Not affected | 134 | | I | Interrupt Disable | Set to 0 | 135 | | D | Decimal Mode Flag | Not affected | 136 | | B | Break Command | Not affected | 137 | | V | Overflow Flag | Not affected | 138 | | N | Negative Flag | Not affected | 139 | 140 | | Addressing Mode | Opcode | Bytes | Cycles | 141 | | --------------- | :----: | :---: | :----: | 142 | | Implied | 0x58 | 1 | 2 | 143 | 144 | See also [SEI](#sei---set-interrupt-disable). 145 | 146 | ##### CLV - Clear Overflow Flag 147 | 148 | Clears the overflow flag. 149 | 150 | | Flag | Description | State | 151 | | :---: | ----------------- | ------------ | 152 | | C | Carry Flag | Not affected | 153 | | Z | Zero Flag | Not affected | 154 | | I | Interrupt Disable | Not affected | 155 | | D | Decimal Mode Flag | Not affected | 156 | | B | Break Command | Not affected | 157 | | V | Overflow Flag | Set to 0 | 158 | | N | Negative Flag | Not affected | 159 | 160 | | Addressing Mode | Opcode | Bytes | Cycles | 161 | | --------------- | :----: | :---: | :----: | 162 | | Implied | 0xB8 | 1 | 2 | 163 | 164 | ##### SEC - Set Carry Flag 165 | 166 | Set the carry flag to one. 167 | 168 | | Flag | Description | State | 169 | | :---: | ----------------- | ------------ | 170 | | C | Carry Flag | Set to 1 | 171 | | Z | Zero Flag | Not affected | 172 | | I | Interrupt Disable | Not affected | 173 | | D | Decimal Mode Flag | Not affected | 174 | | B | Break Command | Not affected | 175 | | V | Overflow Flag | Not affected | 176 | | N | Negative Flag | Not affected | 177 | 178 | | Addressing Mode | Opcode | Bytes | Cycles | 179 | | --------------- | :----: | :---: | :----: | 180 | | Implied | 0x38 | 1 | 2 | 181 | 182 | See also [CLC](#clc---clear-carry-flag). 183 | 184 | ##### SED - Set Decimal Mode 185 | 186 | Sets the decimal mode flag to one. 187 | 188 | | Flag | Description | State | 189 | | :---: | ----------------- | ------------ | 190 | | C | Carry Flag | Not affected | 191 | | Z | Zero Flag | Not affected | 192 | | I | Interrupt Disable | Not affected | 193 | | D | Decimal Mode Flag | Set to 1 | 194 | | B | Break Command | Not affected | 195 | | V | Overflow Flag | Not affected | 196 | | N | Negative Flag | Not affected | 197 | 198 | | Addressing Mode | Opcode | Bytes | Cycles | 199 | | --------------- | :----: | :---: | :----: | 200 | | Implied | 0xF8 | 1 | 2 | 201 | 202 | See also [CLD](#cld---clear-decimal-mode). 203 | 204 | ##### SEI - Set Interrupt Disable 205 | 206 | Sets the interrupt disable flag to zero. 207 | 208 | | Flag | Description | State | 209 | | :---: | ----------------- | ------------ | 210 | | C | Carry Flag | Not affected | 211 | | Z | Zero Flag | Not affected | 212 | | I | Interrupt Disable | Set to 1 | 213 | | D | Decimal Mode Flag | Not affected | 214 | | B | Break Command | Not affected | 215 | | V | Overflow Flag | Not affected | 216 | | N | Negative Flag | Not affected | 217 | 218 | | Addressing Mode | Opcode | Bytes | Cycles | 219 | | --------------- | :----: | :---: | :----: | 220 | | Implied | 0x78 | 1 | 2 | 221 | 222 | See also [CLI](#cli---clear-interrupt-disable). 223 | 224 | #### System Functions 225 | 226 | ##### NOP - No Operation 227 | 228 | The NOP instruction causes no changes to the processor other than the normal 229 | incrementing of the program counter to the next instruction. 230 | 231 | Processor Status after use: 232 | 233 | | Flag | Description | State | 234 | | :---: | ----------------- | ------------ | 235 | | C | Carry Flag | Not affected | 236 | | Z | Zero Flag | Not affected | 237 | | I | Interrupt Disable | Not affected | 238 | | D | Decimal Mode Flag | Not affected | 239 | | B | Break Command | Not affected | 240 | | V | Overflow Flag | Not affected | 241 | | N | Negative Flag | Not affected | 242 | 243 | | Addressing Mode | Opcode | Bytes | Cycles | 244 | | --------------- | :----: | :---: | :----: | 245 | | Implied | 0xEA | 1 | 2 | 246 | 247 | ### Addressing Modes 248 | 249 | #### Implicit Addressing Mode 250 | 251 | #### Accumulator Addressing Mode 252 | 253 | #### Immediate Addressing Mode 254 | 255 | #### Zero Page Addressing Mode 256 | 257 | #### Zero Page,X or Zero Page,Y Addressing Mode 258 | 259 | #### Relative Addressing Mode 260 | 261 | #### Absolute Addressing Mode 262 | 263 | #### Absolute,X or Absolute,Y Addressing Mode 264 | 265 | #### Indirect Addressing Mode 266 | 267 | #### Indirect Indexed Addressing Mode 268 | 269 | ### The memory model 270 | 271 | ## The basic structure of a 6502 processor 272 | 273 | ## Implementing the Set and Clear instructions 274 | 275 | [6502]: https://en.m.wikipedia.org/wiki/6502 276 | [Python]: https://www.python.org/ 277 | [Raspberry Pi]: https://www.raspberrypi.org/ 278 | -------------------------------------------------------------------------------- /m6502/processor.py: -------------------------------------------------------------------------------- 1 | """Emulation of the MOT-6502 Processor.""" 2 | import sys 3 | 4 | import m6502 5 | 6 | 7 | class Processor: 8 | """MOT-6502 Processor.""" 9 | 10 | ADDRESSING = [ 11 | # 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | 12 | "imp", "inx", "imp", "inx", "zp", "zp", "zp", "zp", "imp", "imm", "acc", "imm", "abs", "abs", "abs", "abs", # 0 13 | "rel", "iny", "imp", "iny", "zpx", "zpx", "zpx", "zpx", "imp", "aby", "imp", "aby", "abx", "abx", "abx", "abx", # 1 14 | "abs", "inx", "imp", "inx", "zp", "zp", "zp", "zp", "imp", "imm", "acc", "imm", "abs", "abs", "abs", "abs", # 2 15 | "rel", "iny", "imp", "iny", "zpx", "zpx", "zpx", "zpx", "imp", "aby", "imp", "aby", "abx", "abx", "abx", "abx", # 3 16 | "imp", "inx", "imp", "inx", "zp", "zp", "zp", "zp", "imp", "imm", "acc", "imm", "abs", "abs", "abs", "abs", # 4 17 | "rel", "iny", "imp", "iny", "zpx", "zpx", "zpx", "zpx", "imp", "aby", "imp", "aby", "abx", "abx", "abx", "abx", # 5 18 | "imp", "inx", "imp", "inx", "zp", "zp", "zp", "zp", "imp", "imm", "acc", "imm", "ind", "abs", "abs", "abs", # 6 19 | "rel", "iny", "imp", "iny", "zpx", "zpx", "zpx", "zpx", "imp", "aby", "imp", "aby", "abx", "abx", "abx", "abx", # 7 20 | "imm", "inx", "imm", "inx", "zp", "zp", "zp", "zp", "imp", "imm", "imp", "imm", "abs", "abs", "abs", "abs", # 8 21 | "rel", "iny", "imp", "iny", "zpx", "zpx", "zpy", "zpy", "imp", "aby", "imp", "aby", "abx", "abx", "aby", "aby", # 9 22 | "imm", "inx", "imm", "inx", "zp", "zp", "zp", "zp", "imp", "imm", "imp", "imm", "abs", "abs", "abs", "abs", # A 23 | "rel", "iny", "imp", "iny", "zpx", "zpx", "zpy", "zpy", "imp", "aby", "imp", "aby", "abx", "abx", "aby", "aby", # B 24 | "imm", "inx", "imm", "inx", "zp", "zp", "zp", "zp", "imp", "imm", "imp", "imm", "abs", "abs", "abs", "abs", # C 25 | "rel", "iny", "imp", "iny", "zpx", "zpx", "zpx", "zpx", "imp", "aby", "imp", "aby", "abx", "abx", "abx", "abx", # D 26 | "imm", "inx", "imm", "inx", "zp", "zp", "zp", "zp", "imp", "imm", "imp", "imm", "abs", "abs", "abs", "abs", # E 27 | "rel", "iny", "imp", "iny", "zpx", "zpx", "zpx", "zpx", "imp", "aby", "imp", "aby", "abx", "abx", "abx", "abx", # F 28 | ] 29 | 30 | OPCODES = [ 31 | # 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F | 32 | "brk", "ora", "nop", "slo", "nop", "ora", "asl", "slo", "php", "ora", "asl", "nop", "nop", "ora", "asl", "slo", # 0 33 | "bpl", "ora", "nop", "slo", "nop", "ora", "asl", "slo", "clc", "ora", "nop", "slo", "nop", "ora", "asl", "slo", # 1 34 | "jsr", "and", "nop", "rla", "bit", "and", "rol", "rla", "plp", "and", "rol", "nop", "bit", "and", "rol", "rla", # 2 35 | "bmi", "and", "nop", "rla", "nop", "and", "rol", "rla", "sec", "and", "nop", "rla", "nop", "and", "rol", "rla", # 3 36 | "rti", "eor", "nop", "sre", "nop", "eor", "lsr", "sre", "pha", "eor", "lsr", "nop", "jmp", "eor", "lsr", "sre", # 4 37 | "bvc", "eor", "nop", "sre", "nop", "eor", "lsr", "sre", "cli", "eor", "nop", "sre", "nop", "eor", "lsr", "sre", # 5 38 | "rts", "adc", "nop", "rra", "nop", "adc", "ror", "rra", "pla", "adc", "ror", "nop", "jmp", "adc", "ror", "rra", # 6 39 | "bvs", "adc", "nop", "rra", "nop", "adc", "ror", "rra", "sei", "adc", "nop", "rra", "nop", "adc", "ror", "rra", # 7 40 | "nop", "sta", "nop", "sax", "sty", "sta", "stx", "sax", "dey", "nop", "txa", "nop", "sty", "sta", "stx", "sax", # 8 41 | "bcc", "sta", "nop", "nop", "sty", "sta", "stx", "sax", "tya", "sta", "txs", "nop", "nop", "sta", "nop", "nop", # 9 42 | "ldy", "lda", "ldx", "lax", "ldy", "lda", "ldx", "lax", "tay", "lda", "tax", "nop", "ldy", "lda", "ldx", "lax", # A 43 | "bcs", "lda", "nop", "lax", "ldy", "lda", "ldx", "lax", "clv", "lda", "tsx", "lax", "ldy", "lda", "ldx", "lax", # B 44 | "cpy", "cmp", "nop", "dcp", "cpy", "cmp", "dec", "dcp", "iny", "cmp", "dex", "nop", "cpy", "cmp", "dec", "dcp", # C 45 | "bne", "cmp", "nop", "dcp", "nop", "cmp", "dec", "dcp", "cld", "cmp", "nop", "dcp", "nop", "cmp", "dec", "dcp", # D 46 | "cpx", "sbc", "nop", "isb", "cpx", "sbc", "inc", "isb", "inx", "sbc", "nop", "sbc", "cpx", "sbc", "inc", "isb", # E 47 | "beq", "sbc", "nop", "isb", "nop", "sbc", "inc", "isb", "sed", "sbc", "nop", "isb", "nop", "sbc", "inc", "isb", # F 48 | ] 49 | 50 | def __init__(self: object, memory: m6502.memory) -> None: 51 | """ 52 | Initialize the processor. 53 | 54 | :param memory: The memory to use 55 | :return: None 56 | """ 57 | self.memory = memory 58 | self.reg_a = 0 # Accumlator A 59 | self.reg_y = 0 # Incex Register Y 60 | self.reg_x = 0 # Incex Register X 61 | 62 | self.program_counter = 0 # Program Counter PC 63 | self.stack_pointer = 0 # Stack Pointer S 64 | self.cycles = 0 # Cycles used 65 | 66 | self.flag_c = True # Status flag - Carry Flag 67 | self.flag_z = True # Status flag - Zero Flag 68 | self.flag_i = True # Status flag - Interrupt Disable 69 | self.flag_d = True # Status flag - Decimal Mode Flag 70 | self.flag_b = True # Status flag - Break Command 71 | self.flag_v = True # Status flag - Overflow Flag 72 | self.flag_n = True # Status flag - Negative Flag 73 | 74 | def reset(self: object) -> None: 75 | """ 76 | Reset processor to initial state. 77 | 78 | :return: None 79 | """ 80 | self.program_counter = 0xFCE2 # Hardcoded start vector post-reset 81 | self.stack_pointer = 0x01FD # Hardcoded stack pointer post-reset 82 | self.cycles = 0 83 | 84 | self.flag_i = True 85 | self.flag_d = False 86 | self.flag_b = True 87 | 88 | def fetch_byte(self: object) -> int: 89 | """ 90 | Fetch a byte from memory. 91 | 92 | :param address: The address to read from 93 | :return: int 94 | """ 95 | data = self.read_byte(self.program_counter) 96 | self.program_counter += 1 97 | return data 98 | 99 | def fetch_word(self: object) -> int: 100 | """ 101 | Fetch a word from memory. 102 | 103 | :param address: The address to read from 104 | :return: int 105 | """ 106 | data = self.read_word(self.program_counter) 107 | self.program_counter += 2 108 | return data 109 | 110 | def read_byte(self: object, address: int) -> int: 111 | """ 112 | Read a byte from memory. 113 | 114 | :param address: The address to read from 115 | :return: int 116 | """ 117 | data = self.memory[address] 118 | self.cycles += 1 119 | return data 120 | 121 | def read_word(self: object, address: int) -> int: 122 | """ 123 | Read a word from memory. 124 | 125 | :param address: The address to read from 126 | :return: int 127 | """ 128 | if sys.byteorder == "little": 129 | data = self.read_byte(address) | (self.read_byte(address + 1) << 8) 130 | else: 131 | data = (self.read_byte(address) << 8) | self.read_byte(address + 1) 132 | return data 133 | 134 | def write_byte(self: object, address: int, value: int) -> None: 135 | """ 136 | Write a byte to memory. 137 | 138 | :param address: The address to write to 139 | :param value: The value to write 140 | :return: None 141 | """ 142 | self.memory[address] = value 143 | self.cycles += 1 144 | 145 | def write_word(self: object, address: int, value: int) -> None: 146 | """ 147 | Split a word to two bytes and write to memory. 148 | 149 | :param address: The address to write to 150 | :param value: The value to write 151 | :return: None 152 | """ 153 | if sys.byteorder == "little": 154 | self.write_byte(address, value & 0xFF) 155 | self.write_byte(address + 1, (value >> 8) & 0xFF) 156 | else: 157 | self.write_byte(address, (value >> 8) & 0xFF) 158 | self.write_byte(address + 1, value & 0xFF) 159 | 160 | def read_register_a(self: object) -> int: 161 | """ 162 | Read the A register. 163 | 164 | :return: int 165 | """ 166 | self.cycles += 1 167 | return self.reg_a 168 | 169 | def read_register_x(self: object) -> int: 170 | """ 171 | Read the X register. 172 | 173 | :return: int 174 | """ 175 | self.cycles += 1 176 | return self.reg_x 177 | 178 | def read_register_y(self: object) -> int: 179 | """ 180 | Read the Y register. 181 | 182 | :return: int 183 | """ 184 | self.cycles += 1 185 | return self.reg_y 186 | 187 | def push(self: object, data: int) -> None: 188 | """ 189 | Push data to stack. 190 | 191 | :return: None 192 | """ 193 | self.memory[self.stack_pointer] = data 194 | self.stack_pointer -= 1 195 | self.cycles += 1 196 | 197 | def pop(self: object) -> int: 198 | """ 199 | Pop data from stack. 200 | 201 | :return: int 202 | """ 203 | self.stack_pointer += 1 204 | self.cycles += 1 205 | return self.memory[self.stack_pointer - 1] 206 | 207 | def evaluate_flag_n(self: object, data: int) -> None: 208 | """ 209 | Evaluate negative flag. 210 | 211 | :param data: The data to evaluate 212 | :return: None 213 | """ 214 | self.flag_n = (data & 0x80) != 0 215 | 216 | def evaluate_flag_z(self: object, data: int) -> None: 217 | """ 218 | Evaluate the Zero Flag. 219 | 220 | :param data: The data to evaluate 221 | :return: None 222 | """ 223 | if data == 0: 224 | self.flag_z = True 225 | else: 226 | self.flag_z = False 227 | 228 | def execute(self: object, cycles: int = 0) -> None: 229 | """ 230 | Execute code for X amount of cycles. Or until a breakpoint is reached. 231 | 232 | :param cycles: The number of cycles to execute 233 | :return: None 234 | """ 235 | while (self.cycles < cycles) or (cycles == 0): 236 | opcode = self.fetch_byte() 237 | eval("self.ins_" + self.OPCODES[opcode] + "_" + self.ADDRESSING[opcode] + "()") # noqa: PLW0123 238 | 239 | def ins_nop_imp(self: object) -> None: 240 | """ 241 | NOP - No Operation. 242 | 243 | :return: None 244 | """ 245 | self.cycles += 1 246 | 247 | def ins_clc_imp(self: object) -> None: 248 | """ 249 | CLC - Clear Carry Flag. 250 | 251 | :return: None 252 | """ 253 | self.flag_c = False 254 | self.cycles += 1 255 | 256 | def ins_cld_imp(self: object) -> None: 257 | """ 258 | CLD - Clear Decimal Mode. 259 | 260 | :return: None 261 | """ 262 | self.flag_d = False 263 | self.cycles += 1 264 | 265 | def ins_cli_imp(self: object) -> None: 266 | """ 267 | CLI - Clear Interrupt Disable. 268 | 269 | :return: None 270 | """ 271 | self.flag_i = False 272 | self.cycles += 1 273 | 274 | def ins_clv_imp(self: object) -> None: 275 | """ 276 | CLV - Clear Overflow Flag. 277 | 278 | :return: None 279 | """ 280 | self.flag_v = False 281 | self.cycles += 1 282 | 283 | def ins_dec_zp(self: object) -> None: 284 | """ 285 | DEC - Decrement Memory, Zero Page. 286 | 287 | :return: None 288 | """ 289 | address = self.fetch_byte() 290 | self.write_byte(address, self.read_byte(address) - 1) 291 | self.evaluate_flag_n(self.memory[address]) 292 | self.evaluate_flag_z(self.memory[address]) 293 | self.cycles += 1 294 | 295 | def ins_dec_zpx(self: object) -> None: 296 | """ 297 | DEC - Decrement Memory, Zero Page, X. 298 | 299 | :return: None 300 | """ 301 | address = (self.fetch_byte() + self.read_register_x()) & 0xFF 302 | self.write_byte(address, self.read_byte(address) - 1) 303 | self.evaluate_flag_n(self.memory[address]) 304 | self.evaluate_flag_z(self.memory[address]) 305 | self.cycles += 1 306 | 307 | def ins_dec_abs(self: object) -> None: 308 | """ 309 | DEC - Decrement Memory, Absolute. 310 | 311 | :return: None 312 | """ 313 | address = self.fetch_word() 314 | self.write_byte(address, self.read_byte(address) - 1) 315 | self.evaluate_flag_n(self.memory[address]) 316 | self.evaluate_flag_z(self.memory[address]) 317 | self.cycles += 1 318 | 319 | def ins_dec_abx(self: object) -> None: 320 | """ 321 | DEC - Decrement Memory, Absolute, X. 322 | 323 | :return: None 324 | """ 325 | address = self.fetch_word() + self.read_register_x() 326 | self.write_byte(address, self.read_byte(address) - 1) 327 | self.evaluate_flag_n(self.memory[address]) 328 | self.evaluate_flag_z(self.memory[address]) 329 | self.cycles += 1 330 | 331 | def ins_dex_imp(self: object) -> None: 332 | """ 333 | DEX - Decrement X Register. 334 | 335 | :return: None 336 | """ 337 | self.reg_x = self.read_register_x() - 1 338 | self.evaluate_flag_z(self.reg_x) 339 | self.evaluate_flag_n(self.reg_x) 340 | 341 | def ins_dey_imp(self: object) -> None: 342 | """ 343 | DEY - Decrement Y Register. 344 | 345 | :return: None 346 | """ 347 | self.reg_y = self.read_register_y() - 1 348 | self.evaluate_flag_z(self.reg_y) 349 | self.evaluate_flag_n(self.reg_y) 350 | 351 | def ins_inc_zp(self: object) -> None: 352 | """ 353 | INC - Increment Memory, Zero Page. 354 | 355 | :return: None 356 | """ 357 | address = self.fetch_byte() 358 | self.write_byte(address, self.read_byte(address) + 1) 359 | self.evaluate_flag_n(self.memory[address]) 360 | self.evaluate_flag_z(self.memory[address]) 361 | self.cycles += 1 362 | 363 | def ins_inc_zpx(self: object) -> None: 364 | """ 365 | INC - Increment Memory, Zero Page, X. 366 | 367 | :return: None 368 | """ 369 | address = (self.fetch_byte() + self.read_register_x()) & 0xFF 370 | self.write_byte(address, self.read_byte(address) + 1) 371 | self.evaluate_flag_n(self.memory[address]) 372 | self.evaluate_flag_z(self.memory[address]) 373 | self.cycles += 1 374 | 375 | def ins_inc_abs(self: object) -> None: 376 | """ 377 | INC - Increment Memory, Absolute. 378 | 379 | :return: None 380 | """ 381 | address = self.fetch_word() 382 | self.write_byte(address, self.read_byte(address) + 1) 383 | self.evaluate_flag_n(self.memory[address]) 384 | self.evaluate_flag_z(self.memory[address]) 385 | self.cycles += 1 386 | 387 | def ins_inc_abx(self: object) -> None: 388 | """ 389 | INC - Increment Memory, Absolute, X. 390 | 391 | :return: None 392 | """ 393 | address = self.fetch_word() + self.read_register_x() 394 | self.write_byte(address, self.read_byte(address) + 1) 395 | self.evaluate_flag_n(self.memory[address]) 396 | self.evaluate_flag_z(self.memory[address]) 397 | self.cycles += 1 398 | 399 | def ins_inx_imp(self: object) -> None: 400 | """ 401 | INX - Increment X Register. 402 | 403 | :return: None 404 | """ 405 | self.reg_x = self.read_register_x() + 1 406 | self.evaluate_flag_z(self.reg_x) 407 | self.evaluate_flag_n(self.reg_x) 408 | 409 | def ins_iny_imp(self: object) -> None: 410 | """ 411 | INY - Increment Y Register. 412 | 413 | :return: None 414 | """ 415 | self.reg_y = self.read_register_y() + 1 416 | self.evaluate_flag_z(self.reg_y) 417 | self.evaluate_flag_n(self.reg_y) 418 | 419 | def ins_lda_imm(self: object) -> None: 420 | """ 421 | LDA - Load Accumulator, Immediate. 422 | 423 | :return: None 424 | """ 425 | self.reg_a = self.fetch_byte() 426 | self.evaluate_flag_z(self.reg_a) 427 | self.evaluate_flag_n(self.reg_a) 428 | 429 | def ins_lda_zp(self: object) -> None: 430 | """ 431 | LDA - Load Accumulator, Zero Page. 432 | 433 | :return: None 434 | """ 435 | self.reg_a = self.read_byte( 436 | self.fetch_byte() 437 | ) 438 | self.evaluate_flag_z(self.reg_a) 439 | self.evaluate_flag_n(self.reg_a) 440 | 441 | def ins_lda_zpx(self: object) -> None: 442 | """ 443 | LDA - Load Accumulator, Zero Page, X. 444 | 445 | :return: None 446 | """ 447 | self.reg_a = self.read_byte( 448 | (self.fetch_byte() + self.read_register_x()) & 0xFF 449 | ) 450 | self.evaluate_flag_z(self.reg_a) 451 | self.evaluate_flag_n(self.reg_a) 452 | 453 | def ins_lda_abs(self: object) -> None: 454 | """ 455 | LDA - Load Accumulator, Absolute. 456 | 457 | :return: None 458 | """ 459 | self.reg_a = self.read_byte( 460 | self.fetch_word() 461 | ) 462 | self.evaluate_flag_z(self.reg_a) 463 | self.evaluate_flag_n(self.reg_a) 464 | 465 | def ins_lda_abx(self: object) -> None: 466 | """ 467 | LDA - Load Accumulator, Absolute, X. 468 | 469 | TODO: Using register X directly otherwise we use too many cycles. 470 | 471 | :return: None 472 | """ 473 | self.reg_a = self.read_byte( 474 | self.fetch_word() + self.reg_x 475 | ) 476 | self.evaluate_flag_z(self.reg_a) 477 | self.evaluate_flag_n(self.reg_a) 478 | 479 | def ins_lda_aby(self: object) -> None: 480 | """ 481 | LDA - Load Accumulator, Absolute, Y. 482 | 483 | TODO: Using register Y directly otherwise we use too many cycles. 484 | 485 | :return: None 486 | """ 487 | self.reg_a = self.read_byte( 488 | self.fetch_word() + self.reg_y 489 | ) 490 | self.evaluate_flag_z(self.reg_a) 491 | self.evaluate_flag_n(self.reg_a) 492 | 493 | def ins_lda_inx(self: object) -> None: 494 | """ 495 | LDA - Load Accumulator, Indexed Indirect. 496 | 497 | :return: None 498 | """ 499 | self.reg_a = self.read_byte( 500 | self.read_word( 501 | ((self.fetch_byte() + self.reg_x) & 0xFF) 502 | ) 503 | ) 504 | self.evaluate_flag_z(self.reg_a) 505 | self.evaluate_flag_n(self.reg_a) 506 | self.cycles += 1 507 | 508 | def ins_lda_iny(self: object) -> None: 509 | """ 510 | LDA - Load Accumulator, Indirect Indexed. 511 | 512 | :return: None 513 | """ 514 | self.reg_a = self.read_byte( 515 | self.read_word( 516 | self.fetch_byte() 517 | ) + self.reg_y 518 | ) 519 | self.evaluate_flag_z(self.reg_a) 520 | self.evaluate_flag_n(self.reg_a) 521 | 522 | def ins_ldx_imm(self: object) -> None: 523 | """ 524 | LDA - Load X Register, Immediate. 525 | 526 | :return: None 527 | """ 528 | self.reg_x = self.fetch_byte() 529 | self.evaluate_flag_z(self.reg_x) 530 | self.evaluate_flag_n(self.reg_x) 531 | 532 | def ins_ldx_zp(self: object) -> None: 533 | """ 534 | LDA - Load X Register, Zero Page. 535 | 536 | :return: None 537 | """ 538 | self.reg_x = self.read_byte(self.fetch_byte()) 539 | self.evaluate_flag_z(self.reg_x) 540 | self.evaluate_flag_n(self.reg_x) 541 | 542 | def ins_ldx_zpy(self: object) -> None: 543 | """ 544 | LDA - Load X Register, Zero Page, Y. 545 | 546 | :return: None 547 | """ 548 | self.reg_x = self.read_byte( 549 | (self.fetch_byte() + self.read_register_y()) & 0xFF 550 | ) 551 | self.evaluate_flag_z(self.reg_x) 552 | self.evaluate_flag_n(self.reg_x) 553 | 554 | def ins_ldx_abs(self: object) -> None: 555 | """ 556 | LDA - Load X Register, Absolute. 557 | 558 | :return: None 559 | """ 560 | self.reg_x = self.read_byte( 561 | self.fetch_word() 562 | ) 563 | self.evaluate_flag_z(self.reg_x) 564 | self.evaluate_flag_n(self.reg_x) 565 | 566 | def ins_ldx_aby(self: object) -> None: 567 | """ 568 | LDA - Load X Register, Absolute, Y. 569 | 570 | TODO: Using register Y directly otherwise we use too many cycles. 571 | 572 | :return: None 573 | """ 574 | self.reg_x = self.read_byte( 575 | self.fetch_word() + self.reg_y 576 | ) 577 | self.evaluate_flag_z(self.reg_x) 578 | self.evaluate_flag_n(self.reg_x) 579 | 580 | def ins_ldy_imm(self: object) -> None: 581 | """ 582 | LDA - Load Y Register, Immediate. 583 | 584 | :return: None 585 | """ 586 | self.reg_y = self.fetch_byte() 587 | self.evaluate_flag_z(self.reg_y) 588 | self.evaluate_flag_n(self.reg_y) 589 | 590 | def ins_ldy_zp(self: object) -> None: 591 | """ 592 | LDA - Load Y Register, Zero Page. 593 | 594 | :return: None 595 | """ 596 | self.reg_y = self.read_byte( 597 | self.fetch_byte() 598 | ) 599 | self.evaluate_flag_z(self.reg_y) 600 | self.evaluate_flag_n(self.reg_y) 601 | 602 | def ins_ldy_zpx(self: object) -> None: 603 | """ 604 | LDA - Load Y Register, Zero Page, X. 605 | 606 | :return: None 607 | """ 608 | self.reg_y = self.read_byte( 609 | (self.fetch_byte() + self.read_register_x()) & 0xFF 610 | ) 611 | self.evaluate_flag_z(self.reg_y) 612 | self.evaluate_flag_n(self.reg_y) 613 | 614 | def ins_ldy_abs(self: object) -> None: 615 | """ 616 | LDA - Load Y Register, Absolute. 617 | 618 | :return: None 619 | """ 620 | self.reg_y = self.read_byte( 621 | self.fetch_word() 622 | ) 623 | self.evaluate_flag_z(self.reg_y) 624 | self.evaluate_flag_n(self.reg_y) 625 | 626 | def ins_ldy_abx(self: object) -> None: 627 | """ 628 | LDA - Load Y Register, Absolute, X. 629 | 630 | TODO: Using register X directly otherwise we use too many cycles. 631 | 632 | :return: None 633 | """ 634 | self.reg_y = self.read_byte( 635 | self.fetch_word() + self.reg_x 636 | ) 637 | self.evaluate_flag_z(self.reg_y) 638 | self.evaluate_flag_n(self.reg_y) 639 | 640 | def ins_sec_imp(self: object) -> None: 641 | """ 642 | SEC - Set Carry Flag. 643 | 644 | :return: None 645 | """ 646 | self.flag_c = True 647 | self.cycles += 1 648 | 649 | def ins_sed_imp(self: object) -> None: 650 | """ 651 | SED - Set Decimal Mode. 652 | 653 | :return: None 654 | """ 655 | self.flag_d = True 656 | self.cycles += 1 657 | 658 | def ins_sei_imp(self: object) -> None: 659 | """ 660 | SEI - Set Interrupt Disable. 661 | 662 | :return: None 663 | """ 664 | self.flag_i = True 665 | self.cycles += 1 666 | 667 | def ins_sta_zp(self: object) -> None: 668 | """ 669 | STA - Store Accumulator, Zero Page. 670 | 671 | :return: None 672 | """ 673 | self.write_byte( 674 | self.fetch_byte(), 675 | self.reg_a 676 | ) 677 | 678 | def ins_sta_zpx(self: object) -> None: 679 | """ 680 | STA - Store Accumulator, Zero Page, X. 681 | 682 | :return: None 683 | """ 684 | self.write_byte( 685 | (self.fetch_byte() + self.read_register_x()) & 0xFF, 686 | self.reg_a 687 | ) 688 | 689 | def ins_sta_abs(self: object) -> None: 690 | """ 691 | STA - Store Accumulator, Absolute. 692 | 693 | :return: None 694 | """ 695 | self.write_byte( 696 | self.fetch_word(), 697 | self.reg_a 698 | ) 699 | 700 | def ins_sta_abx(self: object) -> None: 701 | """ 702 | STA - Store Accumulator, Absolute, X. 703 | 704 | TODO: Using register X directly otherwise we use too many cycles. 705 | 706 | :return: None 707 | """ 708 | self.write_byte( 709 | self.read_byte( 710 | self.fetch_word() + self.reg_x 711 | ), 712 | self.reg_a 713 | ) 714 | 715 | def ins_sta_aby(self: object) -> None: 716 | """ 717 | STA - Store Accumulator, Absolute, Y. 718 | 719 | TODO: Using register Y directly otherwise we use too many cycles. 720 | 721 | :return: None 722 | """ 723 | self.write_byte( 724 | self.read_byte( 725 | self.fetch_word() + self.reg_y 726 | ), 727 | self.reg_a 728 | ) 729 | 730 | def ins_sta_inx(self: object) -> None: 731 | """ 732 | STA - Store Accumulator, Indexed Indirect. 733 | 734 | :return: None 735 | """ 736 | self.write_byte( 737 | self.read_byte( 738 | self.read_word( 739 | ((self.fetch_byte() + self.reg_x) & 0xFF) 740 | ) 741 | ), 742 | self.reg_a 743 | ) 744 | 745 | def ins_sta_iny(self: object) -> None: 746 | """ 747 | LDA - Store Accumulator, Indirect Indexed. 748 | 749 | :return: None 750 | """ 751 | self.write_byte( 752 | self.read_byte( 753 | self.read_word( 754 | self.fetch_byte() 755 | ) + self.reg_y 756 | ), 757 | self.reg_a 758 | ) 759 | 760 | def ins_stx_zp(self: object) -> None: 761 | """ 762 | STA - Store X Register, Zero Page. 763 | 764 | :return: None 765 | """ 766 | self.write_byte( 767 | self.fetch_byte(), 768 | self.reg_x 769 | ) 770 | 771 | def ins_stx_zpy(self: object) -> None: 772 | """ 773 | STA - Store Y Register, Zero Page, X. 774 | 775 | :return: None 776 | """ 777 | self.write_byte( 778 | (self.fetch_byte() + self.read_register_y()) & 0xFF, 779 | self.reg_x 780 | ) 781 | 782 | def ins_stx_abs(self: object) -> None: 783 | """ 784 | STA - Store X Register, Absolute. 785 | 786 | :return: None 787 | """ 788 | self.write_byte( 789 | self.fetch_word(), 790 | self.reg_x 791 | ) 792 | 793 | def ins_sty_zp(self: object) -> None: 794 | """ 795 | STA - Store Y Register, Zero Page. 796 | 797 | :return: None 798 | """ 799 | self.write_byte( 800 | self.fetch_byte(), 801 | self.reg_y 802 | ) 803 | 804 | def ins_sty_zpx(self: object) -> None: 805 | """ 806 | STA - Store Y Register, Zero Page, X. 807 | 808 | :return: None 809 | """ 810 | self.write_byte( 811 | (self.fetch_byte() + self.read_register_x()) & 0xFF, 812 | self.reg_y 813 | ) 814 | 815 | def ins_sty_abs(self: object) -> None: 816 | """ 817 | STA - Store Y Register, Absolute. 818 | 819 | :return: None 820 | """ 821 | self.write_byte( 822 | self.fetch_word(), 823 | self.reg_y 824 | ) 825 | 826 | def ins_tax_imp(self: object) -> None: 827 | """ 828 | TAX - Transfer Accumulator to X. 829 | 830 | :return: None 831 | """ 832 | self.reg_x = self.read_register_a() 833 | self.evaluate_flag_z(self.reg_x) 834 | self.evaluate_flag_n(self.reg_x) 835 | 836 | def ins_tay_imp(self: object) -> None: 837 | """ 838 | TAY - Transfer Accumulator to Y. 839 | 840 | :return: None 841 | """ 842 | self.reg_y = self.read_register_a() 843 | self.evaluate_flag_z(self.reg_y) 844 | self.evaluate_flag_n(self.reg_y) 845 | 846 | def ins_tsx_imp(self: object) -> None: 847 | """ 848 | TSX - Transfer Stack Pointer to X. 849 | 850 | :return: None 851 | """ 852 | self.reg_x = self.pop() 853 | self.evaluate_flag_z(self.reg_x) 854 | self.evaluate_flag_n(self.reg_x) 855 | 856 | def ins_txa_imp(self: object) -> None: 857 | """ 858 | TXA - Transfer Register X to Accumulator. 859 | 860 | :return: None 861 | """ 862 | self.reg_a = self.read_register_x() 863 | self.evaluate_flag_z(self.reg_a) 864 | self.evaluate_flag_n(self.reg_a) 865 | 866 | def ins_txs_imp(self: object) -> None: 867 | """ 868 | TXS - Transfer Register X to Stack Pointer. 869 | 870 | :return: None 871 | """ 872 | self.push(self.reg_x) 873 | 874 | def ins_tya_imp(self: object) -> None: 875 | """ 876 | TYA - Transfer Register Y to Accumulator. 877 | 878 | :return: None 879 | """ 880 | self.reg_a = self.read_register_y() 881 | self.evaluate_flag_z(self.reg_a) 882 | self.evaluate_flag_n(self.reg_a) 883 | 884 | def ins_pha_imp(self: object) -> None: 885 | """ 886 | PHA - Push Accumulator. 887 | 888 | TODO: Add check to not cross page 889 | 890 | :return: None 891 | """ 892 | self.memory[self.stack_pointer] = self.read_register_a() 893 | self.stack_pointer -= 1 894 | self.cycles += 1 895 | 896 | def ins_pla_imp(self: object) -> None: 897 | """ 898 | PLA - Pull Accumulator. 899 | 900 | TODO: Add check to not cross page 901 | 902 | :return: None 903 | """ 904 | self.reg_a = self.memory[self.stack_pointer] 905 | self.stack_pointer += 1 906 | self.cycles += 1 907 | self.evaluate_flag_z(self.reg_a) 908 | self.evaluate_flag_n(self.reg_a) 909 | 910 | def ins_php_imp(self: object) -> None: 911 | """ 912 | Push Processor Statys, Implied. 913 | 914 | return: None 915 | """ 916 | flags = 0x00 917 | if self.flag_n: 918 | flags = flags | (1 << 1) 919 | if self.flag_v: 920 | flags = flags | (1 << 2) 921 | if self.flag_b: 922 | flags = flags | (1 << 3) 923 | if self.flag_d: 924 | flags = flags | (1 << 4) 925 | if self.flag_i: 926 | flags = flags | (1 << 5) 927 | if self.flag_z: 928 | flags = flags | (1 << 6) 929 | if self.flag_c: 930 | flags = flags | (1 << 7) 931 | self.push(flags) 932 | self.cycles += 1 933 | 934 | def ins_plp_imp(self: object) -> None: 935 | """ 936 | Pull Processor Status. 937 | 938 | TODO: Implement instruction and test 939 | TODO: Add check to not cross page 940 | 941 | :return: None 942 | """ 943 | flags = self.pop() 944 | # print(flags) 945 | if not flags & (1 << 1): 946 | self.flag_n = False 947 | if not flags & (1 << 2): 948 | self.flag_v = False 949 | if not flags & (1 << 3): 950 | self.flag_b = False 951 | if not flags & (1 << 4): 952 | self.flag_d = False 953 | if not flags & (1 << 5): 954 | self.flag_i = False 955 | if not flags & (1 << 6): 956 | self.flag_z = False 957 | if not flags & (1 << 7): 958 | self.flag_c = False 959 | self.cycles += 2 960 | --------------------------------------------------------------------------------