This is both a custom CPU and Assembler that reads/compile into binary files and is capable of solving some hard problems
- Come up with the instructions
- Memory Addressing
- Lexical analysis
- Debugging bits
[ x ] CPU can read binary files
[ x ] CPU can translate binary into instructions
[ x ] CPU has some form of control of it own memory
[ x ] CPU can perform procedures
[ x ] CPU accept 26 custom instructions
[ x ] Assembler can read .was file
[ x ] Assembler can translate Assembly language into Machine code
The CPU performs three basic steps: Fetch -> Decode -> Execute. First, we load our program into main memory, with a maximum size of 10 KB. Next, we set the PC (Program Counter) to zero and begin fetching bytes from that position. The next step is to read the instruction:
instruc - regA - regB - value
0000 0000 - 0000 - 0000 - 0000 0000 0000 0000
After that, we decode the instruction to determine what the CPU is supposed to do and then execute the corresponding operation.
The Wasilewski features a 256-byte stack that supports basic stack operations. It's recommended for storing register values using the PUSHR instruction.
The BookKeeper (BK) is a 512-byte address holder responsible for all memory operations, as it is the only component that interfaces with the ROM. It is useful for storing initial variable values.
The CPU includes a 1024-byte Read-Only Memory (ROM) segment. This memory is used solely for storing variables or values set by the LOAD and STORE commands.
Clone it with git
$ git clone https://github.com/WasixXD/Wasilewski
Run the tests with
chmod +x ./test.sh
./test.sh
Create a file ending with .was
and compile it with:
$ ./asm.o your_file.was output.bin
After that you can execute the binary with:
$ ./machine.o output.bin
The CPU primarily performs two tasks: processing raw numeric values up to a maximum of 65536, and working with values stored in registers. Most instructions don't require a second register or any register at all.
We have four basic commands for arithmetic operations: ADD
, SUB
, MUL
, and DIV
. Each command requires you to specify the register that will receive the result, called regA
, and a second parameter, which can either be another register (regB) or a raw value.
AND
, OR
, NOT
, XOR
, SHR
, SHL
are all bitwise operations.
NOT
: Don't need a second parameterSHR
(Shift Right by 1 bit): Don't need a second parameterSHL
(Shift Left by 1 bit): Don't need a second parameter
JMP
(JUMP): Sets the PC to the line that we will jump. Notice that the program starts counting from 0JE
(Jump if Equal): Skips the next instruction if theregA
equals to the second parameterJNE
(Jump if Not Equal): Skips the next instruction ifregA
not equals to the second parameterJG
(Jump if Greater than): Skips the next instruction ifregA
is greater than the second parameterJNG
(Jump if Not Greater than): Skips the next instruction ifregA
is not greater than the second parameter
MOV
(Move): Requires bothregA
and a second parameter. The second parameter can be three things:-
raw value
: Numeric values that can go up to 65536$
: With the dollar sign the CPU will put the memory address of that location intoregA
[]
: Any value surronded by square brackets is referring to the value "inside" that memory address. That will be put in toregA
LOAD
: Expects both parameters and always load the value "inside" of that memory address.LOAD
does not require the[]
STORE
: Store the value ofregA
on a specifyc memory address setted up by the user
PUSH
: Push eithersregA
or the value specified to the stackPOP
: Pop toregA
the value from the stackPUSHR
: Push all registers to the stackPOPR
: Pop to registers the values from the stack
CALL
: Push the PC to the stack and then Jumps to specified procedure lineRET
: Pop to PC the value to the stack and Jumps back to previously line
When writing the procedure don't forget to put thename
+ a:
. Ex:procedure_number_1:
The CPU can handle only two SYS CALLS: read and write. And both have some differences because reading a number != string
-
To read a string
-
- First set register
B
to 0 - Then register
C
to 1 (reading string) - Finally put on
D
thesize of string + 1
- The value read will be put on register
A
- First set register
-
To read a number
-
- First set register
B
to 0 - Then register
C
to 0 (reading a number) - The value read will be put on register
A
- First set register
-
To write a string
-
- First set register
B
to 1 - Then register
C
to 1 (writing string) - Finally put on
D
thesize of string + 1
- The value written will be the register
A
- First set register
-
To write a number
-
- First set register
B
to 1 - Then register
C
to 0 (writing a number) - The value written will be the register
A
- First set register
0 MOV a, 2
1 CALL double; assembler -> CALL 5
2 ADD a, 1
3 HLT
4
5 double:
6 MUL a, 2
7 RET
8 HLT
binary
0001 0101 0001 0000 0000 0000 0000 0010
0001 1100 0000 0000 0000 0000 0000 0101
0000 0001 0001 0000 0000 0000 0000 0001
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0001
0000 0011 0001 0000 0000 0000 0000 0010
0001 1101 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
Print the Fibonacci number at the index specified by the user.
MOV d, 0
MOV b, 0;b = 0
MOV c, 0;c = 0
SYS
STORE a, 10; [10] = a
MOV a, 0; a = 0
MOV b, 1; b = 1
ADD c, a; c += a
ADD c, b; c += b
MOV a, b; a = b
MOV b, c; b = c
PUSH a; [a]
PUSH b; [a b]
MOV a, c; a = c
PUSH c; [a b c]
MOV b, 1; b = 1
MOV c, 0; c = 0
SYS
POP c; c = [a b *c*]
POP b; b = [a *b*]
POP a; a = [*a*]
PUSH a; [a]
LOAD a, 10; a = [10]
JE a, d; a == d?
HLT
POP a; a = [*a*]
ADD d, 1; d += 1
XOR c, c; c ^ c
JMP 7; goto 7
binary
0001 0101 0100 0000 0000 0000 0000 0000
0001 0101 0010 0000 0000 0000 0000 0000
0001 0101 0011 0000 0000 0000 0000 0000
1000 0000 0000 0000 0000 0000 0000 0000
0001 0111 0001 0000 0000 0000 0000 1010
0001 0101 0001 0000 0000 0000 0000 0000
0001 0101 0010 0000 0000 0000 0000 0001
0000 0001 0011 0001 0000 0000 0000 0000
0000 0001 0011 0010 0000 0000 0000 0000
0001 0101 0001 0010 0000 0000 0000 0000
0001 0101 0010 0011 0000 0000 0000 0000
0001 1000 0001 0000 0000 0000 0000 0000
0001 1000 0010 0000 0000 0000 0000 0000
0001 0101 0001 0011 0000 0000 0000 0000
0001 1000 0011 0000 0000 0000 0000 0000
0001 0101 0010 0000 0000 0000 0000 0001
0001 0101 0011 0000 0000 0000 0000 0000
1000 0000 0000 0000 0000 0000 0000 0000
0001 1001 0011 0000 0000 0000 0000 0000
0001 1001 0010 0000 0000 0000 0000 0000
0001 1001 0001 0000 0000 0000 0000 0000
0001 1000 0001 0000 0000 0000 0000 0000
0001 0110 0001 0000 0000 0000 0000 1010
0001 0001 0001 0100 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
0001 1001 0001 0000 0000 0000 0000 0000
0000 0001 0100 0000 0000 0000 0000 0001
0000 1000 0011 0011 0000 0000 0000 0000
0001 0000 0000 0000 0000 0000 0000 0111
Counting the number of 1 in the user input
MOV b, 0;b = 0
MOV c, 0;c = 0
SYS ;a = sys
ADD b, 1 ;1 b++
SHR a;a = a >> 1
JE e, 1 ;if e == 1
ADD c, 1 ;c++
JE b, 8 ;if b == 8
CALL print
JMP 3 ;goto 3
print:
MOV a, c; a = c
MOV b, 1; b = 1
MOV c, 0; c = 0
SYS
HLT
binary
0001 0101 0010 0000 0000 0000 0000 0000
0001 0101 0011 0000 0000 0000 0000 0000
1000 0000 0000 0000 0000 0000 0000 0000
0000 0001 0010 0000 0000 0000 0000 0001
0000 1001 0001 0000 0000 0000 0000 0000
0001 0001 0101 0000 0000 0000 0000 0001
0000 0001 0011 0000 0000 0000 0000 0001
0001 0001 0010 0000 0000 0000 0000 1000
0001 1100 0000 0000 0000 0000 0000 1010
0001 0000 0000 0000 0000 0000 0000 0011
0000 0000 0000 0000 0000 0000 0000 0000
0001 0101 0001 0011 0000 0000 0000 0000
0001 0101 0010 0000 0000 0000 0000 0001
0001 0101 0011 0000 0000 0000 0000 0000
1000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
Calculate the factorial based on user input
MOV b, 0
MOV c, 0
SYS
MOV d, a
SUB d, 1
MUL a, d
JE d, 1
CALL print
JMP 4
print:
MOV b, 1
MOV c, 0
SYS
HLT
binary
0001 0101 0010 0000 0000 0000 0000 0000
0001 0101 0011 0000 0000 0000 0000 0000
1000 0000 0000 0000 0000 0000 0000 0000
0001 0101 0100 0001 0000 0000 0000 0000
0000 0010 0100 0000 0000 0000 0000 0001
0000 0011 0001 0100 0000 0000 0000 0000
0001 0001 0100 0000 0000 0000 0000 0001
0001 1100 0000 0000 0000 0000 0000 1001
0001 0000 0000 0000 0000 0000 0000 0100
0000 0000 0000 0000 0000 0000 0000 0000
0001 0101 0010 0000 0000 0000 0000 0001
0001 0101 0011 0000 0000 0000 0000 0000
1000 0000 0000 0000 0000 0000 0000 0000
0000 0000 0000 0000 0000 0000 0000 0000
Registers | Binary |
---|---|
A_REGISTER | 0001 |
B_REGISTER | 0010 |
C_REGISTER | 0011 |
D_REGISTER | 0100 |
E_REGISTER | 0101 |
Opcode | Binary | Opcode | Binary |
---|---|---|---|
HLT | 00000000 | MOV | 00010101 |
ADD | 00000001 | LOAD | 00010110 |
SUB | 00000010 | STORE | 00010111 |
MUL | 00000011 | PUSH | 00011000 |
DIV | 00000100 | POP | 00011001 |
AND | 00000101 | PUSHR | 00011010 |
OR | 00000110 | POPR | 00011011 |
NOT | 00000111 | CALL | 00011100 |
XOR | 00001000 | RET | 00011101 |
SHR | 00001001 | SYS | 10000000 |
SHL | 00001010 | ||
JMP | 00010000 | ||
JE | 00010001 | ||
JNE | 00010010 | ||
JG | 00010011 | ||
JNG | 00010100 |