Skip to content

Commit

Permalink
Merge pull request #13 from craigthomas/audio-mnemonic
Browse files Browse the repository at this point in the history
Add AUDIO mnemonic and processing
  • Loading branch information
craigthomas authored Jul 9, 2024
2 parents 1acd0ce + 4777f12 commit 7643280
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 72 deletions.
23 changes: 12 additions & 11 deletions .github/workflows/python-app.yml
Original file line number Diff line number Diff line change
@@ -1,21 +1,22 @@
name: Build Test Coverage
on: [push, pull_request]
on: [pull_request, workflow_dispatch]
jobs:
run:
runs-on: ubuntu-20.04
env:
OS: ubuntu-20.04
PYTHON: '3.8.10'
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Setup Python
- name: Setup Python 3.8.12
uses: actions/setup-python@v4
with:
python-version: 3.8.10
python-version: '3.8.12'
- name: Update PIP
run: python -m pip install --upgrade pip
- name: Install Requirements
run: pip install -r requirements.txt
- name: Generate Report
run: |
pip install -r requirements.txt
coverage run -m unittest
run: coverage run --source=chip8asm -m unittest
- name: Codecov
uses: codecov/codecov-action@v3.1.0
uses: codecov/codecov-action@v4.2.0
env:
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
103 changes: 55 additions & 48 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,8 @@

## What is it?

This project is a (Super) Chip 8 assembler written in Python 3.6. The assembler will
take valid Super Chip 8 assembly statements and generate a binary file containing
This project is an XO Chip, Super Chip, and Chip 8 assembler written in Python 3.6. The assembler will
take valid XO Chip, Super Chip, and Chip 8 assembly statements, and generate a binary file containing
the correct machine instructions.


Expand Down Expand Up @@ -135,55 +135,62 @@ specifications, as well as pseudo operations.

### Chip 8 Mnemonics

| Mnemonic | Opcode | Operands | Description |
| -------- | ------ | :------: | ----------- |
| `SYS` | `0nnn` | 1 | System call (ignored) |
| `CLR` | `00E0` | 0 | Clear the screen |
| `RTS` | `00EE` | 0 | Return from subroutine |
| `JUMP` | `1nnn` | 1 | Jump to address `nnn` |
| `CALL` | `2nnn` | 1 | Call routine at address `nnn` |
| `SKE` | `3snn` | 2 | Skip next instruction if register `s` equals `nn` |
| `SKNE` | `4snn` | 2 | Do not skip next instruction if register `s` equals `nn` |
| `SKRE` | `5st0` | 2 | Skip if register `s` equals register `t` |
| `LOAD` | `6snn` | 2 | Load register `s` with value `nn` |
| `ADD` | `7snn` | 2 | Add value `nn` to register `s` |
| `MOVE` | `8st0` | 2 | Move value from register `s` to register `t` |
| `OR` | `8st1` | 2 | Perform logical OR on register `s` and `t` and store in `t` |
| `AND` | `8st2` | 2 | Perform logical AND on register `s` and `t` and store in `t` |
| `XOR` | `8st3` | 2 | Perform logical XOR on register `s` and `t` and store in `t` |
| `ADDR` | `8st4` | 2 | Add `s` to `t` and store in `s` - register `F` set on carry |
| `SUB` | `8st5` | 2 | Subtract `s` from `t` and store in `s` - register `F` set on !borrow |
| `SHR` | `8st6` | 2 | Shift bits in `s` 1 bit right, store in `t` - bit 0 shifts to register `F` |
| `SHL` | `8stE` | 2 | Shift bits in `s` 1 bit left, store in `t` - bit 7 shifts to register `F` |
| `SKRNE` | `9st0` | 2 | Skip next instruction if register `s` not equal register `t` |
| `LOADI` | `Annn` | 1 | Load index with value `nnn` |
| `JUMPI` | `Bnnn` | 1 | Jump to address `nnn` + index |
| `RAND` | `Ctnn` | 2 | Generate random number between 0 and `nn` and store in `t` |
| `DRAW` | `Dstn` | 3 | Draw `n` byte sprite at x location reg `s`, y location reg `t` |
| `SKPR` | `Es9E` | 1 | Skip next instruction if the key in reg `s` is pressed |
| `SKUP` | `EsA1` | 1 | Skip next instruction if the key in reg `s` is not pressed |
| `MOVED` | `Ft07` | 1 | Move delay timer value into register `t` |
| `KEYD` | `Ft0A` | 1 | Wait for keypress and store in register `t` |
| `LOADD` | `Fs15` | 1 | Load delay timer with value in register `s` |
| `LOADS` | `Fs18` | 1 | Load sound timer with value in register `s` |
| `ADDI` | `Fs1E` | 1 | Add value in register `s` to index |
| `LDSPR` | `Fs29` | 1 | Load index with sprite from register `s` |
| `BCD` | `Fs33` | 1 | Store the binary coded decimal value of register `s` at index |
| `STOR` | `Fs55` | 1 | Store the values of register `s` registers at index |
| `READ` | `Fs65` | 1 | Read back the stored values at index into registers |
| Mnemonic | Opcode | Operands | Description |
| -------- | ------ |:--------:|----------------------------------------------------------------------------|
| `SYS` | `0nnn` | 1 | System call (ignored) |
| `CLR` | `00E0` | 0 | Clear the screen |
| `RTS` | `00EE` | 0 | Return from subroutine |
| `JUMP` | `1nnn` | 1 | Jump to address `nnn` |
| `CALL` | `2nnn` | 1 | Call routine at address `nnn` |
| `SKE` | `3snn` | 2 | Skip next instruction if register `s` equals `nn` |
| `SKNE` | `4snn` | 2 | Do not skip next instruction if register `s` equals `nn` |
| `SKRE` | `5st0` | 2 | Skip if register `s` equals register `t` |
| `LOAD` | `6snn` | 2 | Load register `s` with value `nn` |
| `ADD` | `7snn` | 2 | Add value `nn` to register `s` |
| `MOVE` | `8st0` | 2 | Move value from register `s` to register `t` |
| `OR` | `8st1` | 2 | Perform logical OR on register `s` and `t` and store in `t` |
| `AND` | `8st2` | 2 | Perform logical AND on register `s` and `t` and store in `t` |
| `XOR` | `8st3` | 2 | Perform logical XOR on register `s` and `t` and store in `t` |
| `ADDR` | `8st4` | 2 | Add `s` to `t` and store in `s` - register `F` set on carry |
| `SUB` | `8st5` | 2 | Subtract `s` from `t` and store in `s` - register `F` set on !borrow |
| `SHR` | `8st6` | 2 | Shift bits in `s` 1 bit right, store in `t` - bit 0 shifts to register `F` |
| `SHL` | `8stE` | 2 | Shift bits in `s` 1 bit left, store in `t` - bit 7 shifts to register `F` |
| `SKRNE` | `9st0` | 2 | Skip next instruction if register `s` not equal register `t` |
| `LOADI` | `Annn` | 1 | Load index with value `nnn` |
| `JUMPI` | `Bnnn` | 1 | Jump to address `nnn` + index |
| `RAND` | `Ctnn` | 2 | Generate random number between 0 and `nn` and store in `t` |
| `DRAW` | `Dstn` | 3 | Draw `n` byte sprite at x location reg `s`, y location reg `t` |
| `SKPR` | `Es9E` | 1 | Skip next instruction if the key in reg `s` is pressed |
| `SKUP` | `EsA1` | 1 | Skip next instruction if the key in reg `s` is not pressed |
| `MOVED` | `Ft07` | 1 | Move delay timer value into register `t` |
| `KEYD` | `Ft0A` | 1 | Wait for keypress and store in register `t` |
| `LOADD` | `Fs15` | 1 | Load delay timer with value in register `s` |
| `LOADS` | `Fs18` | 1 | Load sound timer with value in register `s` |
| `ADDI` | `Fs1E` | 1 | Add value in register `s` to index |
| `LDSPR` | `Fs29` | 1 | Load index with sprite from register `s` |
| `BCD` | `Fs33` | 1 | Store the binary coded decimal value of register `s` at index |
| `STOR` | `Fs55` | 1 | Store the values of register `s` registers at index |
| `READ` | `Fs65` | 1 | Read back the stored values at index into registers |

### Super Chip 8 Mnemonics

| Mnemonic | Opcode | Operands | Description |
| -------- | ------ | :------: | ----------- |
| `SCRD` | `00Cn` | 1 | Scroll down `n` lines |
| `SCRR` | `00FB` | 0 | Scroll right 4 pixels |
| `SCRL` | `00FC` | 0 | Scroll left 4 pixels |
| `EXIT` | `00FD` | 0 | Exit interpreter |
| `EXTD` | `00FE` | 0 | Disable extended mode |
| `EXTE` | `00FF` | 0 | Enable extended mode |
| `SRPL` | `Fs75` | 1 | Store subset of registers in RPL store |
| `LRPL` | `Fs85` | 1 | Read back subset of registers from RPL store |
| Mnemonic | Opcode | Operands | Description |
|----------| ------ |:--------:|----------------------------------------------|
| `SCRD` | `00Cn` | 1 | Scroll down `n` lines |
| `SCRR` | `00FB` | 0 | Scroll right 4 pixels |
| `SCRL` | `00FC` | 0 | Scroll left 4 pixels |
| `EXIT` | `00FD` | 0 | Exit interpreter |
| `EXTD` | `00FE` | 0 | Disable extended mode |
| `EXTE` | `00FF` | 0 | Enable extended mode |
| `SRPL` | `Fs75` | 1 | Store subset of registers in RPL store |
| `LRPL` | `Fs85` | 1 | Read back subset of registers from RPL store |

### XO Chip Mnemonics

| Mnemonic | Opcode | Operands | Description |
|----------|--------|:--------:|------------------------------------------------|
| `AUDIO` | `F002` | 0 | Load 16-byte audio pattern buffer from `index` |


### Pseudo Operations

Expand Down
19 changes: 14 additions & 5 deletions chip8asm/program.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (C) 2014-2019 Craig Thomas
Copyright (C) 2024 Craig Thomas
This project uses an MIT style license - see LICENSE for details.
This file contains the main Program class for the Chip 8 Assembler.
Expand Down Expand Up @@ -96,18 +96,27 @@ def get_statements(self):
"""
return self.statements

def save_binary_file(self, filename):
def generate_machine_code(self):
"""
Writes out the assembled statements to the specified file
name.
Generates the machine code from the actual statements.
:param filename: the name of the file to save statements
:return: a list of integers representing the resulting machine code output
"""
machine_codes = []
for statement in self.statements:
if not statement.is_empty() and not statement.comment_only:
for index in range(0, len(statement.op_code), 2):
machine_codes.append(int(statement.op_code[index:index + 2], 16))
return machine_codes

def save_binary_file(self, filename):
"""
Writes out the assembled statements to the specified file
name.
:param filename: the name of the file to save statements
"""
machine_codes = self.generate_machine_code()
with open(filename, "wb") as outfile:
outfile.write(bytearray(machine_codes))

Expand Down
1 change: 1 addition & 0 deletions chip8asm/statement.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@
Operation(op="Dstn", operands=3, source=1, target=1, numeric=1, mnemonic="DRAW"),
Operation(op="Es9E", operands=1, source=1, target=0, numeric=0, mnemonic="SKPR"),
Operation(op="EsA1", operands=1, source=1, target=0, numeric=0, mnemonic="SKUP"),
Operation(op="F002", operands=0, source=0, target=0, numeric=0, mnemonic="AUDIO"),
Operation(op="Ft07", operands=1, source=0, target=1, numeric=0, mnemonic="MOVED"),
Operation(op="Ft0A", operands=1, source=0, target=1, numeric=0, mnemonic="KEYD"),
Operation(op="Fs15", operands=1, source=1, target=0, numeric=0, mnemonic="LOADD"),
Expand Down
46 changes: 46 additions & 0 deletions test/test_integration.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
"""
Copyright (C) 204 Craig Thomas
This project uses an MIT style license - see LICENSE for details.
A Chip 8 assembler - see the README.md file for details.
"""
# I M P O R T S ###############################################################

import unittest

from chip8asm.program import Program
from chip8asm.statement import Statement

# C L A S S E S ###############################################################


class TestIntegration(unittest.TestCase):
"""
A test class for integration tests.
"""
def setUp(self):
"""
Common setup routines needed for all unit tests.
"""
pass

@staticmethod
def translate_statements(program):
"""
Given a program, translate the statements into machine code.
"""
program.translate_statements()
program.set_addresses()
program.fix_opcodes()
return program

def test_audio_mnemonic_translate_correct(self):
program = Program()
statement = Statement()
statement.parse_line(" AUDIO ; audio statement")
program.statements.append(statement)
program = self.translate_statements(program)
machine_code = program.generate_machine_code()
self.assertEqual([0xF0, 0x02], machine_code)

# E N D O F F I L E #######################################################
16 changes: 8 additions & 8 deletions test/test_statement.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
"""
Copyright (C) 2012-2019 Craig Thomas
Copyright (C) 2024 Craig Thomas
This project uses an MIT style license - see LICENSE for details.
A Chip 8 assembler - see the README.md file for details.
Expand Down Expand Up @@ -144,43 +144,43 @@ def test_is_empty_correct(self):

def test_get_label_correct(self):
statement = Statement()
self.assertEquals("", statement.get_label())
self.assertEqual("", statement.get_label())
statement.label = "label"
self.assertEqual("label", statement.get_label())

def test_get_comment_correct(self):
statement = Statement()
self.assertEquals("", statement.get_comment())
self.assertEqual("", statement.get_comment())
statement.comment = "comment"
self.assertEqual("comment", statement.get_comment())

def test_get_mnemonic_correct(self):
statement = Statement()
self.assertEquals("", statement.get_mnemonic())
self.assertEqual("", statement.get_mnemonic())
statement.mnemonic = "mnemonic"
self.assertEqual("mnemonic", statement.get_mnemonic())

def test_get_op_code_correct(self):
statement = Statement()
self.assertEquals("", statement.get_op_code())
self.assertEqual("", statement.get_op_code())
statement.op_code = "op_code"
self.assertEqual("op_code", statement.get_op_code())

def test_get_operands_correct(self):
statement = Statement()
self.assertEquals("", statement.get_operands())
self.assertEqual("", statement.get_operands())
statement.operands = "operands"
self.assertEqual("operands", statement.get_operands())

def test_get_address_correct(self):
statement = Statement()
self.assertEquals("", statement.get_address())
self.assertEqual("", statement.get_address())
statement.address = "address"
self.assertEqual("address", statement.get_address())

def test_set_address_correct(self):
statement = Statement()
self.assertEquals("", statement.get_address())
self.assertEqual("", statement.get_address())
statement.set_address("address")
self.assertEqual("address", statement.get_address())

Expand Down

0 comments on commit 7643280

Please sign in to comment.