angel-cpu 0.8
A CPU framework
Requires Python: >=3.8

Hello!\
Have you ever tried to make a cpu simulation but don't want to write 300+ lines of code?\
Well then you don't have to anymore!\
AngelCPU does most of the heavy lifting for you, so let's get started before I fall asleep while typing this.
# Getting Started
First lets install Angel
```bash
pip install angel-cpu
```
Now, let's start with some simple stuff,
*give me a second i'm setting up*
```python
from angel_cpu import *
ARCH=cpu("ARCH")
```
*Alright now i'm done setting up.*
As you can see we have a class here
```python
from angel_cpu import *
ARCH=cpu("ARCH")#THIS RIGHT HERE
```
Let's first set up the basic CPU ARCH
```python
from angel_cpu import *
ARCH=cpu("ARCH")
# no reset does NOT reset the ARCH's registers and ram it just resets the ARCH's PC and Program
ARCH.reset()
# The name of the Register doesn't matter go crazy if you want
# The amount of Registers also don't matter, you can add like 50 of them and it wouldn't care
ARCH.add_register("Rxa")
ARCH.add_register("Rxb")
ARCH.add_register("Rxc")
ARCH.add_register("Rxe")
# the size is calculated by the x (400) times the y (200)
ARCH.add_ram(400,200)
# 32 bit integer limit
ARCH.set_max(0xffffffff)
# we're setting the command symbol to be ; so it's like real ASM
ARCH.set_comment_symbol(";")
```
*huff* that took me 30 minutes to write... (all of the time was because of the comments XD)\
Anyway... let's go over what the eclipse is all of these before my head explodes

`ARCH.reset()`

Resets the ARCH's PC and Program

`ARCH.add_register("Register name")`

adds a register

`ARCH.add_ram(400,200)`

set's the ram size which with this one will be... *give me a sec i'm calculating...* 80000 cells... *WOW*

`ARCH.set_max(0xffffffff)`

sets the integer limit, I just put the 32 bit limit because it's the bare minimum *trust me with this, you are going to need **much** more than 4294967295 bits **trust me***

`ARCH.set_comment_symbol(";")`

This Sets the comment symbol, any line that starts with it will be ignored in the assembler.\

Now that we got that covered it's time to add some opcodes so we can actually do stuff
```python
from angel_cpu import *
ARCH=cpu("ARCH")
# no reset does NOT reset the ARCH's registers and ram it just resets the ARCH's PC and Program
ARCH.reset()
# The name of the Register doesn't matter go crazy if you want
# The amount of Registers also don't matter, you can add like 50 of them and it wouldn't care
ARCH.add_register("Rxa")
ARCH.add_register("Rxb")
ARCH.add_register("Rxc")
ARCH.add_register("Rxe")
# the size is calculated by the x (400) times the y (200)
ARCH.add_ram(400,200)
# 32 bit integer limit
ARCH.set_max(0xffffffff)
# we're setting the command symbol to be ; so it's like real ASM
ARCH.set_comment_symbol(";")
def MOV(reg,value):
    ARCH.set_register(reg,value)
def LOG(txt):
    if txt in ARCH.registers:
        print(ARCH.registers[txt])
    else:
        print(txt)
# It's important to note that when adding opcodes don't put parentheses on the function
# ARCH.add_opcode(0x0,MOV()) <-- incorrect, paratheses arnt opposed to be in the opcodes 
# ARCH.add_opcode(0x0,MOV) <-- correct
ARCH.add_opcode(0x0,MOV)
ARCH.add_opcode(0x1,LOG)
c="""
MOV Rxa 10
LOG Rxa
"""
ARCH.assemble(c)
ARCH.run()
```
Okay there's a lot of stuff here so let's talk about the things that changed

`ARCH.add_opcode(hex_ID function)`

This adds an opcode, and when adding the function, please, **do not add parentheses**

`ARCH.assemble(code)`

This assembles the code from ASM format into something that the CPU can actually understand, then it loads it into the CPU's program ready to run.

`ARCH.run()`

This takes the code from the Program and runs it, simple and very useful.

Now let's talk about the ASM format
```AngelASM
MOV Rxa 10
LOG Rxa
```
The command comes first, each argument comes after the command separated by spaces, but then you might be wondering, "what about strings with spaces in them?" well, strings are strings so there has to be these (") surrounding them, without that it wouldn't be a string!

Alright this is taking a long time so i'll just boot something give me a sec...

**\[BIOS\]: BOOTING CMD DUMP...**

**\[BIOS\]: INIT REG**

**\[REG\]: \[OK\]**

**\[BIOS\]: INIT SD**

**\[SD\]: \[OK\]**

**\[BOOTING ASM_HELPER\]: \[OK\]**

**\[ASM_HELPER\]: \[OK\]**

Alright, I booted ASM_helper to speed things along, here's the rest of the commands along with some stuff I already covered

`add_register`:
adds a register, there's not much to say about this, but registers can be named anything, however their *value* cannot go over the integer limit, we'll go over how to set that in a bit
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.add_register("register")
```
`add_ram`:
sets the ram, the size is calulated by the X times the Y however the RAM is a 1D plane, so you can just use `get_ram(addr)` with the addr being an int
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.add_ram("size_x","size_y")
```
`add_opcode`:
adds an opcode for a function, please note that commands must be inputted without parentheses or you RISC *(haha get it?)* making the commands have set arguments
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.add_opcode("opcode","commmand")
```
`add_instruction`:
This is for those of you who want to make your own assembler, or those of you who just want to add instructions without the assembler
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.add_instruction("opcode")
```
`set_window_name`:
This sets the Window's name, and as a side note the window's name starts as the arch's name, but you can set this to what ever you want
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.set_window_name("name")
```
`set_max`:
sets the integer limit, it can be any type of int, like hex, and i recomend using hex because it's much easier to write large numbers in hex, for example 0xffffffff in decimal is 4294967295, now you see what i mean?
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.set_max("number")
```
`set_pc`:
sets the PC, for those of you who aren't fluent in tech, PC means program counter, it tells the CPU what instruction it's meant to be looking at, and it's very useful with making commands like JMP
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.set_pc("number")
```
`set_register`: sets a register's value, however if the register's value overflows to more than the integer limit the register's value resets back to it's base, depending on the type of data the register was holding the fallback will be different, for example
if it was holding a string, and the string's length was longer than the max interger limit it falls back to an empty string ("") the same is for lists, dicts, tuples, and ints, with ints having a fallback of 0x0
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.set_register("register","value")
```
`set_ram`:
sets a ram cell's value, if the value is larger than the integer limit it will fall back to empty, with lists,dicts, and tuples they will fallback to and empty version of themselves with ints falling back to 0x0
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.set_ram("address","value")
```
`reset`:
resets the PC and the program of the CPU, with the PC resetting to 0x0 and the program resetting back to none
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.reset()
```
`set_comment_symbol`:
sets the command symbol, if you where to set it to ; any line starting with ; would be commented out and ignored by the assembler
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.set_comment_symbol("symbol")
```
`get_register`:
returns the value of the selected register, that's it.
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.get_register("register")
```
`get_ram`:
gets the value of the selected ram cell, that's it
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.get_ram("address")
```
`get_pc`:
returns the value of the PC of the CPU, that's it.
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.get_pc()
```
`text`:
prints text onto the screen window, the `text` will not render until `frame` is called, the AddToTxtLog input must either be `True` or `False`, `False` means when you write `text_input` the text will not show up on the screen, while `True` means it will, the `end` input is what will be put on the end of the text, if end is not `"\n"` then it will not create a new line when drawing, the first input in the `customy` input must either be `True` or `False`, `True` means `customy` is enabled, if it is False then `customy` will not be enabled, the second input for `customy` is the y value, note that the y value will only be that if `customy` is enabled
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.text("text","x_pos",("color_R","color_G","color_B"),"Add_to_text_log","end",("Is customy enabled?","y_pos"))
```
`wait`:
waits for the set amount of time, note that the time is in milliseconds.
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.wait(1000)
```
`scroll`:
scrolls the screen by the set amount
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.scroll("X","Y")
```
`is_Mouse_Touching_Surface`:
it returns True is the mouse if touching a surface, else, it returns False
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.is_Mouse_Touching_Surface("name")
```
`show_mouse`:
it sets the mouse's visiblity, if true it shows the mouse, if false it doesn't else? it breaks, it must either be true or false
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.show_mouse("True, or False")
```
`quit`:
when this command is called it quits the screen and the app, i recommend only using it when you need to close EVERYTHING
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.quit()
```
`frame`:
displays the next frame on the screen window, that's it, it just displays the next frame
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.frame()
```
`load_pixels`:
loads the pixels from the pixel buffer to the screen, it needs frame to display the changes to the screen
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.load_pixels()
```
`events`:
events returns the events that happened with key presses it returns kDWN and then the key that's pressed so for e it would return kDWNe, for key release it will return kUP so releasing the key e would make it return kUPe, for mouse down it would return a tuple, with the first being mDWN and the second being another tuple containing the mouse X and Y, when the mouse is UP it returns mUP with the pos following
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.events()
```
`clear`:
clears the screen buffer, the changes don't update until frame is called
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.clear()
```
`clear_logs`:
clears the screen logs, this makes it useful with `text_input` because it stops trailing text
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.clear_logs()
```
`is_held`:
it returns true if the inputted key is held down, else it returns false
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.is_held("key")
```
`render_surfaces`:
This renders the surfaces, you have to call frame to see the effects though
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.render_surfaces()
```
`add_surface`:
This adds a surface, you have to call render_furfaces and frame to see them
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.add_surface("name","width","height",("color_R","color_G","colo_rB"),"pos_X","pos_Y")
```
`add_pixel`:
adds a pixel, you have to call load_pixels and frame to see the effects
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.add_pixel("pos_x","pos_y",("color_R","color_G","colo_rB"),"size","group","addtopixellog")
```
`text_input`:
Text_input displays text on the screen as a prompt, then after the key enter is pressed, it returns what the user typed
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.text_input("prompt",("color_R","color_G","colo_rB"),"prompt_end","prompt_x")
```
`mouse_down_on_group`:
This returns True if the mouse has clicked a pixel group
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.mouse_down_on_group("event output","group")
```
`mouse_pos`:
returns the mouse pos in a tuple
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.mouse_pos()
```
`assemble`:
one of the most useful commands in angel, it compiles your ASM code into opcodes so your CPU can run it, now it compiles ASM not the opcodes themselves so don't go inputting 0x0 for MOV or 0x1 for JMP that just won't work
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.assemble("code")
```
`run`:
runs the code in the program, the program is set by using assemble
```python
from angel_cpu import *
ARCH=cpu("ARCH")
ARCH.run()
```
`0.7 commands`:
the following commands are the new commands added in angel-cpu 0.7

**\[SD\]: LOADING 0.7 COMMANDS**

**\[SD\]: \[OK\]**

`add_disk`:
Adds a disk, add_disk returns a disk class, using this you can create disks and persisting storage, if you are trying to use the ARCH object with the disk use `attach_device`
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.add_disk("sector_size","sectors")
```
**\[SD\]: found multiple entries in the `Disk` class, go over them as well?**

uh... sure...

**\[SD\]: Loading `Disk` sub commands...**

`write`:
writes to the inputted address, note that the address is a tuple, with the first input being the sector and the second being the cell
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.add_disk("sector_size","sectors")
x.write(("addr-sector","addr_cell"),"data")
```
`read`:
reads from the inputted address, note that the address is a tuple, with the first input being the sector and the second being the cell
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.add_disk("sector_size","sectors")
x.read(("addr_sector","addr_cell"))
```
`format`:
formats the Disk, returns the Disk back to when it was first defined
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.add_disk("sector_size","sectors")
x.format()
```
`dump`:
dumps the disk, this dumps the disk to a file, the `name` input is the name of the file, so i recomend you select one name and stick with it
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.add_disk("sector_size","sectors")
x.dump("name")
```
`load`:
loads the disk from the inputted file, note that the name must be exactly what the Disk's file name is
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.add_disk("sector_size","sectors")
x.load("name")
```

**\[SD\]: `Disk` sub commands completed, returning back to CPU commands...**

`attach_device`:
attaches a device to the CPU, the devices can be anything, extra storage, disks, and if you wanted to, you could add an entire second CPU as an device, but i recommend you don't do that, it's hard to manage two CPUs a once,
```python
from angel_cpu import *
ARCH=cpu("ARCH")
device=ARCH.add_disk("sector_size","sectors")
ARCH.attach_device("name",device)
```
`get_device`:
returns the inputted device, this one returns the inputted device's class object, but like how not everything is a potato, not all devices are class based, so it returns whatever the device is
```python
from angel_cpu import *
ARCH=cpu("ARCH")
x=ARCH.add_disk("sector_size","sectors")
ARCH.attach_device("disk",x)
div=ARCH.get_device("disk")
```

And that's all for this update!