def setup(self): """Init state.""" super().setup() self.ram = RandomAccessMemory(HALF_SIZE, 2 ** HALF_SIZE, 'big', is_protected=True) self.control_unit = ControlUnitM(WORD_SIZE, HALF_SIZE, self.registers, self.ram, self.alu, WORD_SIZE) self.operand_size = WORD_SIZE self.address_size = 2 * BYTE_SIZE assert self.control_unit.opcodes == {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x10, 0x11, 0x13, 0x14, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x33, 0x34, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x93, 0x94, 0x95, 0x96, 0x99}
class TestControlUnitM(TBCU2): """Test case for Address Modification Model Machine Control Unit.""" def setup(self): """Init state.""" super().setup() self.ram = RandomAccessMemory(HALF_SIZE, 2 ** HALF_SIZE, 'big', is_protected=True) self.control_unit = ControlUnitM(WORD_SIZE, HALF_SIZE, self.registers, self.ram, self.alu, WORD_SIZE) self.operand_size = WORD_SIZE self.address_size = 2 * BYTE_SIZE assert self.control_unit.opcodes == {0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x10, 0x11, 0x13, 0x14, 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x33, 0x34, 0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x93, 0x94, 0x95, 0x96, 0x99} def test_const(self): super().test_const() assert self.control_unit.OPCODES["addr"] == OP_ADDR assert self.control_unit.OPCODES["rmove"] == OP_RMOVE assert self.control_unit.OPCODES["radd"] == OP_RADD assert self.control_unit.OPCODES["rsub"] == OP_RSUB assert self.control_unit.OPCODES["rsmul"] == OP_RSMUL assert self.control_unit.OPCODES["rsdivmod"] == OP_RSDIVMOD assert self.control_unit.OPCODES["rcomp"] == OP_RCOMP assert self.control_unit.OPCODES["rumul"] == OP_RUMUL assert self.control_unit.OPCODES["rudivmod"] == OP_RUDIVMOD def run_fetch(self, value, opcode, instruction_size, r2=True): """Run one fetch test.""" address1 = 10 address2 = 42 self.ram.put(address1, value, instruction_size) increment = instruction_size // self.ram.word_size # pylint: disable=no-member self.registers.fetch.reset_mock() self.registers.put.reset_mock() def get_register(name, size): """Get PC.""" if name == "PC": assert size == 2 * BYTE_SIZE return address1 elif name == "R2": assert size == WORD_SIZE return address2 else: raise KeyError() self.registers.fetch.side_effect = get_register self.control_unit.fetch_and_decode() if r2: self.registers.fetch.assert_has_calls([call("PC", 2 * BYTE_SIZE), call("R2", WORD_SIZE)]) else: self.registers.fetch.assert_any_call("PC", 2 * BYTE_SIZE) self.registers.put.assert_has_calls([call("RI", value, WORD_SIZE), call("PC", address1 + increment, 2 * BYTE_SIZE)]) assert self.control_unit.opcode == opcode def test_fetch_and_decode(self): """Right fetch and decode is a half of business.""" for opcode in set(range(2 ** BYTE_SIZE)) - self.control_unit.opcodes: with raises(ValueError): self.run_fetch(opcode << BYTE_SIZE, opcode, 2 * BYTE_SIZE) for opcode in ARITHMETIC_OPCODES | JUMP_OPCODES | {OP_COMP, OP_LOAD, OP_ADDR, OP_STORE}: self.control_unit.register1 = None self.control_unit.register2 = None self.control_unit.address = None self.run_fetch(opcode << 24 | 0x120014, opcode, 32) assert self.control_unit.register1 == 'R1' assert self.control_unit.register2 is None assert self.control_unit.address == 0x14 + 42 for opcode in REGISTER_OPCODES: self.control_unit.register1 = None self.control_unit.register2 = None self.control_unit.address = None self.run_fetch(opcode << 8 | 0x12, opcode, 16, r2=False) assert self.control_unit.register1 == 'R1' assert self.control_unit.register2 == 'R2' assert self.control_unit.address is None for opcode in {OP_HALT}: self.control_unit.register1 = None self.control_unit.register2 = None self.control_unit.address = None self.run_fetch(opcode << 8 | 0x12, opcode, 16, r2=False) assert self.control_unit.register1 is None assert self.control_unit.register2 is None assert self.control_unit.address is None def test_load(self): """R1 := [A1], R2 := [A2].""" register1, val1 = 'R3', 123456 register2, val2 = 'R4', 654321 address, val3 = 10, 111111 def get_register(name, size): """Get PC.""" assert size == WORD_SIZE if name == register1: return val1 elif name == register2: return val2 else: raise KeyError() self.registers.fetch.side_effect = get_register self.control_unit.address = address self.control_unit.register1 = register1 self.control_unit.register2 = register2 self.ram.put(address, val3, WORD_SIZE) # pylint: disable=no-member for opcode in ARITHMETIC_OPCODES | {OP_LOAD, OP_COMP}: self.registers.fetch.reset_mock() self.registers.put.reset_mock() self.control_unit.opcode = opcode self.control_unit.load() self.registers.fetch.assert_called_once_with(register1, WORD_SIZE) self.registers.put.assert_has_calls([call("S", val1, WORD_SIZE), call("RZ", val3, WORD_SIZE)]) for opcode in {OP_STORE}: self.registers.fetch.reset_mock() self.registers.put.reset_mock() self.control_unit.opcode = opcode self.control_unit.load() self.registers.fetch.assert_called_once_with(register1, WORD_SIZE) self.registers.put.assert_called_once_with("S", val1, WORD_SIZE) for opcode in REGISTER_OPCODES: self.registers.fetch.reset_mock() self.registers.put.reset_mock() self.control_unit.opcode = opcode self.control_unit.load() self.registers.fetch.assert_has_calls([call(register1, WORD_SIZE), call(register2, WORD_SIZE)]) self.registers.put.assert_has_calls([call("S", val1, WORD_SIZE), call("RZ", val2, WORD_SIZE)]) for opcode in {OP_ADDR}: self.registers.fetch.reset_mock() self.registers.put.reset_mock() self.control_unit.opcode = opcode self.control_unit.load() assert not self.registers.fetch.called self.registers.put.assert_called_once_with("S", address, WORD_SIZE) for opcode in CONDJUMP_OPCODES | {OP_JUMP}: self.registers.fetch.reset_mock() self.registers.put.reset_mock() self.control_unit.opcode = opcode self.control_unit.load() assert not self.registers.fetch.called self.registers.put.assert_called_once_with("ADDR", address, 2 * BYTE_SIZE) for opcode in {OP_HALT}: self.registers.fetch.reset_mock() self.registers.put.reset_mock() self.control_unit.opcode = opcode self.control_unit.load() assert not self.registers.fetch.called assert not self.registers.put.called def test_basic_execute(self, should_move=None): """Test basic operations.""" super().test_basic_execute(should_move=should_move) # pylint: disable=no-member self.control_unit.opcode = OP_MOVE self.alu.move.reset_mock() self.control_unit.execute() self.alu.move.assert_called_once_with('R2', 'S') self.control_unit.opcode = OP_ADDR self.alu.move.reset_mock() self.control_unit.execute() assert not self.alu.move.called def run_write_back(self, should, opcode): """Run write back method for specific opcode.""" print(hex(opcode), should) register1, next_register1, register2 = 'R5', 'R6', 'R8' res_register1, val1 = 'S', 123456 res_register2, val2 = 'RZ', 654321 address, canary = 10, 0 def get_register(name, size): """Get PC.""" assert size == self.operand_size if name == res_register1: return val1 elif name == res_register2: return val2 else: raise KeyError() self.registers.fetch.side_effect = get_register self.control_unit.address = address self.control_unit.register1 = register1 self.control_unit.register2 = register2 self.ram.put(address, canary, self.operand_size) # pylint: disable=no-member self.registers.fetch.reset_mock() self.registers.put.reset_mock() self.control_unit.opcode = opcode self.control_unit.write_back() if should == 'two_registers': self.registers.fetch.assert_has_calls([call(res_register1, self.operand_size), call(res_register2, self.operand_size)]) self.registers.put.assert_has_calls([call(register1, val1, self.operand_size), call(next_register1, val2, self.operand_size)]) assert self.ram.fetch(address, self.operand_size) == canary elif should == 'register': self.registers.fetch.assert_called_once_with(res_register1, self.operand_size) self.registers.put.assert_called_once_with(register1, val1, self.operand_size) assert self.ram.fetch(address, self.operand_size) == canary elif should == 'memory': self.registers.fetch.assert_called_once_with(res_register1, self.operand_size) assert not self.registers.put.called assert self.ram.fetch(address, self.operand_size) == val1 else: assert not self.registers.fetch.called assert not self.registers.put.called assert self.ram.fetch(address, self.operand_size) == canary def test_write_back(self): """Test write back result to the memory.""" for opcode in {OP_SDIVMOD, OP_UDIVMOD}: self.run_write_back('two_registers', opcode) for opcode in (ARITHMETIC_OPCODES | {OP_ADDR, OP_LOAD}) - \ {OP_SDIVMOD, OP_UDIVMOD}: self.run_write_back('register', opcode) for opcode in {OP_STORE}: self.run_write_back('memory', opcode) for opcode in (CONDJUMP_OPCODES | {OP_HALT, OP_JUMP, OP_COMP}): self.run_write_back('nothing', opcode) def test_step(self): """Test step cycle.""" self.control_unit.registers = self.registers = RegisterMemory() for register in {'RI', 'RZ', 'S', 'R0', 'R1', 'R2', 'R3', 'R4', 'R5', 'R6', 'R7', 'R8', 'R9', 'RA', 'RB', 'RC', 'RD', 'RE', 'RF'}: self.registers.add_register(register, self.operand_size) self.alu = ArithmeticLogicUnit(self.registers, self.control_unit.register_names, self.operand_size, self.address_size) self.control_unit.alu = self.alu canary = 0 self.ram.put(0x0000, 0x00000100, WORD_SIZE) self.ram.put(0x0002, 0x0300000C, WORD_SIZE) self.ram.put(0x0004, 0x0400000E, WORD_SIZE) self.ram.put(0x0006, 0x02100102, WORD_SIZE) self.ram.put(0x0008, 0x2311, 2 * BYTE_SIZE) self.ram.put(0x0009, 0x10100104, WORD_SIZE) self.ram.put(0x000B, 0x9900, 2 * BYTE_SIZE) self.ram.put(0x000C, 0xffffffeb, WORD_SIZE) self.ram.put(0x000E, 0x00000032, WORD_SIZE) self.ram.put(0x0100, -123 % 2 ** WORD_SIZE, WORD_SIZE) self.ram.put(0x0102, 456, WORD_SIZE) self.ram.put(0x0104, canary, WORD_SIZE) self.registers.put("PC", 0, 2 * BYTE_SIZE) self.control_unit.step() assert self.registers.fetch("R0", WORD_SIZE) == -123 % 2 ** WORD_SIZE assert self.registers.fetch("R1", WORD_SIZE) == 0 assert self.registers.fetch("PC", 2 * BYTE_SIZE) == 0x02 assert self.ram.fetch(0x0104, WORD_SIZE) == canary assert self.control_unit.get_status() == RUNNING self.control_unit.step() assert self.registers.fetch("R0", WORD_SIZE) == (21 * 123) assert self.registers.fetch("R1", WORD_SIZE) == 0 assert self.registers.fetch("PC", 2 * BYTE_SIZE) == 0x04 assert self.ram.fetch(0x0104, WORD_SIZE) == canary assert self.control_unit.get_status() == RUNNING self.control_unit.step() assert self.registers.fetch("R0", WORD_SIZE) == (21 * 123) // 50 x = 21 * 123 % 50 assert self.registers.fetch("R1", WORD_SIZE) == x assert self.registers.fetch("PC", 2 * BYTE_SIZE) == 0x06 assert self.ram.fetch(0x0104, WORD_SIZE) == canary assert self.control_unit.get_status() == RUNNING self.control_unit.step() assert self.registers.fetch("R0", WORD_SIZE) == (21 * 123) // 50 assert self.registers.fetch("R1", WORD_SIZE) == (x - 456) % 2 ** WORD_SIZE assert self.registers.fetch("PC", 2 * BYTE_SIZE) == 0x08 assert self.ram.fetch(0x0104, WORD_SIZE) == canary assert self.control_unit.get_status() == RUNNING self.control_unit.step() assert self.registers.fetch("R0", WORD_SIZE) == (21 * 123) // 50 assert self.registers.fetch("R1", WORD_SIZE) == (x - 456) ** 2 assert self.registers.fetch("PC", 2 * BYTE_SIZE) == 0x09 assert self.ram.fetch(0x0104, WORD_SIZE) == canary assert self.control_unit.get_status() == RUNNING self.control_unit.step() assert self.registers.fetch("R0", WORD_SIZE) == (21 * 123) // 50 assert self.registers.fetch("R1", WORD_SIZE) == (x - 456) ** 2 assert self.registers.fetch("PC", 2 * BYTE_SIZE) == 0x0b assert self.ram.fetch(0x0104, WORD_SIZE) == (x - 456) ** 2 assert self.control_unit.get_status() == RUNNING self.control_unit.step() assert self.registers.fetch("R0", WORD_SIZE) == (21 * 123) // 50 assert self.registers.fetch("R1", WORD_SIZE) == (x - 456) ** 2 assert self.registers.fetch("PC", 2 * BYTE_SIZE) == 0x0C assert self.control_unit.get_status() == HALTED def test_run(self): """Very simple program.""" self.control_unit.registers = self.registers = RegisterMemory() for register in {'RI', 'RZ', 'S', 'R0', 'R1', 'R2', 'R3', 'R4', 'R5', 'R6', 'R7', 'R8', 'R9', 'RA', 'RB', 'RC', 'RD', 'RE', 'RF'}: self.registers.add_register(register, self.operand_size) self.alu = ArithmeticLogicUnit(self.registers, self.control_unit.register_names, self.operand_size, self.address_size) self.control_unit.alu = self.alu self.ram.put(0x0000, 0x00000100, WORD_SIZE) self.ram.put(0x0002, 0x0300000C, WORD_SIZE) self.ram.put(0x0004, 0x0400000E, WORD_SIZE) self.ram.put(0x0006, 0x02100102, WORD_SIZE) self.ram.put(0x0008, 0x2311, 2 * BYTE_SIZE) self.ram.put(0x0009, 0x10100104, WORD_SIZE) self.ram.put(0x000B, 0x9900, 2 * BYTE_SIZE) self.ram.put(0x000C, 0xffffffeb, WORD_SIZE) self.ram.put(0x000E, 0x00000032, WORD_SIZE) self.ram.put(0x0100, 0xffffff85, WORD_SIZE) self.ram.put(0x0102, 0x000001c8, WORD_SIZE) self.registers.put("PC", 0, 2 * BYTE_SIZE) self.control_unit.run() assert self.ram.fetch(0x0104, WORD_SIZE) == 178929 assert self.registers.fetch("PC", 2 * BYTE_SIZE) == 0x000C assert self.control_unit.get_status() == HALTED def test_minimal_run(self): """Minimal program.""" self.control_unit.registers = self.registers = RegisterMemory() self.registers.add_register('RI', self.operand_size) self.alu = ArithmeticLogicUnit(self.registers, self.control_unit.register_names, self.operand_size, self.address_size) self.control_unit.alu = self.alu self.ram.put(0x00, 0x9900, 2 * BYTE_SIZE) self.registers.put("PC", 0, 2 * BYTE_SIZE) self.control_unit.run() assert self.registers.fetch("PC", 2 * BYTE_SIZE) == 0x01 assert self.control_unit.get_status() == HALTED