Package: bytecore
ByteCore EmulatorByteCore is a simple 8-bit CPU design following Von Neumann architecture (The design where both instructions and data share the same memory address space) capable of addressing 65536 (64KB) bytes of memory.ByteCore Emulator is the Python implementation of the ByteCore CPU to test out the instruction set.RegistersAccumulator (A): An 8-bit register where arithmetic and logic operations are carried out.Program Counter (PC): A 16-bit register which keeps track of the address of the current instruction being executed and points to the next instruction to be executed.Internal RegistersIn addition to the Accumulator and Program Counter registers, the ByteCore CPU uses a few internal registers to manage its operations:Temp Register (T): An 8-bit register used by the CPU to temporarily store data during arithmetic and logic operations.Memory Address Register (MAR): A 16-bit register used by the CPU to hold the address to be accessed during memory operations.Increment Register (IR): A constant register, permanently set to 1, used by the CPU specifically to increment the Program Counter.These registers are used internally by the CPU and are not directly accessible to user programs. They are essential to the operation of the CPU and understanding their role can help users better understand the internal workings of the ByteCore CPU.Addressing ModesThe ByteCore CPU uses a big-endian memory architecture. In this design, the most significant byte (MSB) of a multi-byte value is stored at the smallest memory address.Extended Mode: Each instruction is directly followed in memory by a 16-bit address. This address is stored across two consecutive memory locations, with the most significant byte in the first location.Note: All uninitialized memory defaults to 0x0.Word SizeIn the context of the ByteCore CPU architecture, a "word" is defined as an 8-bit unit of data, consistent with its 8-bit design. This is the primary unit of data that can be manipulated with arithmetic and logic instructions.A "double word" is a 16-bit unit of data, or two consecutive 8-bit words. This is primarily used in memory addressing, allowing the ByteCore CPU to access 65536 ($2^{16}$) individual memory locations.InstructionsEach instruction is represented by a unique 8-bit operation code, commonly abbreviated as "opcode". The instructions supported by the ByteCore CPU are as follows:Opcode (Binary)Opcode (Hexadecimal)Opcode (Decimal)InstructionDescription000000000x00HALTStop execution.000000010x11LOADLoad the value from an extended memory address into the accumulator.000000100x22STOREStore the value in the accumulator into an extended memory address.000001000x44ADDAdd the value from an extended memory address to the value in the accumulator.000010000x88SUBSubtract the value from an extended memory address from the value in the accumulator.000100000x1016JMPUnconditional jump to an extended memory address.001000000x2032JZConditional jump to an extended memory address if the accumulator is zero.Note: The opcode for HALT being 0x0 means that any uninitialized memory (which defaults to 0x0) will also halt the CPU, providing a fail-safe for programs that exceed their intended bounds.Note: The opcode assignments are set up so that each opcode has exactly one bit set to 1. This is chosen to simplify hardware implementation where it will be possible to use a bit-selection logic to decode the opcodes.HALT InstructionThe HALT instruction (opcode 0x0) stops the CPU from processing further instructions. When a HALT instruction is encountered, the CPU will enter a loop where it continually fetches and decodes the HALT instruction without incrementing the Program Counter (PC). This effectively suspends the CPU's operation until an external event occurs or the emulation is manually stopped. This behavior can be particularly useful for handling error situations or stopping the CPU after a program has finished execution.Unrecognized OpcodesIn the ByteCore CPU, any opcode that does not correspond to a defined instruction is treated as an invalid opcode. To ensure the stability and predictability of the system, the CPU defaults to executing the HALT operation when it encounters an invalid opcode.This fail-safe mechanism prevents the execution of unintended or unknown instructions, aiding in error detection and debugging. It's important to note that when the CPU halts unexpectedly, it may be due to an unrecognized opcode in the instruction set.Instruction CycleThis is a description of the instruction cycle, also known as the fetch-decode-execute cycle. The steps look like this:Fetch: The CPU fetches the instruction from memory. This is the location pointed to by the program counter (PC).Decode: The CPU decodes the instruction to determine what operation to perform.Evaluate Address MSB: If the instruction involves data from memory, the CPU calculates the address of the most significant byte (MSB) of the data.Fetch Operand MSB: If the instruction involves data from memory, the CPU fetches the most significant byte (MSB) of the data.Evaluate Address LSB: The CPU calculates the address of the least significant byte (LSB) of the data.Fetch Operand LSB: The CPU fetches the least significant byte (LSB) of the data.Execute: The CPU performs the operation.Store Result: If the operation produces a result, the CPU stores it in the appropriate place (either a register or a memory location).Increment PC: The CPU increments the program counter to point to the next instruction.These nine steps form one complete instruction cycle. The speed at which the CPU can complete these steps is determined by its clock speed. Each tick of the clock allows the CPU to move one step forward in the cycle.Boundary ConditionsThe ByteCore CPU handles certain edge cases as follows:Accumulator Overflow and Underflow: The accumulator (A register) is an 8-bit register. If the result of an operation would be greater than 0xFF or less than 0x00, the value wraps around to fit within 8 bits. For example, if the accumulator holds the value 0xFF and an ADD instruction adds 1 to it, the result will be 0x00 (overflow). Similarly, if the accumulator holds the value 0x00 and a SUB instruction subtracts 1 from it, the result will be 0xFF (underflow).Memory Address Overflow: If an instruction attempts to read or write to an address that's outside the valid memory range (greater than 0xFFFF), the address wraps around to start from 0 again. For example, if a LOAD or STORE instruction tries to access the address 0x10000, it will instead access address 0x0000.Running the EmulatorFollow these steps to run the emulator.Manual SetupEnsure that you have Python 3.11 or newer installed on your system.Clone the repository and navigate into the root directory. You may want to create a Python virtual environment to isolate the project's dependencies.To install the necessary dependencies, run:pip3install-rrequirements.txtGitpod SetupYou can also use Gitpod to run the emulator by clickinghere.Running the EmulatorCreate a new Python file (e.g.,example.py) in the src directory and add the following import statements:frombytecore.memoryimportMemoryfrombytecore.byteimportBytefrombytecore.opcodeimportOpcodefrombytecore.emulatorimportByteCoreNext, initialize a 64KB array of bytes as the memory:memory_bytes=Memory.get_default_memory_bytes()You can then load your program into memory. Here is a simple example which loads theHALTinstruction:memory_bytes[0]=Opcode.HALTInstantiate the emulator with the provided memory:byte_core=ByteCore(memory_bytes)You now have three options for running the emulator:Execute a single step of the current instruction cycle:byte_core.step()Complete the current instruction cycle:byte_core.cycle()Execute instructions until theHALTstate is reached:byte_core.cycle_until_halt()To inspect the state of memory at any point, use:dump=byte_core.dump()memory_dump=dump.memoryYou can print the entire memory dump, though please note that this will print 64KB of data to your screen:print(memory_dump)For a more manageable output, consider printing a slice of the memory:print(memory_dump[0:20])Finally, to run yourexample.pyfile, use:pythonsrc/example.pyExample ProgramsBelow, you'll find two example programs demonstrating how to utilize the ByteCore emulator. The first is a basic introduction, while the second one engages all the opcodes.Example 1: Simple ProgramThis program begins with a simple syntax using hexadecimal numbers for ease of drafting. It's then converted into a format the emulator understands, which uses decimal numbers (0 to 255) encapsulated in a Byte class.# Initial draft in hexadecimal:# 00 00 LOAD# 00 01 00# 00 02 0A# 00 03 ADD# 00 04 00# 00 05 0B# 00 06 STORE# 00 07 FF# 00 08 FF# 00 09 HALT# 00 0A 14; 20# 00 0B 1E; 30Conversion to a format that the emulator understands:frombytecore.memoryimportMemoryfrombytecore.byteimportBytefrombytecore.opcodeimportOpcodefrombytecore.emulatorimportByteCorememory_bytes=Memory.get_default_memory_bytes()memory_bytes[0]=Opcode.LOADmemory_bytes[1]=Byte(0)memory_bytes[2]=Byte(10)memory_bytes[3]=Opcode.ADDmemory_bytes[4]=Byte(0)memory_bytes[5]=Byte(11)memory_bytes[6]=Opcode.STOREmemory_bytes[7]=Byte(255)memory_bytes[8]=Byte(255)memory_bytes[9]=Opcode.HALTmemory_bytes[10]=Byte(20)memory_bytes[11]=Byte(30)byte_core=ByteCore(memory_bytes)byte_core.cycle_until_halt()dump=byte_core.dump()dump.memory[-1]# equals 50To aid understanding, here is a Python equivalent of the ByteCore program:output=0a=20b=30accumulator=a# LOADaccumulator+=b# ADDoutput=accumulator# STORE#                     # HALTprint(output)# prints 50Example 2: Advanced ProgramThis advanced example begins similarly, drafting in hexadecimal. The transition to the emulator's format benefits from a custom builder class, streamlining the assembly of Byte instances.# Initial draft in hexadecimal:# 00 00 JMP# 00 01 FE# 00 02 00# 01 00 37; 55# 01 01 14; 20# 01 02 02;  2# 01 03 01;  1# FE 00 LOAD# FE 01 01# FE 02 00# FE 03 ADD# FE 04 01# FE 05 02# FE 06 STORE# FE 07 01# FE 08 00# FE 09 LOAD# FE 0A 01# FE 0B 01# FE 0C SUB# FE 0D 01# FE 0E 03# FE 0F STORE# FE 10 01# FE 11 01# FE 12 LOAD# FE 13 01# FE 14 01# FE 15 JZ# FE 16 FF# FE 17 00# FE 18 JMP# FE 19 FE# FE 1A 00# FF 00 LOAD# FF 01 01# FF 02 00# FF 03 STORE# FF 04 FF# FF 05 FF# FF 06 HALTTranslation to the emulator-friendly format:frombytecore.memory_bytes_builderimportMemoryBytesBuilderfrombytecore.emulatorimportByteCorememory_bytes=MemoryBytesBuilder()\.msb('00').lsb('00').jmp()\.msb('00').lsb('01').data('FE')\.msb('00').lsb('02').data('00')\
    \.msb('01').lsb('00').data('37').comment('55')\.msb('01').lsb('01').data('14').comment('20')\.msb('01').lsb('02').data('02').comment(' 2')\.msb('01').lsb('03').data('01').comment(' 1')\
    \.msb('FE').lsb('00').load()\.msb('FE').lsb('01').data('01')\.msb('FE').lsb('02').data('00')\.msb('FE').lsb('03').add()\.msb('FE').lsb('04').data('01')\.msb('FE').lsb('05').data('02')\.msb('FE').lsb('06').store()\.msb('FE').lsb('07').data('01')\.msb('FE').lsb('08').data('00')\.msb('FE').lsb('09').load()\.msb('FE').lsb('0A').data('01')\.msb('FE').lsb('0B').data('01')\.msb('FE').lsb('0C').sub()\.msb('FE').lsb('0D').data('01')\.msb('FE').lsb('0E').data('03')\.msb('FE').lsb('0F').store()\.msb('FE').lsb('10').data('01')\.msb('FE').lsb('11').data('01')\.msb('FE').lsb('12').load()\.msb('FE').lsb('13').data('01')\.msb('FE').lsb('14').data('01')\.msb('FE').lsb('15').jz()\.msb('FE').lsb('16').data('FF')\.msb('FE').lsb('17').data('00')\.msb('FE').lsb('18').jmp()\.msb('FE').lsb('19').data('FE')\.msb('FE').lsb('1A').data('00')\
    \.msb('FF').lsb('00').load()\.msb('FF').lsb('01').data('01')\.msb('FF').lsb('02').data('00')\.msb('FF').lsb('03').store()\.msb('FF').lsb('04').data('FF')\.msb('FF').lsb('05').data('FF')\.msb('FF').lsb('06').halt()\.build()byte_core=ByteCore(memory_bytes)byte_core.cycle_until_halt()dump=byte_core.dump()dump.memory[-1]# equals 95For further clarity, here's the Python equivalent of the ByteCore advanced program:output=0#                         # JMP to 1a=55b=20c=2d=1whileTrue:#                     # 1accumulator=a# LOADaccumulator+=c# ADDa=accumulator# STOREaccumulator=b# LOADaccumulator-=d# SUBb=accumulator# STOREaccumulator=b# LOADifaccumulator==0:# JZ to 2break#                     # JMP to 1#                         # 2accumulator=a# LOADoutput=accumulator# STORE#                         # HALTprint(output)# prints 95LicenseThis project is licensed under the terms of the MIT License. See theLICENSEfile for the full text.