diff --git a/asm/asm.py b/asm/asm.py index b597d6418..7944b0cf6 100644 --- a/asm/asm.py +++ b/asm/asm.py @@ -44,6 +44,7 @@ "NOT": {"type": 1, "code": "01101001"}, "OR": {"type": 2, "code": "10101010"}, "POP": {"type": 1, "code": "01000110"}, + "PRL": {"type": 1, "code": "01110011"}, "PRA": {"type": 1, "code": "01001000"}, "PRN": {"type": 1, "code": "01000111"}, "PUSH": {"type": 1, "code": "01000101"}, diff --git a/asm/histogram.asm b/asm/histogram.asm new file mode 100644 index 000000000..99058d4fb --- /dev/null +++ b/asm/histogram.asm @@ -0,0 +1,26 @@ +; histogram.asm +; +; Expected output: +; * +; ** +; **** +; ******** +; **************** +; ******************************** +; **************************************************************** + +LDI R0,1 ; 0 - current asterisks +LDI R1,1 ; 1 - current line +LDI R2,8 ; 2 - max lines + 1 +LDI R3,2 ; 3 - multiply by +LDI R4,Print ; 4 - load print address + +Print: + +PRL R0 ; 5 - print line with current asterisks +MUL R0,R3 ; 6 - double current asterisks +INC R1 ; 7 - increment current line +CMP R1,R2 ; 8 - compare lines +JLT R4 ; 9 - less than max lines loop back to print +HLT ; 10 - halt when done + diff --git a/asm/mult.asm b/asm/mult.asm index ef8c65192..9761c4a4d 100644 --- a/asm/mult.asm +++ b/asm/mult.asm @@ -1,6 +1,6 @@ -# mult.asm -# -# Expected output: 72 +; mult.asm +; +; Expected output: 72 LDI R0,8 LDI R1,9 diff --git a/ls8/cpu.py b/ls8/cpu.py index 9a307496e..0bf9e05ea 100644 --- a/ls8/cpu.py +++ b/ls8/cpu.py @@ -1,65 +1,542 @@ """CPU functionality.""" import sys +from time import time + +# Instructions & Flags -----------------------------------> +# region <-- vscode collapse region tag +CALL = 0b01010000 +HLT = 0b00000001 +LDI = 0b10000010 +POP = 0b01000110 +PRN = 0b01000111 +PUSH = 0b01000101 +RET = 0b00010001 +LD = 0b10000011 +ST = 0b10000100 +PRA = 0b01001000 +PRL = 0b01110011 +JMP = 0b01010100 +JEQ = 0b01010101 +JNE = 0b01010110 +JLT = 0b01011000 +# ALU Instructions ----> +ADD = 0b10100000 +AND = 0b10101000 +CMP = 0b10100111 +DEC = 0b01100110 +DIV = 0b10100011 +INC = 0b01100101 +MOD = 0b10100100 +MUL = 0b10100010 +NOT = 0b01101001 +OR = 0b10101010 +SHL = 0b10101100 +SHR = 0b10101101 +SUB = 0b10100001 +XOR = 0b10101011 +# Flags ---------------> +FLB = 0b00000000 +FLE = 0b00000001 +FLG = 0b00000010 +FLL = 0b00000100 +# Interrupts ----------> +INT = 0b01010010 +IRET = 0b00010011 +I0 = 0b00000001 +I1 = 0b00000010 +I2 = 0b00000100 +I3 = 0b00001000 +I4 = 0b00010000 +I5 = 0b00100000 +I6 = 0b01000000 +I7 = 0b10000000 +# endregion +# ========================================================> + class CPU: """Main CPU class.""" + # Constructor ----------------------------------------> def __init__(self): """Construct a new CPU.""" - pass + self.__ram = [ 0 ] * 256 # RAM + self.__reg = [ 0 ] * 8 # Registers + self.__pc = 0 # Program Counter + self.__running = True # Main Loop Var + self.__sp = 7 # Stack Pointer + self.__fl = FLB # Flag + self.__imsk = 5 # Interrupt Mask + self.__istat = 6 # Interrupt Status + + self.__intrpts_enbld = True # Interrupt Bool + + self.__reg[ self.__sp ] = 0xF4 # Empty Stack + + # Interrupt Mask; I0 & I1 only + self.__reg[ self.__imsk ] = [ 0b00000011 ] + + # Interrupt Vector Table + self.__ivt = [ 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF ] + + # ALU + self.__alu = {} + self.__alu[ ADD ] = self.__hndl_add + self.__alu[ AND ] = self.__hndl_and + self.__alu[ CMP ] = self.__hndl_cmp + self.__alu[ DEC ] = self.__hndl_dec + self.__alu[ DIV ] = self.__hndl_div + self.__alu[ INC ] = self.__hndl_inc + self.__alu[ MOD ] = self.__hndl_mod + self.__alu[ MUL ] = self.__hndl_mul + self.__alu[ NOT ] = self.__hndl_not + self.__alu[ OR ] = self.__hndl_or + self.__alu[ SHL ] = self.__hndl_shl + self.__alu[ SHR ] = self.__hndl_shr + self.__alu[ SUB ] = self.__hndl_sub + self.__alu[ XOR ] = self.__hndl_xor + + # Branch Table + self.__b_tbl = {} + self.__b_tbl[ LDI ] = self.__hndl_ldi + self.__b_tbl[ PRN ] = self.__hndl_prn + self.__b_tbl[ PUSH ] = self.__hndl_push + self.__b_tbl[ POP ] = self.__hndl_pop + self.__b_tbl[ CALL ] = self.__hndl_call + self.__b_tbl[ RET ] = self.__hndl_ret + self.__b_tbl[ INT ] = self.__hndl_intrpt + self.__b_tbl[ IRET ] = self.__hndl_iret + self.__b_tbl[ LD ] = self.__hndl_ld + self.__b_tbl[ ST ] = self.__hndl_st + self.__b_tbl[ PRA ] = self.__hndl_pra + self.__b_tbl[ PRL ] = self.__hndl_prl + self.__b_tbl[ JMP ] = self.__hndl_jmp + self.__b_tbl[ JEQ ] = self.__hndl_jeq + self.__b_tbl[ JNE ] = self.__hndl_jne + self.__b_tbl[ JLT ] = self.__hndl_jlt + self.__b_tbl[ ADD ] = self.__alu[ ADD ] + self.__b_tbl[ AND ] = self.__alu[ AND ] + self.__b_tbl[ CMP ] = self.__alu[ CMP ] + self.__b_tbl[ DEC ] = self.__alu[ DEC ] + self.__b_tbl[ DIV ] = self.__alu[ DIV ] + self.__b_tbl[ INC ] = self.__alu[ INC ] + self.__b_tbl[ MOD ] = self.__alu[ MOD ] + self.__b_tbl[ MUL ] = self.__alu[ MUL ] + self.__b_tbl[ NOT ] = self.__alu[ NOT ] + self.__b_tbl[ OR ] = self.__alu[ OR ] + self.__b_tbl[ SHL ] = self.__alu[ SHL ] + self.__b_tbl[ SHR ] = self.__alu[ SHR ] + self.__b_tbl[ SUB ] = self.__alu[ SUB ] + self.__b_tbl[ XOR ] = self.__alu[ XOR ] + # ====================================================> - def load(self): + + # Memory Functions -----------------------------------> + # region + # Memory Address Register (MAR) + # Memory Data Register (MDR) + def __ram_read( self, MAR ): + return self.__ram[ MAR ] + # ============> + + + def __ram_write( self, MAR, MDR ): + self.__ram[ MAR ] = MDR + # ============> + + + def load( self ): """Load a program into memory.""" - address = 0 + if len( sys.argv ) != 2: + print( 'Insufficient number of arguments' ) + sys.exit( 1 ) - # For now, we've just hardcoded a program: + address = 0 - program = [ - # From print8.ls8 - 0b10000010, # LDI R0,8 - 0b00000000, - 0b00001000, - 0b01000111, # PRN R0 - 0b00000000, - 0b00000001, # HLT - ] + try: + with open( sys.argv[ 1 ] ) as f: + for line in f: + line = line.split( '#' ) + num = line[ 0 ].strip() - for instruction in program: - self.ram[address] = instruction - address += 1 + if num == '': + continue + else: + try: + self.__ram_write( address, int( num, 2 ) ) + except: + print( 'Could not convert string to integer' ) + + address += 1 + + except: + print( 'File not found' ) + sys.exit( 1 ) + # ============> - def alu(self, op, reg_a, reg_b): - """ALU operations.""" - if op == "ADD": - self.reg[reg_a] += self.reg[reg_b] - #elif op == "SUB": etc - else: - raise Exception("Unsupported ALU operation") + def __push( self, a ): + self.__dec_sp() + self.__ram_write( self.__reg[ self.__sp ], a ) + # ============> + - def trace(self): + def __pop( self ): + val = self.__ram_read( self.__reg[ self.__sp ] ) + self.__inc_sp() + return val + # endregion + # ====================================================> + + + # Traceback ------------------------------------------> + def __trace( self ): """ Handy function to print out the CPU state. You might want to call this from run() if you need help debugging. """ - print(f"TRACE: %02X | %02X %02X %02X |" % ( - self.pc, - #self.fl, + print( f"TRACE: %02X | %02X %02X %02X |" % ( + self.__pc, + self.__fl, #self.ie, - self.ram_read(self.pc), - self.ram_read(self.pc + 1), - self.ram_read(self.pc + 2) - ), end='') + self.__ram_read( self.__pc ), + self.__ram_read( self.__pc + 1 ), + self.__ram_read( self.__pc + 2 ) + ), end = '' ) - for i in range(8): - print(" %02X" % self.reg[i], end='') + for i in range( 8 ): + print( " %02X" % self.__reg[ i ], end = '' ) print() + # ====================================================> + + + # Instruction Handlers -------------------------------> + # region + def __hndl_ldi( self, a, b ): + self.__reg[ a ] = b + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_prn( self, a, b ): + print ( self.__reg[ a ] ) + self.__inc_pc( 2 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_push( self, a, b ): + self.__push( self.__reg[ a ] ) + self.__inc_pc( 2 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_pop( self, a, b ): + self.__reg[ a ] = self.__pop() + self.__inc_pc( 2 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_call( self, a, b ): + self.__dec_sp() + self.__ram_write( self.__reg[ self.__sp ], self.__pc + 2 ) + self.__pc = self.__reg[ a ] + # ============> + + + def __hndl_ret( self, a, b ): + self.__pc = self.__ram_read( self.__reg[ self.__sp ] ) + self.__inc_sp() + # ============> + + + def __hndl_intrpt( self, a, b ): + val = self.__reg[ a ] & self.__reg[ self.__imsk ] + if val in [ I0, I1 ]: + self.__reg[ self.__istat ] |= val + # ============> + + + def __hndl_iret( self, a, b ): + for i in range( 6, -1, -1 ): + self.__reg[ i ] = self.__pop() + + self.__fl = self.__pop() + self.__pc = self.__pop() + + self.__intrpts_enbld = True + # ============> + + + def __hndl_ld( self, a, b ): + self.__reg[ a ] = self.__ram_read( self.reg[ b ] ) + self.__inc_sp( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_st( self, a, b ): + self.__ram_write( self.__reg[ a ], self.__reg[ b ] ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_pra( self, a, b ): + print( chr( self.__reg[ a ] ) ) + self.__inc_pc( 2 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_prl( self, a, b ): + for i in range(0, self.__reg[ a ] ): + print( '*', end="" ) + print( '' ) + self.__inc_pc( 2 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_jmp( self, a, b ): + self.__pc = self.__reg[ a ] + # ============> + + + def __hndl_jeq( self, a, b ): + if self.__fl == FLE: + self.__pc = self.__reg[ a ] + + else: + self.__inc_pc( 2 ) + + self.__fl == FLB + # ============> + + + def __hndl_jne( self, a, b ): + if self.__fl in [ FLB, FLG, FLL ]: + self.__pc = self.__reg[ a ] + + else: + self.__inc_pc( 2 ) + + self.__fl == FLB + # ============> + + + def __hndl_jlt( self, a, b ): + if self.__fl == FLL: + self.__pc = self.__reg[ a ] + else: + self.__inc_pc( 2 ) + + self.__fl == FLB + # ============> + # endregion + + + # ALU Instructions Handlers --------------------------> + # region + def __hndl_add( self, a, b ): + self.__reg[ a ] += self.__reg[ b ] + self.__and( a ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_and( self, a, b ): + self.__and( a, self.__reg[ b ] ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_cmp( self, a, b ): + first = self.__reg[ a ] + second = self.__reg[ b ] + + if first == second: + #print( 'equal' ) + self.__set_flag( FLE ) + + elif first > second: + self.__set_flag( FLG ) + + elif first < second: + self.__set_flag( FLL ) + + else: + #print( 'not equal' ) + self.__set_flag( FLB ) + + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_dec( self, a, b ): + self.__reg[ a ] -= 1 + self.__and( a ) + self.__inc_pc( 2 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_div( self, a, b ): + self.__reg[ a ] /= self.__reg[ b ] + self.__and( a ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_inc( self, a, b ): + self.__reg[ a ] += 1 + self.__and( a ) + self.__inc_pc( 2 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_mod( self, a, b ): + self.__reg[ a ] %= self.__reg[ b ] + self.__and( a ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_mul( self, a, b ): + self.__reg[ a ] *= self.__reg[ b ] + self.__and( a ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_not( self, a, b ): + self.__reg[ a ] = ~self.__reg[ a ] + self.__and( a ) + self.__inc_pc( 2 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_or( self, a, b ): + self.__reg[ a ] |= self.__reg[ b ] + self.__and( a ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_shl( self, a, b ): + self.__reg[ a ] <<= self.__reg[ b ] + self.__and( a ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_shr( self, a, b ): + self.__reg[ a ] >>= self.__reg[ b ] + self.__and( a ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_sub( self, a, b ): + self.__reg[ a ] -= self.__reg[ b ] + self.__and( a ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + + + def __hndl_xor( self, a, b ): + self.__reg[ a ] ^= self.__reg[ b ] + self.__and( a ) + self.__inc_pc( 3 ) # +1 base increment plus 1 for each used operand + # ============> + # endregion + + + # Instruction Helpers --------------------------------> + # region + def __inc_pc( self, num ): + self.__pc += num + # ============> + + + def __inc_sp( self ): + self.__reg[ self.__sp ] += 1 + self.__and( self.__sp ) + # ============> + + + def __dec_sp( self ): + self.__reg[ self.__sp ] -= 1 + # ============> + + + def __and( self, a, b=0xFF ): + self.__reg[ a ] &= b + # ============> + + + def __set_flag( self, flag ): + self.__fl = flag + # ============> + + + def __process_intrpt( self ): + self.__intrpts_enbld = False + + pc = None + status = self.__reg[ self.__istat ] + + if status & I0: + self.__reg[ self.__istat ] -= I0 + pc = self.__ram_read( self.__ivt[ 0 ] ) + + elif status & I1: + self.__reg[ self.__istat ] -= I1 + pc = self.__ram_read( self.__ivt[ 1 ] ) + + if pc is not None: + self.__push( self.__pc ) + self.__push( self.__fl ) + + for i in range( 0, 7 ): + self.__push( self.__reg[ i ] ) + + self.__pc = pc + # endregion + # ====================================================> + + + # Main Loop ------------------------------------------> def run(self): """Run the CPU.""" - pass + + start_time = time() + + while self.__running: + if self.__intrpts_enbld: + if self.__reg[ self.__istat ] != 0: + self.__process_intrpt() + + # Instruction Register (IR) + IR = self.__ram_read( self.__pc ) + + if IR == HLT: + self.__running = False + + elif IR not in self.__b_tbl: + print( f'Instruction Register Unknown: {IR} at program counter {self.__pc}' ) + self.__running = False + + else: + op_a = self.__ram_read( self.__pc + 1 ) + op_b = self.__ram_read( self.__pc + 2 ) + + func = self.__b_tbl[ IR ] + func( op_a, op_b ) + + time_check = time() + + if time_check - start_time >= 1: + self.__reg[ self.__istat ] += I0 + start_time = time() +# EoF ----------------------------------------------------> \ No newline at end of file diff --git a/ls8/examples/histogram.ls8 b/ls8/examples/histogram.ls8 new file mode 100644 index 000000000..20a548a55 --- /dev/null +++ b/ls8/examples/histogram.ls8 @@ -0,0 +1,29 @@ +10000010 # LDI R0,1 +00000000 +00000001 +10000010 # LDI R1,1 +00000001 +00000001 +10000010 # LDI R2,8 +00000010 +00001000 +10000010 # LDI R3,2 +00000011 +00000010 +10000010 # LDI R4,PRINT +00000100 +00001111 +# PRINT (address 15): +01110011 # PRL R0 +00000000 +10100010 # MUL R0,R3 +00000000 +00000011 +01100101 # INC R1 +00000001 +10100111 # CMP R1,R2 +00000001 +00000010 +01011000 # JLT R4 +00000100 +00000001 # HLT diff --git a/ls8/examples/sctest.ls8 b/ls8/examples/sctest.ls8 index 7853b76a4..a063d4bdb 100644 --- a/ls8/examples/sctest.ls8 +++ b/ls8/examples/sctest.ls8 @@ -1,31 +1,31 @@ 10000010 # LDI R0,10 00000000 -00001010 +00001010 # R0 = 10 10000010 # LDI R1,20 00000001 -00010100 +00010100 # R1 = 20 10000010 # LDI R2,TEST1 00000010 -00010011 +00010011 # R2 = address of test1 (19 line 21) 10100111 # CMP R0,R1 00000000 -00000001 +00000001 # 10 != 20 FLB 01010101 # JEQ R2 -00000010 +00000010 # not equal do nothing 10000010 # LDI R3,1 00000011 -00000001 +00000001 # R3 = 1 01000111 # PRN R3 -00000011 +00000011 # print 1 # TEST1 (address 19): 10000010 # LDI R2,TEST2 00000010 -00100000 +00100000 # R2 = address of test2 (32 line 35) 10100111 # CMP R0,R1 00000000 -00000001 +00000001 # 10 != 20 FLB 01010110 # JNE R2 -00000010 +00000010 # not equal jump to address in R2 (32 line 35) 10000010 # LDI R3,2 00000011 00000010 @@ -34,15 +34,15 @@ # TEST2 (address 32): 10000010 # LDI R1,10 00000001 -00001010 +00001010 # from jump line 28 R1 = 10 10000010 # LDI R2,TEST3 00000010 -00110000 +00110000 # R2 = address of test3 (48 line 52) 10100111 # CMP R0,R1 00000000 -00000001 +00000001 # 10 = 10 FLE 01010101 # JEQ R2 -00000010 +00000010 # equal jump to address in R2 (48 line 52) 10000010 # LDI R3,3 00000011 00000011 @@ -51,29 +51,29 @@ # TEST3 (address 48): 10000010 # LDI R2,TEST4 00000010 -00111101 +00111101 # from jump line 45 R2 = address of test4 (61 line 66) 10100111 # CMP R0,R1 00000000 -00000001 +00000001 # 10 = 10 FLE 01010110 # JNE R2 -00000010 +00000010 # equal do nothing 10000010 # LDI R3,4 00000011 -00000100 +00000100 # R3 = 4 01000111 # PRN R3 -00000011 +00000011 # print 4 # TEST4 (address 61): 10000010 # LDI R3,5 00000011 -00000101 +00000101 # R3 = 5 01000111 # PRN R3 -00000011 +00000011 # print 5 10000010 # LDI R2,TEST5 00000010 -01001001 +01001001 # R2 = address of test5 (73 line 79) 01010100 # JMP R2 -00000010 +00000010 # jump to address in R2 (73 line 79) 01000111 # PRN R3 00000011 # TEST5 (address 73): -00000001 # HLT +00000001 # HLT # end program