Ferale.art


Louve

Louve is a 16 bits processor architecture. She* has 16 operations and handles 16 bits numbers (which means the highest positive number is 65535).

Memory addresses are also 16 bits numbers, so the highest amount of memory a program can have is 64K words.

* Louve is a female wolf in the Ferale Computer project.

Operations

0 MOV Move a value from a source to a destination
1 ADD Add two values together
2 ADC Add to values plus the carry of the previous operation
3 SUB Subtract two values
4 SBC Subtract two values and the carry of the previous operation
5 SHF Shift bits left or right (multiply or divide by 2) (1)
6 ROT Rotate bits left or right (1)
7 SWP Swap two values
8 NOT Inverse all bits of a value
9 AND Logical AND between two values
A IOR Inclusive OR between two values
B XOR Exclusive OR between two values
C CMP Compare two values and save the results in flags Z, N and V (2)
D DMC Direct Memory Copy (3)
E Command (4)
F Jump (5)

Operation encoding

Each operation is expressed in binary as a 16 bits number. It's easy to express in hexadecimal because every nibble (pack of 4 bits) encodes a different part of the operation.

Operation Source register Destination register Modes
???? ???? ???? ?? ??

Example

1AB9
1 A B 9
Operation Source register Destination register Modes
0001 1010 1011 10 01
ADD Register A Register B Pull Reference

The operation 1AB9 reads memory at address given by register A, increment this address (pull mode), then reads memory at address given by register B, adds the values together (ADD) and puts the result in memory at address given by register B.

In assembly language, it would be written ADD (A++), (B).

In a real situation, it could be part of a "sum" function: Register A would point to the list of values to be summed, and B to the result. Calling this in a loop would sum up all values.

Modes

00 Direct R Read or write the value in the register
01 Reference (R) Read or write the value in memory at address in register
10 Pull (R++) Read or write in memory then increment address in register
11 Push (--R) Decrement address in register then read or write in memory
Source mode Destination mode
00 Source
01 Memory[Source]
10 Memory[Source] (then Source+1)
11 Memory[Source-1]
00 Destination
01 Memory[Destination]
10 Memory[Destination] (then Destination+1)
11 Memory[Destination-1]

(1) Shift and Rotate

Those two operations don't use the Source argument like others. Instead of using Source as a register, it tells how many bits to shift or rotate, and the Source mode gives the direction.

Shift Bits to shift Destination register Direction Destination mode
0101 ???? ???? ?? ??
00 To the right
01 To the left
Rotate Bits to rotate Destination register Direction Destination mode
0110 ???? ???? ?? ??
00 To the right
01 To the left

(2) Comparison

CMP compares two values and save the result in three flags. It's up to you to decide which result is significant depending on what the initial numbers were supposed to be.

Z A=B
N A>B (unsigned)
V A>B (signed)

(3) Direct Memory Copy

DMC does an immediate copy of multiple consecutive values from one part of memory to another.

It's faster than implementing a loop yourself.

Copy Source Destination Size
1101 ???? ???? ????

Source and Destination registers must contain addresses. They will be read with mode 10 (pull).

Size register must contain an unsigned number which will act as a counter for the operation.

Example

    MOV 1200, R8 ; source of the data
    MOV F000, R9 ; destination address
    MOV   80, RA ; size = 128 words
    DMC R8,R9,RA

(4) Command

E0 HALT Stop the program
E1 RTI Return from interrupt
E2 SWI Software interrupt to address in Destination
E3 INCB Move to next register bank
E4 DECB Move to previous register bank

(5) Jump

Jump Source register Condition Source Mode Type
1111 ???? ? ??? ?? ??
Source Condition Mode & Type
???? ?? Source register and mode
??? Condition (which flag to check)
? Negate condition?
00 Jump absolute
01 Call absolute (subroutine)
10 Jump relative
11 Call relative (subroutine)

Jump example

FA95
F A 9 5
Jump Source Condition Mode & Type
1111 1010 1 001 01 01
1010 01 Get address from Memory[Register A]
1 001 If max is not set (flag 1)
01 Call subroutine at absolute address

Registers and flags

R0 R1 R2 R3 R4 R5 R6 R7
Banked registers (can be switched with INCB and DECB)
R8 R9 RA RB RC SP SR PC
Fixed registers Stack pointer Status register Program counter
SR
? ? ? ? ? ? ? ? I V N Z C M A
Bank pointer (changed by INCB and DECB) (unused) Mask interrupts Overflow Negative Zero Carry Max Always 1
111 110 101 100 011 010 001 000

Numbers

Numbers are 16 bits. It means there are encoded in binary with 16 digits.

Depending on your interpretation, numbers can be considerer unsigned (all the bits encode a positive value), or signed (the highest bit means "negative" and the remaining 15 bits encode the value).

Mixing them in operations is not recommended, and might trigger the "overflow" flag.

Min Max
Unsigned 0000 FFFF (65535)
Signed 8000 (-32768) 7FFF (32767)

Parts of a binary number have special names.

16 bits ABCD a word
8 bits AB a byte
4 bits A a nibble

Louve expects the memory to contain 64K words (65536 numbers of 16 bits).

It's usually implemented in hardware by putting two 64KB memory chips together.

Bourdon syntax (assembly language)

It can be tedious to write (and read!) a whole program in hexadecimal. So there's a program called an assembler, which reads source code and transforms it into the equivalent binary format.

The assembler for Louve is called Bourdon and has the following syntax.

Syntax

label          OPERATION arg1, arg2, ...
A line can start with a label.
      sublabel OPERATION arg1, arg2, ...
A label with spaces before is a sub-label.
               META arg1 arg2 ...
Specific keywords are assembler's meta-operations. Args are separated by spaces.
; comment
Anything after a semicolon is a comment.

Meta operations

AT address
Move the assembler to a specific address.
DEF name value
Define a constant value.
DATA val1 val2 ...
Write raw binary data to the current position.
FILL num val1 val2 ...
Repeat raw binary data to the current position NUM times.
STR string
Write raw unicode data (in UTF16) to the current position, until end of line.
RET
Return from subroutine. Shortcut for MOV (SP++), PC.

Meta examples

AT F100
Move to address F100 in the assembled binary.
DEF STDOUT 7F80
Define the name "STDOUT" to be replaced everywhere in code by the value 7F80.
DATA 6C 6F 1234
Write binary words 006C 006F 1234 at current position.
FILL 0F    A0A0 B0B0
Fill the next words of the binary with A0A0 B0B0, 15 times (0F).
STR Hello,\0ATo the world\21
Write the unicode string at current position. An antislash is used to input any hexadecimal value directly. To write an antislash, use "\\".

Hello world example

Address Program Code




0000:
0002:
0004:






0005:
0006:
0008:
0009:
000A:
000C:
000E:
000F:


0010:
0011:
0013:
0015:
0017:
0019:
001B:
001D:




0F88 0010
FF09 0005
E000






E300
0F18 F100
0808
0819
3F08 0001
FFB8 0009
E400
0DF8


000D
0048 0065
006C 006C
006F 0020
0057 006F
0072 006C
0064 0021
000A
DEF STDOUT F100

AT 0000

program  MOV hello, R8
         CALL A, print
         HALT

;; (print) a length-prefixed string to STDOUT
;; in --
;; R8 string address
;; out --
;; None
print    INCB
         MOV STDOUT, R1
         MOV (R8++), R0    ; get length
    loop MOV (R8++), (R1)  ; put char out
         SUB 1, R0         ; len--
         JUMP !Z, loop     ; repeat until len=0
         DECB
         RET

hello
DATA 0D
STR Hello world!\0A






Lexer/Parser/Assembler algorithm

Forbidden identifiers for labels

Operations: MOV ADD ADC SUB SBC SHF ROT SWP NOT AND IOR XOR CMP DMC JUMP CALL
Commands: HALT RTI SWI INCB DECB
Meta: AT DEF DATA FILL STR RET
1 letter words, including flags: I V N Z C M A
Register names: R0 R1 R2 R3 R4 R5 R6 R7 R8 R9 RA RB RC RD RE RF SP SR PC
2 and 4 digits hexadecimal numbers: FEED, DEAD, BEAF, F00D, AD, FADE...

5 letters words and above will always pass as labels. The check is only required for the first 4 letters.

A finite state machine is used to quickly check if a token is a reserved keyword (and determine which one, to be used in the parser too).

A-D-D    Operation 1
|  \
|   C    Operation 2
|\
| N-D    Operation 9
 \
  T      Meta AT

S-U-B    Operation 3
|\
| B-C    Operation 4
|\
| H-F    Operation 5
|\
| W-P    Operation 7
|  \
|   I    Command E2
|\
| P      Register RD
|\
| R      Register RE
 \
  T-R    Meta STR

D-M-C    Operation D
|\
| E-C-B  Command E4
|  \
|   F    Meta DEF
 \
  A-T-A  Meta DATA

R-O-T    Operation 6
|\
| T-I    Command E1
|\
| E-T    Meta RET
 \
  hex?   Register R?

C-M-P    Operation C
 \
  A-L-L  Operation F (call)

I-O-R    Operation A
 \
  N-C-B  Command 3

P-C      Register RF
M-O-V    Operation 0
N-O-T    Operation 8
X-O-R    Operation B
J-U-M-P  Operation F (jump)
H-A-L-T  Command 0
F-I-L-L  Meta FILL

This tree is used on every token to generate the Abstract Syntax Tree (AST) of the source code.

Then the parser checks the grammar.

Then the assembler can move through the AST and apply two passes: Generate as much binary as possible while leaving "holes" for the missing references, then passing again filling those holes with calculated values.

Credits

The initial Louve design was heavily inspired from the QNICE architecture by Bernd Ulmann and their team. Thanks to them!

The uxn architecture and the Varvara project were also a big influence on my work. The uxn community rocks!

Apart from those inspirations, the Ferale Computer project is made with love and dedication by Zoé since 2018.