aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAstatin <[email protected]>2024-08-04 15:50:44 +0900
committerAstatin <astatin@redacted>2024-08-04 15:50:44 +0900
commitba934f1a6bf39036b05d8923520cfb8e699dd72d (patch)
tree68802ec08a9d675fa41b53a3684506f16899e152
first commit
-rwxr-xr-xgbasm372
1 files changed, 372 insertions, 0 deletions
diff --git a/gbasm b/gbasm
new file mode 100755
index 0000000..ec981f0
--- /dev/null
+++ b/gbasm
@@ -0,0 +1,372 @@
+#!/bin/python
+import argparse
+from os.path import abspath, dirname
+
+def padTo(args, addr):
+ if (args[0] == 0):
+ raise ValueError(".PADTO with label or to 0x0000 is not allowed")
+
+ if (addr > args[0]):
+ raise ValueError(".PADTO cannot pad to 0x{:04x} when starting from 0x{:04x}".format(args[0], addr))
+
+ return (args[0] - addr) * [0]
+
+labels = {}
+
+instructions = [
+ { "opcode": "LD", "params": [
+ { "type": ["r", "r"], "format": lambda args, _: [0b01000000 | (args[0] << 3) | args[1]] },
+ { "type": ["r", "8b"], "format": lambda args, _: [0b00000110 | (args[0] << 3), args[1]] },
+ { "type": ["r", "(HL)"], "format": lambda args, _: [0b01000110 | (args[0] << 3)] },
+ { "type": ["(HL)", "r"], "format": lambda args, _: [0b01110000 | args[1]] },
+ { "type": ["(HL)", "8b"], "format": lambda args, _: [0b00110110, args[1]] },
+ { "type": ["A", "(BC)"], "format": lambda args, _: [0b00001010] },
+ { "type": ["A", "(DE)"], "format": lambda args, _: [0b00011010] },
+ { "type": ["(BC)", "A"], "format": lambda args, _: [0b00000010] },
+ { "type": ["(DE)", "A"], "format": lambda args, _: [0b00010010] },
+ { "type": ["A", "(nn)"], "format": lambda args, _: [0b11111010, args[1] & 0xff, args[1] >> 8] },
+ { "type": ["(nn)", "A"], "format": lambda args, _: [0b11101010, args[0] & 0xff, args[0] >> 8] },
+ { "type": ["A", "(C)"], "format": lambda args, _: [0b11110010] },
+ { "type": ["(C)", "A"], "format": lambda args, _: [0b11100010] },
+ { "type": ["A", "(n)"], "format": lambda args, _: [0b11110000, args[1]] },
+ { "type": ["(n)", "A"], "format": lambda args, _: [0b11100000, args[0]] },
+ { "type": ["A", "(HL-)"], "format": lambda args, _: [0b00111010] },
+ { "type": ["(HL-)", "A"], "format": lambda args, _: [0b00110010] },
+ { "type": ["A", "(HL+)"], "format": lambda args, _: [0b00101010] },
+ { "type": ["(HL+)", "A"], "format": lambda args, _: [0b00100010] },
+ { "type": ["rr", "16b"], "format": lambda args, _: [0b00000001 | (args[0] << 4), args[1] & 0xff, args[1] >> 8] },
+ { "type": ["(nn)", "SP"], "format": lambda args, _: [0b00001000, args[0] & 0xff, args[0] >> 8] },
+ { "type": ["SP", "HL"], "format": lambda args, _: [0b11111001] },
+ { "type": ["HL", "8b"], "format": lambda args, _: [0b11111000, args[1]] },
+ ]},
+ { "opcode": "PUSH", "params": [
+ { "type": ["rr"], "format": lambda args, _: [0b11000101 | (args[0] << 4)] },
+ ]},
+ { "opcode": "POP", "params": [
+ { "type": ["rr"], "format": lambda args, _: [0b11000001 | (args[0] << 4)] },
+ ]},
+ { "opcode": "ADD", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b10000000 | (args[0])] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b10000110] },
+ { "type": ["8b"], "format": lambda args, _: [0b11000110, args[0]] },
+ { "type": ["SP", "8b"], "format": lambda args, _: [0b11101000, args[1]] },
+
+ # TODO: HL,rr (x9 ?)
+ ]},
+ { "opcode": "ADC", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b10001000 | args[0]] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b10001110] },
+ { "type": ["8b"], "format": lambda args, _: [0b11001110, args[0]] },
+ ]},
+ { "opcode": "SUB", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b10010000 | (args[0])] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b10010110] },
+ { "type": ["8b"], "format": lambda args, _: [0b11010110, args[0]] },
+ ]},
+ { "opcode": "SBC", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b10011000 | args[0]] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b10011110] },
+ { "type": ["8b"], "format": lambda args, _: [0b11011110, args[0]] },
+ ]},
+ { "opcode": "CP", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b10111000 | (args[0])] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b10111110] },
+ { "type": ["8b"], "format": lambda args, _: [0b11111110, args[0]] },
+ ]},
+ { "opcode": "INC", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b00000100 | (args[0] << 3)] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b00110100] },
+
+ # THIS ONE IS SPECULATIVE
+ { "type": ["rr"], "format": lambda args, _: [0b00000011 | (args[0] << 4)] },
+ ]},
+ { "opcode": "DEC", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b00000101 | (args[0] << 3)] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b00110101] },
+
+ # TODO: rr
+ ]},
+ { "opcode": "AND", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b10100000 | (args[0])] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b10100110] },
+ { "type": ["8b"], "format": lambda args, _: [0b11100110, args[0]] },
+ ]},
+ { "opcode": "OR", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b10110000 | (args[0])] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b10110110] },
+ { "type": ["8b"], "format": lambda args, _: [0b11110110, args[0]] },
+ ]},
+ { "opcode": "XOR", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b10101000 | (args[0])] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b10101110] },
+ { "type": ["8b"], "format": lambda args, _: [0b11101110, args[0]] },
+ ]},
+ { "opcode": "CCF", "params": [
+ { "type": [], "format": lambda args, _: [0b00111111] },
+ ]},
+ { "opcode": "SCF", "params": [
+ { "type": [], "format": lambda args, _: [0b00110111] },
+ ]},
+ { "opcode": "DAA", "params": [
+ { "type": [], "format": lambda args, _: [0b00100111] },
+ ]},
+ { "opcode": "CPL", "params": [
+ { "type": [], "format": lambda args, _: [0b00101111] },
+ ]},
+ { "opcode": "JP", "params": [
+ { "type": ["16b"], "format": lambda args, _: [0b11000011, args[0] & 0xff, args[0] >> 8] },
+ { "type": ["HL"], "format": lambda args, _: [0b11101001] },
+ { "type": ["cc", "16b"], "format": lambda args, _: [0b11000010 | (args[0] << 3), args[1] & 0xff, args[1] >> 8] }
+ ]},
+ { "opcode": "JR", "params": [
+ { "type": ["8b"], "format": lambda args, _: [0b00011000, args[0]] },
+ { "type": ["16b"], "format": lambda args, addr: [0b00011000, (args[0] - addr - 2) & 0xff] },
+ { "type": ["cc", "8b"], "format": lambda args, _: [0b00100000 | (args[0] << 3), args[1]] },
+ { "type": ["C", "8b"], "format": lambda args, _: [0b00100000 | (3 << 3), args[1]] },
+ { "type": ["cc", "16b"], "format": lambda args, addr: [0b00100000 | (args[0] << 3), (args[1] - addr - 2) & 0xff] },
+ { "type": ["C", "16b"], "format": lambda args, addr: [0b00100000 | (3 << 3), (args[1] - addr - 2) & 0xff] }
+ ]},
+ { "opcode": "CALL", "params": [
+ { "type": ["16b"], "format": lambda args, _: [0b11001101, args[0] & 0xff, args[0] >> 8] },
+ { "type": ["cc", "16b"], "format": lambda args, _: [0b11000100 | (args[0] << 3), args[1] & 0xff, args[1] >> 8] }
+ ]},
+ { "opcode": "RET", "params": [
+ { "type": [], "format": lambda args, _: [0b11001001] },
+ { "type": ["cc"], "format": lambda args, _: [0b11000000 | (args[0] << 3)] },
+ ]},
+ { "opcode": "RETI", "params": [
+ { "type": [], "format": lambda args, _: [0b11011001] },
+ ]},
+ { "opcode": "RST", "params": [
+ { "type": ["n"], "format": lambda args, _: [0b11000111 | (args[0] << 3)] },
+ ]},
+ { "opcode": "DI", "params": [
+ { "type": [], "format": lambda args, _: [0b11110011] },
+ ]},
+ { "opcode": "EI", "params": [
+ { "type": [], "format": lambda args, _: [0b11111011] },
+ ]},
+ { "opcode": "NOP", "params": [
+ { "type": [], "format": lambda args, _: [0b00000000] },
+ ]},
+ { "opcode": "HALT", "params": [
+ { "type": [], "format": lambda args, _: [0b01110110] },
+ ]},
+ { "opcode": "STOP", "params": [
+ { "type": [], "format": lambda args, _: [0b00010000, 0b00000000] },
+ ]},
+ { "opcode": "RLCA", "params": [
+ { "type": [], "format": lambda args, _: [0b00000111] },
+ ]},
+ { "opcode": "RLA", "params": [
+ { "type": [], "format": lambda args, _: [0b00010111] },
+ ]},
+ { "opcode": "RRCA", "params": [
+ { "type": [], "format": lambda args, _: [0b00001111] },
+ ]},
+ { "opcode": "RRA", "params": [
+ { "type": [], "format": lambda args, _: [0b00011111] },
+ ]},
+ { "opcode": "BIT", "params": [
+ { "type": ["n", "r"], "format": lambda args, _: [0b11001011, 0b01000000 | (args[0] << 3) | args[1]] },
+ { "type": ["n", "(HL)"], "format": lambda args, _: [0b11001011, 0b01000110 | (args[0] << 3)] },
+ ]},
+ { "opcode": "SET", "params": [
+ { "type": ["n", "r"], "format": lambda args, _: [0b11001011, 0b11000000 | (args[0] << 3) | args[1]] },
+ { "type": ["n", "(HL)"], "format": lambda args, _: [0b11001011, 0b11000110 | (args[0] << 3)] },
+ ]},
+ { "opcode": "RES", "params": [
+ { "type": ["n", "r"], "format": lambda args, _: [0b11001011, 0b10000000 | (args[0] << 3) | args[1]] },
+ { "type": ["n", "(HL)"], "format": lambda args, _: [0b11001011, 0b10000110 | (args[0] << 3)] },
+ ]},
+ { "opcode": "RLC", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b11001011, 0b00000000 | args[0]] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b11001011, 0b00000110] },
+ ]},
+ { "opcode": "RL", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b11001011, 0b00010000 | args[0]] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b11001011, 0b00010110] },
+ ]},
+ { "opcode": "RRC", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b11001011, 0b00001000 | args[0]] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b11001011, 0b00001110] },
+ ]},
+ { "opcode": "RR", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b11001011, 0b00011000 | args[0]] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b11001011, 0b00011110] },
+ ]},
+ { "opcode": "SLA", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b11001011, 0b00100000 | args[0]] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b11001011, 0b00100110] },
+ ]},
+ { "opcode": "SWAP", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b11001011, 0b00110000 | args[0]] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b11001011, 0b00110110] },
+ ]},
+ { "opcode": "SRA", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b11001011, 0b00101000 | args[0]] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b11001011, 0b00101110] },
+ ]},
+ { "opcode": "SRL", "params": [
+ { "type": ["r"], "format": lambda args, _: [0b11001011, 0b00111000 | args[0]] },
+ { "type": ["(HL)"], "format": lambda args, _: [0b11001011, 0b00111110] },
+ ]},
+ { "opcode": ".DB", "params": [
+ { "type": ["*"], "format": lambda args, _: args },
+ ]},
+ { "opcode": ".PADTO", "params": [
+ { "type": ["16b"], "format": lambda args, addr: padTo(args,addr) },
+ ]},
+]
+
+registers = {
+ "A": 7,
+ "B": 0,
+ "C": 1,
+ "D": 2, # Maybe ?
+ "E": 3,
+ "H": 4, # Maybe ?
+ "L": 5, # Maybe ?
+ # (HL) is not a register but is associated with 6
+
+ "BC": 0,
+ "DE": 1,
+ "HL": 2, # Confirmed
+ "AF": 3, # TODO: Only for PUSH & POP
+ "SP": 3, # TODO: For everything except PUSH & POP
+}
+
+conditions = {
+ # Not checked:
+ "NZ": 0,
+ "Z": 1,
+ "NC": 2,
+ "C": 3,
+}
+
+class Param:
+ def __init__(self, value, labels):
+ self.type, self.value = self.get_type_value(value.upper().strip(), labels)
+
+ def get_type_value(self, input, labels):
+ if input in ['A', 'B', 'C', 'D', 'E', 'H', 'L']:
+ return ['r', input], registers[input]
+ elif input in ['BC', 'DE', 'HL', 'SP', 'AF']:
+ return ['rr', input], registers[input]
+ elif len(input) == 4 and input[:2] == '0X':
+ return ['8b'], int(input[2:], 16)
+ elif len(input) == 6 and input[:2] == '0X':
+ return ['16b'], int(input[2:], 16)
+ elif len(input) == 8 and input[:3] == '(0X' and input[-1] == ')':
+ return ['(nn)'], int(input[3:-1], 16)
+ elif len(input) == 6 and input[:3] == '(0X' and input[-1] == ')':
+ return ['(n)'], int(input[3:-1], 16)
+ elif input in ['NZ', 'Z', 'NC', 'C']:
+ return ['cc', input], conditions[input]
+ elif input in ['0','1','2','3','4','5','6','7']:
+ return ['n'], int(input)
+ elif input in ["(HL)", "(BC)", "(DE)", "(C)", "(HL-)", "(HL+)"]:
+ return [input], 0
+ elif input.startswith('='):
+ if labels is None:
+ return ['16b'], 0
+ else:
+ return ['16b'], labels[input[1:]]
+ else:
+ raise ValueError("Invalid parameter ({})".format(input))
+
+
+class Instruction:
+ def __init__(self, value, labels):
+ splitted = value.split(' ')
+ self.opcode = splitted[0].upper().strip()
+ self.params = [Param(param, labels) for param in splitted[1:] if param.strip()]
+
+ def get_instruction_format(self):
+ for instruction in instructions:
+ if self.opcode == instruction['opcode']:
+ for params in instruction['params']:
+ if len(params["type"]) == 1 and params["type"][0] == "*":
+ return params
+ if len(params["type"]) == len(self.params):
+ for i in range(len(params["type"])):
+ if params["type"][i] not in self.params[i].type:
+ break
+ else:
+ return params
+ return None
+
+ def to_bytes(self, address):
+ instruction_format = self.get_instruction_format()['format']
+ return instruction_format([param.value for param in self.params], address)
+
+def main():
+ parser = argparse.ArgumentParser()
+ parser.add_argument("input_file", help="The input file in gbasm")
+ parser.add_argument("output_file", help="The output gb rom filename (e.g. basic.rom)")
+ args = parser.parse_args()
+
+ lines, _ = preprocess(args.input_file, 0)
+ print("\n".join(lines))
+
+ program = assemble(lines)
+
+ output = open(args.output_file, "wb")
+ output.write(program)
+ output.close()
+
+def preprocess(input_file, offset):
+ f = open(input_file, "r")
+ relative_path = dirname(abspath(input_file))
+
+ global labels
+ starting_address = offset;
+ lines = []
+ # Preprocess
+ for line in f:
+ line_without_comment = line.split(';')[0].strip()
+
+ words = line_without_comment.split(' ')
+
+ if len(words) == 2 and words[0].upper() == ".INCLUDE":
+ if not words[1].startswith('"') or not words[1].endswith('"'):
+ raise ValueError("Invalid parameter for .INCLUDE ({})".format(words[1]))
+ file = relative_path + "/" + words[1][1:-1]
+ included_lines, starting_address = preprocess(file, starting_address)
+ lines += included_lines
+ continue
+
+ if ':' in line_without_comment:
+ splitted = line_without_comment.split(':')
+ labels[splitted[0].strip().upper()] = starting_address
+ line_without_comment = splitted[1].strip()
+
+ if line_without_comment != '':
+ res = line_without_comment.replace("$", "0x").replace(",", " ").replace("0xFF00+", "")
+ lines.append(res)
+ instruction = Instruction(res, None)
+ print("Line: " + line)
+ print("Instruction: " + instruction.opcode + " " + str(instruction.params))
+ print("Valid: " + str(instruction.get_instruction_format()))
+ print("Format: " + str(instruction))
+ starting_address += len(instruction.to_bytes(starting_address))
+
+ print(labels)
+
+ return lines, starting_address
+
+def assemble(lines):
+ program = []
+ # Compile
+ for line in lines:
+ instruction = Instruction(line, labels)
+ print("Instruction: " + instruction.opcode + " " + str(instruction.params))
+ print("Valid: " + str(instruction.get_instruction_format()))
+ print("Format: " + str(instruction))
+
+ program += instruction.to_bytes(len(program))
+ return bytearray(program)
+
+
+if __name__ == "__main__":
+ main()