├── .gitignore ├── README.md ├── examples ├── hellow.asm ├── strrev.asm ├── test.asm └── toupper.asm ├── i4004web.py ├── intel4004_emu ├── __init__.py ├── consolex.py ├── executor.py ├── str_executor_facade.py └── translator.py ├── main.py └── test ├── test_assmbly.py └── test_str_executor_facade.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Intel 4004 emulator 2 | 3 | It is a Python emulator of Intel 4004 cpu - created for solving [these programming puzzles](http://www.codeabbey.com/index/task_list/assembly). 4 | 5 | It could be run from command line like this: 6 | 7 | $ python main.py examples/test.asm 8 | 9 | Documentation [in form of wiki](https://github.com/CodeAbbey/intel4004-emu/wiki) is currently in state of being created. You can already read about most essential instructions there and see examples in our online emulator. 10 | 11 | [Online version](http://www.codeabbey.com/index/wiki/intel-4004-emulator) 12 | is based on the same files but gives you an opportunity to try it without downloading and running in the console. 13 | -------------------------------------------------------------------------------- /examples/hellow.asm: -------------------------------------------------------------------------------- 1 | ; put data in the beginning since address should be 0..255 2 | 3 | jun start 4 | 5 | hw_string: 6 | db 'Hello, World!' 13 10 0 ;caps not work now 7 | 8 | start: 9 | fim r0 hw_string 10 | 11 | next_char: 12 | fin r2 13 | ld r3 14 | jcn an not_end 15 | ld r2 16 | jcn az finish 17 | not_end: 18 | jms $3e0 19 | ld r1 20 | iac 21 | xch r1 22 | tcc 23 | add r0 24 | xch r0 25 | jun next_char 26 | 27 | finish: 28 | nop 29 | 30 | -------------------------------------------------------------------------------- /examples/strrev.asm: -------------------------------------------------------------------------------- 1 | fim r4 0 ; memory index (r4:r5) set to 0 2 | 3 | ;============= 4 | ; "input string" part 5 | 6 | main: 7 | jms $3f0 ; input character (C) 8 | 9 | ld r2 ; load high-nibble of C 10 | jcn az input_done ; if it is 0 (e.g. newline was read) stop input 11 | 12 | src r4 ; set memory pointer from r4:r5 13 | ld r2 ; load high-nibble of C 14 | wrm ; write it to memory 15 | jms inc_index ; call increment mem-pointer subroutine 16 | 17 | src r4 ; set memory pointer again 18 | ld r3 ; load low-nibble of C 19 | wrm ; write it to memory 20 | jms inc_index ; increment mem-pointer 21 | 22 | jun main ; repeat for next character 23 | 24 | 25 | ;============= 26 | ; "reverse string" part 27 | 28 | input_done: ; two labels at the same place for convenience 29 | reverse: 30 | ld r5 ; load low-nibble of mem-pointer 31 | jcn an cont ; if it is not zero, skip further check 32 | ld r4 ; load high-nibble of mem-pointer 33 | jcn az finish ; if it is zero (i.e. r4:r5 = 0) then exit this loop 34 | 35 | cont: 36 | jms dec_index ; decrement mem-pointer 37 | src r4 ; set memory-pointer 38 | rdm ; read memory to acc 39 | xch r3 ; move this value (low-nibble of C) to r3 40 | jms dec_index ; decrement mem-pointer 41 | src r4 ; set memory-pointer 42 | rdm ; read memory to acc 43 | xch r2 ; move this value (high-nibble of C) to r2 44 | jms $3e0 ; print character from r2:r3 45 | jun reverse ; go to beginning of the loop 46 | 47 | ;============= 48 | ; a couple of subroutines 49 | 50 | inc_index: 51 | ld r5 ; acc = r5 52 | iac ; acc += 1 53 | xch r5 ; r5 = acc 54 | tcc ; acc = carry 55 | add r4 ; acc += r4 56 | xch r4 ; r4 = acc (so r4 is incremented by carry of previous addition) 57 | bbl 0 ; return 58 | 59 | dec_index: 60 | ld r5 ; acc = r5 61 | dac ; acc -= 1 62 | xch r5 ; r5 = acc 63 | cmc ; invert carry (it serves as "borrow" flag) 64 | ldm 0 ; acc = 0 65 | xch r4 ; exchange r4 with acc 66 | sub r4 ; subtract r4 (which is 0) and carry (borrow) from acc 67 | xch r4 ; r4 = acc 68 | bbl 0 ; return 69 | 70 | ;============== 71 | ; end here 72 | 73 | finish: 74 | fim r2 $0d ; load carriage-return to r2:r3 75 | jms $3e0 ; print it 76 | fim r2 $0a ; and also print new-line 77 | jms $3e0 -------------------------------------------------------------------------------- /examples/test.asm: -------------------------------------------------------------------------------- 1 | ;run 'python main.py test.asm N' 2 | ;where N is some value 3 | ;program will calculate sum of 1 + 2 + ... + N 4 | ;in registers r2 and r3 5 | ;by default N = 5 6 | 7 | ldm 0 ; acc = 0 8 | add r0 ; acc += r0 9 | jcn an main_loop ; if acc == 0 jump to main_loop 10 | 11 | ldm 5 ; acc = 5 12 | xch r0 ; store it to r0 13 | 14 | main_loop: 15 | clc ; clear carry 16 | ld r0 ; acc = r0 17 | add r2 ; acc += r2 18 | xch r2 ; store result to r2 19 | tcc ; acc = carry 20 | add r3 ; acc += r3 + carry 21 | xch r3 ; store result to r3 22 | 23 | ld r0 ; acc = r0 24 | dac ; acc -= 1 25 | jcn az end ; if acc == 0 jump to end 26 | xch r0 ; exchange acc to r0 27 | jun main_loop ; jump to main_loop 28 | 29 | end: 30 | jms $3ff ; call custom subroutine for printing regs 31 | -------------------------------------------------------------------------------- /examples/toupper.asm: -------------------------------------------------------------------------------- 1 | ; this program reads characters from console 2 | ; and prints them back, converting to uppercase 3 | ; i.e. characters with codes in ranghe 0x60 <= ord(c) < 0x80 4 | ; are converted (by subtracting 0x20), while others remain unchanged 5 | 6 | fim r4 $68 ; load values r4 = 6, r5 = 8 used for comparison 7 | 8 | loop: 9 | jms $3f0 ; read character into r2:r3 (call to custom subroutine) 10 | 11 | ld r2 ; acc = higher 4 bits from r2 12 | jcn az finish ; if acc = 0 then go to end 13 | 14 | clc ; clear carry flag 15 | sub r4 ; subtract r4 = 6 from acc 16 | jcn c0 skip_conv ; if carry = 0 (i.e. there was a borrow because r2 < 6) skip conversion 17 | 18 | ld r2 ; acc = r2 again 19 | clc ; clear carry 20 | sub r5 ; subtract r5 = 8 from acc 21 | jcn c1 skip_conv ; if carry = 1 (i.e. there was no borrow, r2 >= 8) skip conversion 22 | 23 | do_convert: ; no jumps to this label, it simply marks logical fragment 24 | ld r2 ; acc = r2 (higher 4 bits of the character) 25 | dac ; acc -= 1 26 | dac ; acc -= 1 27 | xch r2 ; move decremented value back to r2 so r2:r3 is reduced by 0x20 28 | 29 | skip_conv: 30 | jms $3e0 ; print character from r2:r3 (call to custom subroutine) 31 | jun loop ; jump back to repeat the loop 32 | 33 | finish: 34 | fim r2 $0d ; load the code of carriage return (0x0D) to r2:r3 35 | jms $3e0 ; print it 36 | fim r2 $0a ; also print newline (0x0A) character 37 | jms $3e0 -------------------------------------------------------------------------------- /i4004web.py: -------------------------------------------------------------------------------- 1 | #!/usr/local/bin/python2.7 2 | 3 | import sys 4 | import re 5 | try: 6 | import translator 7 | import executor 8 | except ImportError: 9 | from intel4004_emu import translator 10 | from intel4004_emu import executor 11 | 12 | class EnhancedExecutor(executor.Executor): 13 | 14 | def __init__(self): 15 | super(EnhancedExecutor, self).__init__() 16 | self.inputCount = 0 17 | self.instructions = 0 18 | 19 | def step(self, line): 20 | super(EnhancedExecutor, self).step(line) 21 | self.instructions += 1 22 | if self.instructions > 1000000: 23 | raise Exception("Million instructions limit reached!") 24 | 25 | def printRegs(self): 26 | print(' '.join([("%x" % r) for r in self.regs])) 27 | 28 | def fetchState(self, data): 29 | data = data.strip() 30 | r = re.compile('^[0-9a-f](?:\s[0-9a-f]){15}$') 31 | if not r.match(data): 32 | return False 33 | data = data.split() 34 | for i in range(len(self.regs)): 35 | self.regs[i] = int(data[i], 16) 36 | return True 37 | 38 | def c_3f0(self): 39 | v = 0 if self.inputCount >= len(self.inputData) else ord(self.inputData[self.inputCount]) 40 | self.inputCount += 1 41 | self.regs[3] = v & 0xF 42 | self.regs[2] = v >> 4 43 | 44 | def c_3e0(self): 45 | v = (self.regs[2] << 4) + self.regs[3] 46 | sys.stdout.write(chr(v)) 47 | 48 | def loadSource(): 49 | texts = sys.stdin.read() 50 | text = texts.splitlines() 51 | if len(text) < 3: 52 | raise Exception('improper script invocation') 53 | inputCount = int(text[0].strip()) 54 | return (text[inputCount + 1:], text[1 : inputCount + 1]) 55 | 56 | def main(): 57 | from StringIO import StringIO 58 | import sys 59 | print("Content-Type: text/plain") 60 | 61 | try: 62 | src, inputData = loadSource() 63 | prg = translator.translate(src) 64 | print("Program-Size: " + str(prg['_top'])) 65 | cpu = EnhancedExecutor() 66 | if len(inputData) == 1: 67 | if cpu.fetchState(inputData[0]): 68 | inputData = [] 69 | cpu.inputData = '\n'.join(inputData) 70 | old_stdout = sys.stdout 71 | my_stdout = StringIO() 72 | sys.stdout = my_stdout 73 | cpu.run(prg) 74 | sys.stdout = old_stdout 75 | print("Program-Cycles: " + str(cpu.cycles)) 76 | print('') 77 | result = my_stdout.getvalue() 78 | sys.stdout.write(result) 79 | if len(inputData) == 0: 80 | cpu.printRegs() 81 | except Exception as e: 82 | sys.stdout = old_stdout 83 | print('') 84 | print("Error: %s\n" % e) 85 | 86 | main() 87 | -------------------------------------------------------------------------------- /intel4004_emu/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/CodeAbbey/intel4004-emu/fb31d89d1b5f03dfc236c7522fbd921e3320de17/intel4004_emu/__init__.py -------------------------------------------------------------------------------- /intel4004_emu/consolex.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | class Consolex: 4 | 5 | def c_3f0(self): 6 | ch = sys.stdin.read(1) 7 | v = ord(ch) if len(ch) > 0 else 0 8 | self.regs[3] = v & 0xF 9 | self.regs[2] = v >> 4 10 | 11 | def c_3e0(self): 12 | v = (self.regs[2] << 4) + self.regs[3] 13 | sys.stdout.write(chr(v)) 14 | -------------------------------------------------------------------------------- /intel4004_emu/executor.py: -------------------------------------------------------------------------------- 1 | class Executor(object): 2 | 3 | def __init__(self): 4 | self.acc = 0 5 | self.regs = [0] * 16 6 | self.cy = 0 7 | self.memory = [0] * 256 8 | self.dp = 0 9 | self.ip = 0 10 | self.stack = [] 11 | self.cycles = 0 12 | 13 | def run(self, prg): 14 | self.prg = prg 15 | while self.ip in prg: 16 | self.step(prg[self.ip]) 17 | 18 | def step(self, line): 19 | self.ip += line.size 20 | cmd = getattr(self, 'i_' + line.opcode) 21 | cmd(line.params) 22 | self.cycles += line.size 23 | 24 | def jump(self, param): 25 | if type(param) != int: 26 | raise Exception('Label address not resolved: ' + str(param)) 27 | self.ip = param 28 | 29 | def i_add(self, params): 30 | p = params[0] 31 | self.acc = self.acc + self.regs[p] + self.cy 32 | self.cy = self.acc >> 4 33 | self.acc &= 0xF 34 | 35 | def i_adm(self, params): 36 | self.acc = self.acc + self.memory[self.dp] + self.cy 37 | self.cy = self.acc >> 4 38 | self.acc &= 0xF 39 | 40 | def i_bbl(self, params): 41 | self.jump(self.stack.pop()) 42 | self.acc = params[0] & 0xF 43 | 44 | def i_clb(self, params): 45 | self.acc = 0 46 | self.cy = 0 47 | 48 | def i_clc(self, params): 49 | self.cy = 0 50 | 51 | def i_cma(self, params): 52 | self.acc ^= 0xF 53 | 54 | def i_cmc(self, params): 55 | self.cy ^= 1 56 | 57 | def i_daa(self, params): 58 | if self.acc >= 10 or self.cy: 59 | self.acc += 6 60 | if self.acc >= 16: 61 | self.cy = 1 62 | self.acc -= 16 63 | 64 | def i_dac(self, params): 65 | self.acc = (self.acc - 1) & 0xF 66 | self.cy = 1 if self.acc != 15 else 0 67 | 68 | def i_fim(self, params): 69 | p = params[0] & 0xE 70 | v = params[1] 71 | self.regs[p] = (v >> 4) & 0xF 72 | self.regs[p + 1] = v & 0xF 73 | 74 | def i_fin(self, params): 75 | p = params[0] & 0xE 76 | addr = 'd' + str(self.regs[0] * 16 + self.regs[1]) 77 | if addr in self.prg: 78 | v = self.prg[addr] 79 | self.regs[p] = (v >> 4) & 0xF 80 | self.regs[p + 1] = v & 0xF 81 | self.cycles += 1 82 | else: 83 | raise Exception("Attempt to read the data from uninitialized ROM address %s" % addr[1:]) 84 | 85 | def i_iac(self, params): 86 | self.acc = (self.acc + 1) & 0xF 87 | self.cy = 1 if self.acc == 0 else 0 88 | 89 | def i_jcn(self, params): 90 | op = params[0] 91 | if op not in ['c0', 'c1', 'az', 'an']: 92 | raise Exception("Unknown jump condition '%s'!" % op); 93 | if op[0] == 'c': 94 | if str(self.cy) != op[1]: 95 | return 96 | elif op == 'az' and self.acc != 0: 97 | return 98 | elif op == 'an' and self.acc == 0: 99 | return 100 | self.jump(params[1]) 101 | 102 | def i_jms(self, params): 103 | addr = params[0] 104 | if type(addr) != int: 105 | raise Exception('Subroutine address not resolved: ' + str(addr)) 106 | if addr < 0x300: 107 | self.stack.append(self.ip) 108 | self.jump(params[0]) 109 | else: 110 | try: 111 | subr = getattr(self, "c_%0.3x" % addr) 112 | except AttributeError: 113 | raise Exception("No custom subroutine for address %0.3x was defined!" % addr) 114 | subr() 115 | 116 | def i_jun(self, params): 117 | self.jump(params[0]) 118 | 119 | def i_inc(self, params): 120 | p = params[0] 121 | self.regs[p] = (self.regs[p] + 1) & 0xF 122 | 123 | def i_isz(self, params): 124 | p = params[0] 125 | self.regs[p] = (self.regs[p] + 1) & 0xF 126 | if self.regs[p] != 0: 127 | self.jump(params[1]) 128 | 129 | def i_ld(self, params): 130 | self.acc = self.regs[params[0]] 131 | 132 | def i_ldm(self, params): 133 | self.acc = params[0] & 0xF 134 | 135 | def i_nop(self, params): 136 | pass 137 | 138 | def i_ral(self, params): 139 | self.acc = (self.acc << 1) + self.cy 140 | self.cy = self.acc >> 4 141 | self.acc &= 0xF 142 | 143 | def i_rar(self, params): 144 | cy = self.acc & 1 145 | self.acc = (self.acc >> 1) + (self.cy << 3) 146 | self.cy = cy 147 | 148 | def i_rdm(self, params): 149 | self.acc = self.memory[self.dp] 150 | 151 | def i_src(self, params): 152 | p = params[0] & 0xE 153 | self.dp = (self.regs[p] << 4) + self.regs[p + 1] 154 | 155 | def i_stc(self, params): 156 | self.cy = 1 157 | 158 | def i_sub(self, params): 159 | p = params[0] 160 | self.acc = self.acc + self.cy + (self.regs[p] ^ 0xF) 161 | self.cy = self.acc >> 4 162 | self.acc &= 0xF 163 | 164 | def i_sbm(self, params): 165 | self.acc = self.acc + 16 - self.memory[self.dp] - self.cy 166 | self.cy = self.acc >> 4 167 | self.acc &= 0xF 168 | 169 | def i_tcc(self, params): 170 | self.acc = self.cy 171 | self.cy = 0 172 | 173 | def i_tcs(self, params): 174 | self.acc = 9 + self.cy 175 | self.cy = 0 176 | 177 | def i_wrm(self, params): 178 | self.memory[self.dp] = self.acc 179 | 180 | def i_xch(self, params): 181 | p = params[0] 182 | self.regs[p], self.acc = self.acc, self.regs[p] 183 | -------------------------------------------------------------------------------- /intel4004_emu/str_executor_facade.py: -------------------------------------------------------------------------------- 1 | from intel4004_emu import executor 2 | from intel4004_emu import translator 3 | 4 | 5 | class ExecutorExt(executor.Executor): 6 | def __init__(self): 7 | super(executor.Executor, self).__init__() 8 | self.console_input = [] 9 | self.console_output = [] 10 | 11 | def clear_memory(self): 12 | self.acc = 0 13 | self.regs = [0] * 16 14 | self.cy = 0 15 | self.memory = [0] * 256 16 | 17 | def clear_stack(self): 18 | self.dp = 0 19 | self.ip = 0 20 | self.stack = [] 21 | self.console_input = [] 22 | self.console_output = [] 23 | 24 | @property 25 | def regs2str(self): 26 | return ' '.join('{:x}'.format(x) for x in self.regs) 27 | 28 | @property 29 | def regs2str_ex(self): 30 | return '{}, acc={}, cy={}'.format(' '.join('{:x}'.format(x) for x in self.regs), self.acc, self.cy) 31 | 32 | def c_3f0(self): 33 | if self.console_input: 34 | v = ord(self.console_input.pop(0)) 35 | else: 36 | v = 0 37 | self.regs[3] = v & 0xF 38 | self.regs[2] = v >> 4 39 | 40 | def c_3e0(self): 41 | v = (self.regs[2] << 4) + self.regs[3] 42 | self.console_output.append(chr(v)) 43 | 44 | 45 | class StrExecutorFacade(object): 46 | executor_class = ExecutorExt 47 | 48 | def __init__(self, src=None): 49 | self.cpu = self.executor_class() 50 | self.prg = None 51 | if src is not None: 52 | self.load_assembly(src) 53 | 54 | def load_assembly(self, src): 55 | self.prg = translator.translate(src.splitlines()) 56 | 57 | def run(self, state=None, console=None, clear=True): 58 | if self.prg is None: 59 | raise RuntimeError('Program is not loaded. Execute load_assembly() before.') 60 | self.cpu.clear_stack() 61 | if clear: 62 | self.cpu.clear_memory() 63 | if console: 64 | self.cpu.console_input = list(console) 65 | if state is not None: 66 | self.cpu.regs = [int(x, 16) for x in state.split()] 67 | self.cpu.run(self.prg) 68 | return self 69 | 70 | @property 71 | def regs2str(self): 72 | return self.cpu.regs2str 73 | 74 | @property 75 | def regs2str_ex(self): 76 | return self.cpu.regs2str_ex 77 | 78 | @property 79 | def console_output(self): 80 | return ''.join(self.cpu.console_output) 81 | 82 | 83 | class ExecutorExtDebug(ExecutorExt): 84 | 85 | def step(self, line): 86 | super().step(line) 87 | print('{:<16} {}'.format(str(line), self.regs2str_ex)) 88 | 89 | def run(self, prg, limit=50): 90 | i = 0 91 | print('Before: {}'.format(self.regs2str_ex)) 92 | while self.ip in prg: 93 | self.step(prg[self.ip]) 94 | i += 1 95 | if i > limit: 96 | break 97 | 98 | 99 | class StrExecutorFacadeDebug(StrExecutorFacade): 100 | executor_class = ExecutorExtDebug 101 | -------------------------------------------------------------------------------- /intel4004_emu/translator.py: -------------------------------------------------------------------------------- 1 | class Instruction(object): 2 | opcodes = { 3 | 'nop': (1, 0), 4 | 'jcn': (2, 2), 5 | 'fim': (2, 2), 6 | 'jun': (2, 1), 7 | 'fin': (1, 1), 8 | 'jms': (2, 1), 9 | 'inc': (1, 1), 10 | 'isz': (2, 2), 11 | 'add': (1, 1), 12 | 'sub': (1, 1), 13 | 'ld': (1, 1), 14 | 'xch': (1, 1), 15 | 'bbl': (1, 1), 16 | 'ldm': (1, 1), 17 | 'clb': (1, 0), 18 | 'clc': (1, 0), 19 | 'iac': (1, 0), 20 | 'cmc': (1, 0), 21 | 'cma': (1, 0), 22 | 'ral': (1, 0), 23 | 'rar': (1, 0), 24 | 'tcc': (1, 0), 25 | 'dac': (1, 0), 26 | 'stc': (1, 0), 27 | 'daa': (1, 0), 28 | 'tcs': (1, 0), 29 | 30 | 'src': (1, 1), 31 | 'wrm': (1, 0), 32 | 'rdm': (1, 0), 33 | 'adm': (1, 0), 34 | 'sbm': (1, 0), 35 | } 36 | 37 | class Line(object): 38 | 39 | def __init__(self, index, text): 40 | self.index = index 41 | self.text = text.strip() 42 | self.codeTop = 0 43 | self.dataTop = 0 44 | 45 | def stripLabel(self): 46 | pos = self.text.find(':') 47 | if pos < 0: 48 | return None 49 | label = self.text[:pos] 50 | for c in label: 51 | if not c.isalpha() and not c.isdigit() and c != '_': 52 | return None 53 | self.text = self.text[pos + 1:].strip() 54 | return label 55 | 56 | def splitParts(self): 57 | self.parts = [] 58 | s = self.text + ' ' 59 | n = len(s) 60 | p1 = 0 61 | while p1 < n: 62 | if s[p1].isspace(): 63 | p1 += 1 64 | continue 65 | if s[p1] == ';': 66 | break 67 | if s[p1] == '\'': 68 | p2 = s.find('\'', p1 + 1) 69 | if p2 < 0: 70 | raise "Unmatched quote in the line %" % self.index 71 | self.parts.append(s[p1 + 1 : p2]) 72 | p1 = p2 + 1 73 | else: 74 | p2 = p1 75 | while not s[p2].isspace(): 76 | p2 += 1 77 | v = s[p1:p2].lower() 78 | if v.isdigit(): 79 | v = int(v) 80 | elif v[0] == '$': 81 | v = int(v[1:], 16) 82 | self.parts.append(v) 83 | p1 = p2 84 | 85 | def isCode(self): 86 | return self.parts[0] != 'db' 87 | 88 | def parseInstruction(self, addr): 89 | inTheLine = "in the line %d!" % self.index 90 | self.opcode = self.parts[0] 91 | self.params = self.parts[1:] 92 | try: 93 | size, params = Instruction.opcodes[self.opcode] 94 | except KeyError: 95 | raise Exception("Unknown instruction %s %s" % (self.opcode, inTheLine)) 96 | if len(self.parts) != params + 1: 97 | raise Exception("Expected %d parameters %s" % (params, inTheLine)) 98 | self.size = size 99 | self.addr = addr 100 | 101 | def parseData(self, addr): 102 | self.data = [] 103 | for p in self.parts[1:]: 104 | if type(p) is int: 105 | self.data.append(p) 106 | else: 107 | for x in p: 108 | self.data.append(ord(x)) 109 | self.size = len(self.data) 110 | self.addr = addr 111 | 112 | def __str__(self): 113 | return str(self.addr) + ": " + self.opcode + " " + ' '.join([str(x) for x in self.params]) 114 | 115 | def __repr__(self): 116 | return '"' + self.__str__() + '"' 117 | 118 | def prepareLines(src): 119 | lines = [] 120 | count = len(src) 121 | for i in range(count): 122 | line = Line(i + 1, src[i]) 123 | lines.append(line) 124 | return lines 125 | 126 | def firstPass(lines): 127 | linesRes = [] 128 | labels = {} 129 | addr = 0 130 | for line in lines: 131 | label = line.stripLabel() 132 | if label != None: 133 | labels[label.lower()] = addr 134 | line.splitParts() 135 | if len(line.parts) == 0: 136 | continue 137 | if line.isCode(): 138 | line.parseInstruction(addr) 139 | else: 140 | line.parseData(addr) 141 | addr += line.size 142 | linesRes.append(line) 143 | return linesRes, labels 144 | 145 | 146 | def secondPass(lines, labels): 147 | prg = {} 148 | top = 0 149 | for line in lines: 150 | top = max(top, line.addr + line.size) 151 | if not line.isCode(): 152 | for offset in range(len(line.data)): 153 | prg['d' + str(line.addr + offset)] = line.data[offset] 154 | continue 155 | cntParams = len(line.params) 156 | for j in range(cntParams): 157 | p = line.params[j] 158 | if type(p) is str: 159 | if p in labels: 160 | line.params[j] = labels[p] 161 | elif p[0] == 'r': 162 | line.params[j] = int(p[1:]) 163 | prg[line.addr] = line 164 | prg['_top'] = top 165 | return prg 166 | 167 | def translate(src): 168 | lines = prepareLines(src) 169 | lines, labels = firstPass(lines) 170 | prg = secondPass(lines, labels) 171 | return prg 172 | 173 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | import sys 2 | from intel4004_emu import translator 3 | from intel4004_emu import executor 4 | from intel4004_emu import consolex 5 | 6 | 7 | class EnhancedExecutor(executor.Executor, consolex.Consolex): 8 | 9 | def printRegs(self): 10 | print(' '.join([str(r) for r in self.regs])) 11 | print("acc=%d, cy=%d, ip=%d, dp=%d, cycles=%d" % (self.acc, self.cy, self.ip, self.dp, self.cycles)) 12 | 13 | def printMemory(self, rows, cols): 14 | for row in range(rows): 15 | for col in range(cols): 16 | print "%X" % self.memory[row * cols + col], 17 | print 18 | 19 | def c_3ff(self): 20 | self.printRegs() 21 | 22 | def c_3fe(self): 23 | self.printMemory(8, 32) 24 | 25 | def c_3fd(self): 26 | self.printMemory(4, 16) 27 | 28 | 29 | def loadSource(): 30 | if len(sys.argv) < 2: 31 | raise Exception('Source file should be specified!') 32 | fileName = sys.argv[1] 33 | f = open(fileName) 34 | lines = f.readlines() 35 | f.close() 36 | return lines 37 | 38 | 39 | def fetchState(cpu): 40 | for i in range(2, len(sys.argv)): 41 | cpu.regs[i - 2] = int(sys.argv[i]) 42 | 43 | 44 | def main(): 45 | try: 46 | src = loadSource() 47 | prg = translator.translate(src) 48 | cpu = EnhancedExecutor() 49 | fetchState(cpu) 50 | cpu.run(prg) 51 | except Exception as e: 52 | sys.stderr.write("Error: %s\n" % e) 53 | 54 | 55 | if __name__ == '__main__': 56 | main() 57 | -------------------------------------------------------------------------------- /test/test_assmbly.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | import sys 3 | import os 4 | 5 | if __name__ == '__main__': 6 | sys.path.append(os.path.abspath('..')) # using test without package 7 | 8 | from intel4004_emu.executor import Executor 9 | from intel4004_emu import translator 10 | 11 | 12 | class TestASM(unittest.TestCase): 13 | 14 | @staticmethod 15 | def run_assembly(src, inp=None): 16 | prg = translator.translate(src.splitlines()) 17 | cpu = Executor() 18 | if inp: 19 | cpu.regs = [int(x, 16) for x in inp.split()] 20 | cpu.run(prg) 21 | return ' '.join('{:x}'.format(x) for x in cpu.regs) 22 | 23 | def test_wiki_example1(self): 24 | asm = '''\ 25 | ldm 5 26 | xch r2''' 27 | self.assertEqual('0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0', self.run_assembly(asm)) 28 | 29 | def test_wiki_example2(self): 30 | asm = '''\ 31 | start: 32 | iac 33 | xch r1 34 | finish: 35 | nop 36 | ''' 37 | self.assertEqual('0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0', self.run_assembly(asm)) 38 | 39 | def test_wiki_example3(self): 40 | asm = '''\ 41 | fim r0 $31 42 | fim r2 $41 43 | fim r4 $59 44 | fim r6 $26 45 | fim r12 250 46 | fim r14 206 47 | ''' 48 | self.assertEqual('3 1 4 1 5 9 2 6 0 0 0 0 f a c e', self.run_assembly(asm)) 49 | 50 | def test_wiki_example4(self): 51 | asm = '''\ 52 | ldm 6 53 | ral 54 | xch r0 55 | 56 | ldm 8 57 | rar 58 | xch r1 59 | 60 | ldm 7 61 | rar 62 | rar 63 | xch r2 64 | ''' 65 | self.assertEqual('c 4 9 0 0 0 0 0 0 0 0 0 0 0 0 0', self.run_assembly(asm)) 66 | 67 | def test_wiki_example5(self): 68 | asm = '''\ 69 | ldm 5 70 | xch r0 71 | jun skip_few 72 | ldm 6 73 | xch r1 74 | skip_few: 75 | ldm 7 76 | xch r2 77 | ''' 78 | self.assertEqual('5 0 7 0 0 0 0 0 0 0 0 0 0 0 0 0', self.run_assembly(asm)) 79 | 80 | def test_wiki_example6(self): 81 | asm = '''\ 82 | ldm 0 83 | xch r0 84 | 85 | jms add_three 86 | jms add_three 87 | jms add_three 88 | jun finish 89 | 90 | add_three: 91 | ld r0 92 | iac 93 | iac 94 | iac 95 | xch r0 96 | bbl 0 97 | 98 | finish: 99 | nop 100 | ''' 101 | self.assertEqual('9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0', self.run_assembly(asm)) 102 | 103 | def test_fetch_indirect(self): 104 | asm = ''' 105 | jun start 106 | data_block: db $39 107 | start: 108 | fim r0 data_block 109 | fin r2 110 | ''' 111 | self.assertEqual('0 2 3 9 0 0 0 0 0 0 0 0 0 0 0 0', self.run_assembly(asm)) 112 | 113 | if __name__ == '__main__': 114 | unittest.main() 115 | -------------------------------------------------------------------------------- /test/test_str_executor_facade.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | import sys 4 | import os 5 | import io 6 | 7 | if __name__ == '__main__': 8 | sys.path.append(os.path.abspath('..')) # using test without package 9 | 10 | from intel4004_emu.str_executor_facade import StrExecutorFacade, StrExecutorFacadeDebug 11 | 12 | 13 | class TestStrExecutorFacade(unittest.TestCase): 14 | 15 | def test_examples(self): 16 | cpu = StrExecutorFacade() 17 | cpu.load_assembly('''\ 18 | ldm 5 19 | xch r2''') 20 | self.assertEqual('0 0 5 0 0 0 0 0 0 0 0 0 0 0 0 0', cpu.run().regs2str) 21 | 22 | cpu.load_assembly('''\ 23 | ldm 5 24 | xch r2 25 | ldm 3 26 | xch r2 27 | xch r1 28 | ''') 29 | self.assertEqual('0 5 3 0 0 0 0 0 0 0 0 0 0 0 0 0', cpu.run().regs2str) 30 | 31 | cpu.load_assembly('''\ 32 | start: 33 | iac 34 | xch r1 35 | finish: 36 | nop 37 | ''') 38 | self.assertEqual('0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0', cpu.run().regs2str) 39 | 40 | cpu.load_assembly('''\ 41 | fim r0 $31 42 | fim r2 $41 43 | fim r4 $59 44 | fim r6 $26 45 | fim r12 250 46 | fim r14 206 47 | ''') 48 | self.assertEqual('3 1 4 1 5 9 2 6 0 0 0 0 f a c e', cpu.run().regs2str) 49 | 50 | cpu.load_assembly('''\ 51 | ldm 6 52 | ral 53 | xch r0 54 | 55 | ldm 8 56 | rar 57 | xch r1 58 | 59 | ldm 7 60 | rar 61 | rar 62 | xch r2 63 | ''') 64 | self.assertEqual('c 4 9 0 0 0 0 0 0 0 0 0 0 0 0 0', cpu.run().regs2str) 65 | 66 | cpu.load_assembly('''\ 67 | ldm 5 68 | xch r0 69 | jun skip_few 70 | ldm 6 71 | xch r1 72 | skip_few: 73 | ldm 7 74 | xch r2 75 | ''') 76 | self.assertEqual('5 0 7 0 0 0 0 0 0 0 0 0 0 0 0 0', cpu.run().regs2str) 77 | 78 | cpu.load_assembly('''\ 79 | ldm 0 80 | xch r0 81 | 82 | jms add_three 83 | jms add_three 84 | jms add_three 85 | jun finish 86 | 87 | add_three: 88 | ld r0 89 | iac 90 | iac 91 | iac 92 | xch r0 93 | bbl 0 94 | 95 | finish: 96 | nop 97 | ''') 98 | self.assertEqual('9 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0', cpu.run().regs2str) 99 | 100 | def test_clear(self): 101 | cpu = StrExecutorFacade('''\ 102 | add r0 103 | add r0 104 | ''') 105 | self.assertEqual('8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1', 106 | cpu.run('8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0').regs2str_ex) 107 | cpu.load_assembly('') 108 | self.assertEqual('8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1', 109 | cpu.run(clear=False).regs2str_ex) 110 | 111 | cpu = StrExecutorFacade('''\ 112 | add r0 113 | ''') 114 | self.assertEqual('8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=8, cy=0', 115 | cpu.run('8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0').regs2str_ex) 116 | self.assertEqual('8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1', 117 | cpu.run(clear=False).regs2str_ex) 118 | self.assertEqual('8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=9, cy=0', 119 | cpu.run(clear=False).regs2str_ex) 120 | 121 | def test_debug(self): 122 | out = io.StringIO() 123 | sys.stdout = out 124 | cpu = StrExecutorFacadeDebug('''\ 125 | add r0 126 | add r0 127 | ''') 128 | self.assertEqual('8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1', 129 | cpu.run('8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0').regs2str_ex) 130 | self.assertEqual('''\ 131 | Before: 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=0 132 | 0: add 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=8, cy=0 133 | 1: add 0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1 134 | ''', out.getvalue()) 135 | 136 | def test_run_multiple_times(self): 137 | cpu = StrExecutorFacade('''\ 138 | xch r1 139 | rar 140 | xch r1 141 | ''') 142 | for state, res in [ 143 | ('0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=0'), 144 | ('0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1'), 145 | ('0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=0'), 146 | ('0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 1 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1'), 147 | ('0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=0'), 148 | ('0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 2 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1'), 149 | ('0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=0'), 150 | ('0 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 3 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1'), 151 | ('0 8 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=0'), 152 | ('0 9 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 4 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1'), 153 | ('0 a 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=0'), 154 | ('0 b 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 5 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1'), 155 | ('0 c 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=0'), 156 | ('0 d 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 6 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1'), 157 | ('0 e 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=0'), 158 | ('0 f 0 0 0 0 0 0 0 0 0 0 0 0 0 0', '0 7 0 0 0 0 0 0 0 0 0 0 0 0 0 0, acc=0, cy=1'), 159 | ]: 160 | self.assertEqual(res, cpu.run(state).regs2str_ex) 161 | 162 | def test_console(self): 163 | cpu = StrExecutorFacade('''\ 164 | jms $3f0 165 | jms $3e0 166 | ''') 167 | self.assertEqual('A', cpu.run(console='A').console_output) 168 | cpu = StrExecutorFacade('''\ 169 | loop: 170 | jms $3f0 171 | ld r2 172 | jcn az exit 173 | inc r3 174 | jms $3e0 175 | jun loop 176 | exit: 177 | ''') 178 | self.assertEqual('BCD', cpu.run(console='ABC').console_output) 179 | 180 | 181 | if __name__ == '__main__': 182 | unittest.main() --------------------------------------------------------------------------------