def test_bytecode_assembly(self):
        test_line_id = LineIdentifier(42, 'test_bytecode_assembly')
        parts1 = [
            NumericByteCodePart(15, 4, True, 'big', test_line_id),
            NumericByteCodePart(3, 2, False, 'big', test_line_id),
            NumericByteCodePart(3, 2, False, 'big', test_line_id),
        ]

        ai1 = AssembledInstruction(123, parts1)
        self.assertEqual(ai1.byte_size, 1)
        self.assertEqual(ai1.get_bytes({}), bytearray([0xff]),
                         'generated bytes should match')

        parts2 = [
            NumericByteCodePart(8, 4, True, 'big', test_line_id),
            NumericByteCodePart(0x1122, 16, True, 'little', test_line_id),
            NumericByteCodePart(0x11223344, 32, True, 'little', test_line_id),
            NumericByteCodePart(0x8, 4, False, 'big', test_line_id),
        ]

        ai2 = AssembledInstruction(456, parts2)
        self.assertEqual(ai2.byte_size, 8)
        self.assertEqual(
            ai2.get_bytes({}),
            bytearray([0x80, 0x22, 0x11, 0x44, 0x33, 0x22, 0x11, 0x80]),
            'generated bytes should match')
    def test_composite_bytecode_part(self):
        test_line_id = LineIdentifier(88, 'test_composite_bytecode_part')
        register_labels = set(['a', 'i'])
        label_values = GlobalLabelScope(register_labels)
        label_values.set_label_value('var1', 2, 1)
        label_values.set_label_value('var2', 0xF0, 2)

        p1 = NumericByteCodePart(1, 3, True, 'big', test_line_id)
        p2 = NumericByteCodePart(3, 2, True, 'big', test_line_id)
        p3 = ExpressionByteCodePart('var1+13', 4, True, 'big', test_line_id)

        c1 = CompositeByteCodePart([p1, p2], False, 'big', test_line_id)
        self.assertEqual(5, c1.value_size, 'bit size should match')
        self.assertEqual(7, c1.get_value(label_values), 'value should match')

        c2 = CompositeByteCodePart([p2, p1], False, 'big', test_line_id)
        self.assertEqual(5, c2.value_size, 'bit size should match')
        self.assertEqual(25, c2.get_value(label_values), 'value should match')

        c3 = CompositeByteCodePart([p1, p2, p3], False, 'big', test_line_id)
        self.assertEqual(9, c3.value_size, 'bit size should match')
        self.assertEqual(127, c3.get_value(label_values), 'value should match')

        c4 = CompositeByteCodePart([p1, p2, p1], False, 'big', test_line_id)
        self.assertEqual(8, c4.value_size, 'bit size should match')
        self.assertEqual(57, c4.get_value(label_values), 'value should match')
    def test_numeric_enumeration_operand(self):
        with pkg_resources.path(config_files,
                                'test_operand_features.yaml') as fp:
            isa_model = AssemblerModel(str(fp), 0)
        lineid = LineIdentifier(33, 'test_numeric_enumeration_operand')

        t1 = InstructionLine.factory(lineid, 'num my_val+7', 'comment',
                                     isa_model)
        t1.set_start_address(1)
        t1.label_scope = TestInstructionParsing.label_values
        self.assertIsInstance(t1, InstructionLine)
        self.assertEqual(t1.byte_size, 1, 'has 1 bytes')
        t1.generate_bytes()
        self.assertEqual(list(t1.get_bytes()), [0b10101000],
                         'instruction byte should match')

        t2 = InstructionLine.factory(lineid, 'sftl a, 1', 'comment', isa_model)
        t2.set_start_address(1)
        t2.label_scope = TestInstructionParsing.label_values
        self.assertIsInstance(t2, InstructionLine)
        self.assertEqual(t2.byte_size, 1, 'has 1 bytes')
        t2.generate_bytes()
        self.assertEqual(list(t2.get_bytes()), [0b10001001],
                         'instruction byte should match')

        with self.assertRaises(SystemExit,
                               msg='test invalid enumeration values'):
            e1 = InstructionLine.factory(lineid, 'num 7',
                                         'number 7 is not allowed', isa_model)
            e1.label_scope = TestInstructionParsing.label_values
            e1.generate_bytes()
    def test_deferred_operands(self):
        with pkg_resources.path(config_files,
                                'test_operand_features.yaml') as fp:
            isa_model = AssemblerModel(str(fp), 0)
        lineid = LineIdentifier(13, 'test_deferred_operands')

        t1 = InstructionLine.factory(lineid, '  ld a, [[$F0]]', 'comment',
                                     isa_model)
        t1.set_start_address(1)
        t1.label_scope = TestInstructionParsing.label_values
        self.assertIsInstance(t1, InstructionLine)
        self.assertEqual(t1.byte_size, 2, 'has 2 bytes')
        t1.generate_bytes()
        self.assertEqual(list(t1.get_bytes()), [0b00010101, 0xF0],
                         'instruction byte should match')

        t2 = InstructionLine.factory(lineid, '  ld [[my_val]],x', 'comment',
                                     isa_model)
        t2.set_start_address(1)
        t2.label_scope = TestInstructionParsing.label_values
        self.assertIsInstance(t2, InstructionLine)
        self.assertEqual(t2.byte_size, 2, 'has 2 bytes')
        t2.generate_bytes()
        self.assertEqual(list(t2.get_bytes()), [0b10011101, 0x08],
                         'instruction byte should match')
 def test_label_parsing(self):
     with pkg_resources.path(config_files,
                             'test_instructions_with_variants.yaml') as fp:
         isa_model = AssemblerModel(str(fp), 0)
     lineid = LineIdentifier(42, 'test_label_parsing')
     l1: LineObject = LineOjectFactory.parse_line(lineid,
                                                  "LABEL = %00001111",
                                                  isa_model)[0]
     self.assertIsInstance(l1, LabelLine)
     self.assertTrue(l1.is_constant, )
     self.assertEqual(l1.get_value(), 0x0F, 'value should be right')
Example #6
0
    def load_line_objects(
                self,
                isa_model: AssemblerModel,
                include_paths: set[str],
                log_verbosity: int,
                assembly_files_used: set = set()
            ) -> list[LineObject]:
        line_objects = []

        with open(self.filename, 'r') as f:
            assembly_files_used.add(self.filename)
            line_num = 0
            current_scope = self.label_scope
            for line in f:
                line_num += 1
                line_id = LineIdentifier(line_num, filename=self.filename)
                line_str = line.strip()
                if len(line_str) > 0:
                    # check to see if this is a #include line
                    if line_str.startswith('#include'):
                        additional_line_objects = self._handle_include_file(
                            line_str,
                            line_id,
                            isa_model,
                            include_paths,
                            log_verbosity,
                            assembly_files_used
                        )
                        line_objects.extend(additional_line_objects)
                        continue
                    if line_str.startswith('#require'):
                        self._handle_require_language(line_str, line_id, isa_model, log_verbosity)
                        continue

                    lobj_list = LineOjectFactory.parse_line(line_id, line_str, isa_model)
                    for lobj in lobj_list:
                        if isinstance(lobj, LabelLine):
                            if not lobj.is_constant and LabelScopeType.get_label_scope(lobj.get_label()) != LabelScopeType.LOCAL:
                                current_scope = LabelScope(LabelScopeType.LOCAL, self.label_scope, lobj.get_label())
                        elif isinstance(lobj, AddressOrgLine):
                            current_scope = self.label_scope
                        lobj.label_scope = current_scope
                        # setting constants now so they can be used when evluating lines later.
                        if isinstance(lobj, LabelLine) and lobj.is_constant:
                            lobj.label_scope.set_label_value(lobj.get_label(), lobj.get_value(), lobj.line_id)
                        line_objects.append(lobj)

        if log_verbosity > 1:
            click.echo(f'Found {len(line_objects)} lines in source file {self.filename}')

        return line_objects
    def test_enumeration_operand(self):
        with pkg_resources.path(config_files,
                                'test_operand_features.yaml') as fp:
            isa_model = AssemblerModel(str(fp), 0)
        lineid = LineIdentifier(21, 'test_enumeration_operand')

        t1 = InstructionLine.factory(lineid, '  tst bee', 'comment', isa_model)
        t1.set_start_address(1)
        t1.label_scope = TestInstructionParsing.label_values
        self.assertIsInstance(t1, InstructionLine)
        self.assertEqual(t1.byte_size, 2, 'has 2 bytes')
        t1.generate_bytes()
        self.assertEqual(list(t1.get_bytes()), [0b00111001, 0xBB],
                         'instruction byte should match')
    def test_numeric_bytecode_operand(self):
        with pkg_resources.path(config_files,
                                'test_operand_features.yaml') as fp:
            isa_model = AssemblerModel(str(fp), 0)
        lineid = LineIdentifier(33, 'test_numeric_bytecode_operand')

        t1 = InstructionLine.factory(lineid, '  tstb x, the_two+1', 'comment',
                                     isa_model)
        t1.set_start_address(1)
        t1.label_scope = TestInstructionParsing.label_values
        self.assertIsInstance(t1, InstructionLine)
        self.assertEqual(t1.byte_size, 1, 'has 1 bytes')
        t1.generate_bytes()
        self.assertEqual(list(t1.get_bytes()), [0b10011011],
                         'instruction byte should match')

        t2 = InstructionLine.factory(lineid, '  tstb a, 7', 'comment',
                                     isa_model)
        t2.set_start_address(1)
        t2.label_scope = TestInstructionParsing.label_values
        self.assertIsInstance(t2, InstructionLine)
        self.assertEqual(t2.byte_size, 1, 'has 1 bytes')
        t2.generate_bytes()
        self.assertEqual(list(t2.get_bytes()), [0b00011111],
                         'instruction byte should match')

        t3 = InstructionLine.factory(lineid, '  enumarg 6', 'comment',
                                     isa_model)
        t3.set_start_address(1)
        t3.label_scope = TestInstructionParsing.label_values
        self.assertIsInstance(t3, InstructionLine)
        self.assertEqual(t3.byte_size, 2, 'has 2 bytes')
        t3.generate_bytes()
        self.assertEqual(list(t3.get_bytes()), [254, 64],
                         'instruction byte should match')

        with self.assertRaises(SystemExit, msg='test bounds'):
            e1 = InstructionLine.factory(lineid, '  tstb b, 14',
                                         'second argument is too large',
                                         isa_model)
            e1.label_scope = TestInstructionParsing.label_values
            e1.generate_bytes()

        with self.assertRaises(SystemExit, msg='test invalid enumerations'):
            e2 = InstructionLine.factory(lineid, '  enumarg 12', 'comment',
                                         isa_model)
            e2.label_scope = TestInstructionParsing.label_values
            e2.generate_bytes()
Example #9
0
    def assemble_bytecode(self):
        global_label_scope = LabelScope.global_scope(self._model.registers)
        # add predefined constants to global scope
        predefines_lineid = LineIdentifier(0,
                                           os.path.basename(self._config_file))
        for predefined_constant in self._model.predefined_constants:
            label: str = predefined_constant['name']
            value: int = predefined_constant['value']
            global_label_scope.set_label_value(label, value, predefines_lineid)

        # create the predefined memory blocks
        predefined_line_obs: list[LineObject] = []
        for predefined_memory in self._model.predefined_memory_blocks:
            label: str = predefined_memory['name']
            address: int = predefined_memory['address']
            value: int = predefined_memory['value']
            byte_length: int = predefined_memory['size']
            # create data object
            data_obj = PredefinedDataLine(predefines_lineid, byte_length,
                                          value, label)
            data_obj.set_start_address(address)
            predefined_line_obs.append(data_obj)
            # set data object's label
            global_label_scope.set_label_value(label, address,
                                               predefines_lineid)

            # add its label to the global scope
        # find base file containing directory
        include_dirs = set([os.path.dirname(self.source_file)] +
                           list(self._include_paths))

        asm_file = AssemblyFile(self.source_file, global_label_scope)
        line_obs: list[LineObject] = asm_file.load_line_objects(
            self._model, include_dirs, self._verbose)

        if self._verbose > 2:
            click.echo(f'Found {len(line_obs)} lines across all source files')

        # First pass: assign addresses to labels
        cur_address = self._model.default_origin
        for lobj in line_obs:
            lobj.set_start_address(cur_address)
            cur_address = lobj.address + lobj.byte_size
            if isinstance(lobj, LabelLine) and not lobj.is_constant:
                lobj.label_scope.set_label_value(lobj.get_label(),
                                                 lobj.get_value(),
                                                 lobj.line_id)

        # now merge prefined line objects and parsed line objects
        line_obs.extend(predefined_line_obs)

        # Sort lines according to their assigned address. This allows for .org directives
        line_obs.sort(key=lambda x: x.address)
        max_generated_address = line_obs[-1].address
        line_dict = {
            lobj.address: lobj
            for lobj in line_obs if isinstance(lobj, LineWithBytes)
        }

        # second pass: build the machine code and check for overlaps
        if self._verbose > 2:
            print("\nProcessing lines:")
        max_instruction_text_size = 0
        byte_code = bytearray()
        last_line = None
        for lobj in line_obs:
            if isinstance(lobj, LineWithBytes):
                lobj.generate_bytes()
            if self._verbose > 2:
                click.echo(
                    f'Processing {lobj.line_id} = {lobj} at address ${lobj.address:x}'
                )
            if len(lobj.instruction) > max_instruction_text_size:
                max_instruction_text_size = len(lobj.instruction)
            if isinstance(lobj, LineWithBytes):
                if last_line is not None and (
                        last_line.address +
                        last_line.byte_size) > lobj.address:
                    print(line_obs)
                    sys.exit(
                        f'ERROR: {lobj.line_id} - Address of byte code at this line overlaps with bytecode from '
                        f'line {last_line.line_id} at address {hex(lobj.address)}'
                    )
                last_line = lobj

        # Finally generate the binaey image
        fill_bytes = bytearray([self._binary_fill_value])
        addr = self._binary_start

        if self._verbose > 2:
            print("\nGenerating byte code:")
        while addr <= (max_generated_address
                       if self._binary_end is None else self._binary_end):
            lobj = line_dict.get(addr, None)
            insertion_bytes = fill_bytes
            if lobj is not None:
                line_bytes = lobj.get_bytes()
                if line_bytes is not None:
                    insertion_bytes = line_bytes
                    if self._verbose > 2:
                        line_bytes_str = binascii.hexlify(
                            line_bytes, sep=' ').decode("utf-8")
                        click.echo(
                            f'Address ${addr:x} : {lobj} bytes = {line_bytes_str}'
                        )
            byte_code.extend(insertion_bytes)
            addr += len(insertion_bytes)

        click.echo(
            f'Writing {len(byte_code)} bytes of byte code to {self._output_file}'
        )
        with open(self._output_file, 'wb') as f:
            f.write(byte_code)

        if self._enable_pretty_print:
            pretty_str = self._pretty_print_results(line_obs,
                                                    max_instruction_text_size)
            if self._pretty_print_output == 'stdout':
                print(pretty_str)
            else:
                with open(self._pretty_print_output, 'w') as f:
                    f.write(pretty_str)
Example #10
0
    def test_label_line_with_instruction(self):
        with pkg_resources.path(config_files,
                                'test_instructions_with_variants.yaml') as fp:
            isa_model = AssemblerModel(str(fp), 0)

        label_values = GlobalLabelScope(isa_model.registers)
        label_values.set_label_value('a_const', 40, 1)

        lineid = LineIdentifier(123, 'test_label_line_with_instruction')

        # test data line on label line
        objs1: list[LineObject] = LineOjectFactory.parse_line(
            lineid, 'the_byte: .byte 0x88 ; label and instruction', isa_model)
        self.assertEqual(len(objs1), 2, 'there should be two instructions')
        self.assertIsInstance(objs1[0], LabelLine,
                              'the first line object should be a label')
        self.assertIsInstance(objs1[1], DataLine,
                              'the first line object should be a data line')
        self.assertEqual(objs1[0].get_label(), 'the_byte',
                         'the label string should match')
        objs1[1].label_scope = label_values
        objs1[1].generate_bytes()
        self.assertEqual(objs1[1].byte_size, 1,
                         'the data value should have 1 byte')
        self.assertEqual(list(objs1[1].get_bytes()), [0x88],
                         'the data value should be [0x88]')

        # test instruction on label line
        objs2: list[LineObject] = LineOjectFactory.parse_line(
            lineid, 'the_instr: mov a, a_const ; label and instruction',
            isa_model)
        self.assertEqual(len(objs2), 2, 'there should be two instructions')
        self.assertIsInstance(objs2[0], LabelLine,
                              'the first line object should be a label')
        self.assertIsInstance(
            objs2[1], InstructionLine,
            'the first line object should be an Instruction line')
        self.assertEqual(objs2[0].get_label(), 'the_instr',
                         'the label string should match')
        objs2[1].label_scope = label_values
        objs2[1].generate_bytes()
        self.assertEqual(objs2[1].byte_size, 2,
                         'the instruction value should have 2 bytes')
        self.assertEqual(list(objs2[1].get_bytes()), [0b01000111, 40],
                         'the instruction bytes should match')

        # labels with no inline instruction should also work
        objs3: list[LineObject] = LineOjectFactory.parse_line(
            lineid, 'the_label: ;just a label', isa_model)
        self.assertEqual(len(objs3), 1, 'there should be two instructions')
        self.assertIsInstance(objs3[0], LabelLine,
                              'the first line object should be a label')
        self.assertEqual(objs3[0].get_label(), 'the_label',
                         'the label string should match')

        # labels with constants should not work
        with self.assertRaises(SystemExit, msg='this instruction should fail'):
            LineOjectFactory.parse_line(
                lineid, 'the_label: const = 3 ; label with constant',
                isa_model)
        # labels with other labels should not work
        with self.assertRaises(SystemExit, msg='this instruction should fail'):
            LineOjectFactory.parse_line(
                lineid,
                'the_label: the_second_label: ; label with another label',
                isa_model)
Example #11
0
    def test_instruction_parsing(self):
        with pkg_resources.path(config_files, 'eater-sap1-isa.yaml') as fp:
            model1 = AssemblerModel(str(fp), 0)

        test_line_id = LineIdentifier(1212, 'test_instruction_parsing')

        pi1 = model1.parse_instruction(test_line_id, 'LDA $f')
        self.assertEqual(pi1.byte_size, 1, 'assembled instruciton is 1 byte')
        self.assertEqual(pi1.get_bytes(TestConfigObject.label_values),
                         bytearray([0x1F]), 'assembled instruction')

        pi2 = model1.parse_instruction(test_line_id, 'add label1+5')
        self.assertEqual(pi2.byte_size, 1, 'assembled instruciton is 1 byte')
        self.assertEqual(pi2.get_bytes(TestConfigObject.label_values),
                         bytearray([0x27]), 'assembled instruction')

        pi3 = model1.parse_instruction(test_line_id, 'out')
        self.assertEqual(pi3.byte_size, 1, 'assembled instruciton is 1 byte')
        self.assertEqual(pi3.get_bytes(TestConfigObject.label_values),
                         bytearray([0xE0]), 'assembled instruction')

        with pkg_resources.path(config_files,
                                'register_argument_exmaple_config.yaml') as fp:
            model2 = AssemblerModel(str(fp), 0)

        piA = model2.parse_instruction(LineIdentifier(1, 'test_mov_a_i'),
                                       'mov a, i')
        self.assertEqual(piA.byte_size, 1, 'assembled instruciton is 1 byte')
        self.assertEqual(list(piA.get_bytes(TestConfigObject.label_values)),
                         [0b01000010], 'assembled instruction')

        piB = model2.parse_instruction(test_line_id, 'mov a,[$1120 + label1]')
        self.assertEqual(piB.byte_size, 3, 'assembled instruciton is 3 byte')
        self.assertEqual(piB.get_bytes(TestConfigObject.label_values),
                         bytearray([0b01000110, 0x22, 0x11]),
                         'assembled instruction')

        piC = model2.parse_instruction(test_line_id, 'add i')
        self.assertEqual(piC.byte_size, 1, 'assembled instruciton is 1 byte')
        self.assertEqual(piC.get_bytes(TestConfigObject.label_values),
                         bytearray([0b10111010]), 'assembled instruction')

        piD = model2.parse_instruction(
            test_line_id, 'mov [$110D + (label1 + LABEL2)] , 0x88')
        self.assertEqual(piD.byte_size, 4, 'assembled instruciton is 4 byte')
        self.assertEqual(piD.get_bytes(TestConfigObject.label_values),
                         bytearray([0b01110111, 0x88, 0xFF, 0x11]),
                         'arguments should be in reverse order')

        piE = model2.parse_instruction(test_line_id,
                                       'mov [sp - label1] , 0x88')
        self.assertEqual(piE.byte_size, 3, 'assembled instruciton is 3 byte')
        self.assertEqual(piE.get_bytes(TestConfigObject.label_values),
                         bytearray([0b01101111, 0x88, 0b11111110]),
                         'arguments should be in reverse order')

        piF = model2.parse_instruction(test_line_id, 'mov [sp+label1] , 0x88')
        self.assertEqual(piF.byte_size, 3, 'assembled instruciton is 3 byte')
        self.assertEqual(piF.get_bytes(TestConfigObject.label_values),
                         bytearray([0b01101111, 0x88, 2]),
                         'arguments should be in reverse order')

        piG = model2.parse_instruction(test_line_id, 'mov [sp] , 0x88')
        self.assertEqual(piG.byte_size, 3, 'assembled instruciton is 3 byte')
        self.assertEqual(piG.get_bytes(TestConfigObject.label_values),
                         bytearray([0b01101111, 0x88, 0]),
                         'arguments should be in reverse order')

        piH = model2.parse_instruction(test_line_id, 'mov [$8000], [label1]')
        self.assertEqual(piH.byte_size, 5, 'assembled instruciton is 5 byte')
        self.assertEqual(piH.get_bytes(TestConfigObject.label_values),
                         bytearray([0b01110110, 2, 0, 0, 0x80]),
                         'arguments should be in reverse order')

        piI = model2.parse_instruction(test_line_id, 'mov [mar], [label1]')
        self.assertEqual(piI.byte_size, 3, 'assembled instruciton is 3 byte')
        self.assertEqual(piI.get_bytes(TestConfigObject.label_values),
                         bytearray([0b01100110, 2, 0]),
                         'no offset should be emitted for [mar]')

        piJ = model2.parse_instruction(test_line_id, 'swap [$8000], [label1]')
        self.assertEqual(piJ.byte_size, 5, 'assembled instruciton is 3 byte')
        self.assertEqual(piJ.get_bytes(TestConfigObject.label_values),
                         bytearray([0b11110110, 0, 0x80, 2, 0]),
                         'arguments should NOT be in reverse order')

        piK = model2.parse_instruction(test_line_id, 'mov [sp+2], [sp+4]')
        self.assertEqual(piK.byte_size, 3, 'assembled instruciton is 3 byte')
        self.assertEqual(piK.get_bytes(TestConfigObject.label_values),
                         bytearray([0b01101101, 4, 2]),
                         'arguments should be in reverse order')

        piL = model2.parse_instruction(test_line_id, 'pop i')
        self.assertEqual(piL.byte_size, 1, 'assembled instruciton is 1 byte')
        self.assertEqual(list(piL.get_bytes(TestConfigObject.label_values)),
                         [0b00001010], 'pop to i')

        piM = model2.parse_instruction(
            LineIdentifier(158, 'test_pop_empty_arg'), 'pop')
        self.assertEqual(piM.byte_size, 1, 'assembled instruciton is 1 byte')
        self.assertEqual(piM.get_bytes(TestConfigObject.label_values),
                         bytearray([0b00001111]), 'just pop')

        piN = model2.parse_instruction(test_line_id, 'mov a, [sp+2]')
        self.assertEqual(piN.byte_size, 2, 'assembled instruciton is 2 byte')
        self.assertEqual(piN.get_bytes(TestConfigObject.label_values),
                         bytearray([0b01000101, 2]), 'just move [sp+2] into a')

        with self.assertRaises(
                SystemExit,
                msg='should error on unallowed operand combinations'):
            model2.parse_instruction(test_line_id, 'mov a, a')
        with self.assertRaises(SystemExit, msg='[mar] should have no offset'):
            model2.parse_instruction(test_line_id, 'mov [mar+2], [label1]')
        with self.assertRaises(SystemExit,
                               msg='should error due to too many operands'):
            model2.parse_instruction(test_line_id, 'mov a, i, j')
        with self.assertRaises(SystemExit,
                               msg='should error due to too many operands'):
            model2.parse_instruction(test_line_id, 'nop 123')
        with self.assertRaises(SystemExit,
                               msg='should error due to too few operands'):
            model2.parse_instruction(test_line_id, 'mov a')