diff options
author | Astatin <[email protected]> | 2024-08-04 15:50:44 +0900 |
---|---|---|
committer | Astatin <astatin@redacted> | 2024-08-04 15:50:44 +0900 |
commit | ba934f1a6bf39036b05d8923520cfb8e699dd72d (patch) | |
tree | 68802ec08a9d675fa41b53a3684506f16899e152 |
first commit
-rwxr-xr-x | gbasm | 372 |
1 files changed, 372 insertions, 0 deletions
@@ -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() |