We are currently working on new rules for what content should and shouldn't be allowed on this website, and are looking for feedback! See Esolang:2026 topicality proposal to view and give feedback on the current draft.
User:Enny/ENNCPU
| Paradigm(s) | imperative |
|---|---|
| Designed by | Juice F. Ennedy |
| Appeared in | |
| Memory system | register, cell |
| Computational class | Turing-complete |
| Reference implementation | |
| Influenced by | ez80, ARM7 |
| File extension(s) | .enn |
ENNCPU is an instruction set architecture (ISA) for a fantasy 24-bit CPU.
Design
ENNCPU is meant to be a somewhat serious ISA inspired by real CPUs and designs of the eighties and nineties, meant to represent a CPU that would power high-end workstations in an alternative computing history. It is not very esoteric, as it is not particularly difficult to program in, but it nevertheless has some unusual and otherwise rare design choices. General outline of important or interesting features:
- Eight general-purpose 24-bit registers named
A, B, C, D, E, F, G, H - Shadow registry i.e. secondary register file that can be swapped out at need (cf. z80, ez80)
- Special-purpose registers, including a stack pointer, inaccessible for general computation
- 24-bit address space (cf. ez80), and all GP registers can be used as address registers
- Two permission levels (kernel and usermode)
- Simple MMU managing mapping and permissions for 4K pages
- Two-operand instructions (cf. x86) where the destination is one of the sources
- Fixed-width 16-bit instructions (cf. Arm Thumb 1)
- 24-bit floating point instructions operating on the same registers as normal arithmetic (cf. RISC-V Zfinx ext.)
- Packed 3x8-bit operations providing SIMD in a single GP register (cf. RISC-V P ext.)
- Predication for all instructions (cf. Arm6/7/11), determined by single bit
- Size of immediate arguments limited to 6 or rarely 8 bits (cf. small immediates in Arm)
- Non-predicated conditional jumps
JxZy, JxNZybased on whether a register is (non)zero (cf. Aarch64CBZ, CBNZ)
ENNCPU is trivially the same computation class as an average general-purpose CPU; if we say that x86 or the ez80 are Turing-complete, then so is ENNCPU (despite memory limitations).
Example code
Here is an example of a short ENNCPU program that generates the first 38 members of the Fibonacci sequence (up to register limits):
.ORG 0x0000
.SEC %PGA
@START
ADRL A, @STK ; 6800
ADRM A, @STK ; 7004 / loads addr. of @STK into A
WSP A ; 2c11 / writes A to SP
@FIB
MOV A, #0 ; 6800
MOV B, #1 ; 6a01
MOV H, #37 ; 6725
@FIB_LOOP
PSHS A ; 2c02
MOV C, B ; 0391
ADD B, A ; 0208
MOV A, C ; 0382
SUB H, #1 ; 23c1
JMNZO H, @FIB_LOOP ; 55e5
ERR ; 0000
; at this point the stack contains the
; Fibonacci numbers in reverse order
%PGA
.ORG 0x0400
@STK
Instruction Set Overview
| Instruction | Full Name | Pseudo-C++ Equivalent |
|---|---|---|
đđđ đ.P ... |
predicated (any insn.) | if(FLAG) { /* ... */ }
|
ERR |
error | quit();
|
MOV Rd, Rs |
move register | Rd = Rs;
|
MOVL Rd, #imm8 |
move into low | Rd = (arg & 0xff);
|
ADRL Rd, @label
| ||
MOVM Rd, #imm8 |
move into mid | Rd &= 0xff00ff; Rd |= ((arg & 0xff) << 8); |
ADRM Rd, @label
| ||
MOVH Rd, #imm8 |
move into high | Rd &= 0x00ffff; Rd |= ((arg & 0xff) << 16); |
ADRH Rd, @label
| ||
GETL Rd, Rs |
get low | Rd = (Rs & 0xff);
|
GETM Rd, Rs |
get mid | Rd = (Rs & 0xff) >> 8;
|
GETH Rd, Rs |
get high | Rd = (Rs & 0xff) >> 16;
|
HALFA Rd, Rs |
get lower 12b | Rd = Rs & 0xfff;
|
HALFB Rd, Rs |
get upper 12b | Rd = (Rs >> 12) & 0xfff;
|
ADD Rd, Rs/#imm6 |
add | Rd += Rs;Rd += imm6;
|
SUB Rd, Rs/#imm6 |
subtract | Rd -= Rs;Rd -= imm6;
|
MULA Rd, Rs |
multiply low | u64 temp = u64(Rd) * Rs; Rd = temp & 0x00ffffff; |
MULB Rd, Rs |
multiply high | u64 temp = u64(Rd) * Rs; Rd = (temp >> 24) & 0x00ffffff; |
DIV Rd, Rs |
integer divide | Rd /= Rs;
|
MOV Rd, Rs |
modulo/remainder | Rd %= Rs;
|
CEQ Rd, Rs/#imm6 |
check equal | FLAG = (Rd == Rs);
|
OR CEQ Rd, Rs/#imm6 |
if(Rd == Rs) { FLAG = true; }
| |
CNE Rd, Rs/#imm6 |
check nonequal | FLAG = (Rd != Rs);
|
OR CNE Rd, Rs/#imm6 |
if(Rd != Rs) { FLAG = true; }
| |
CGT Rd, Rs/#imm6 |
conditional greater | FLAG = (Rd > Rs);
|
OR CGT Rd, Rs/#imm6 |
if(Rd > Rs) { FLAG = true; }
| |
CGE Rd, Rs/#imm6 |
conditional greater-or-equal | FLAG = (Rd >= Rs);
|
OR CGE Rd, Rs/#imm6 |
if(Rd >= Rs) { FLAG = true; }
| |
CLT Rd, Rs/#imm6 |
conditional less than | FLAG = (Rd < Rs);
|
OR CLT Rd, Rs/#imm6 |
if(Rd < Rs) { FLAG = true; }
| |
CLE Rd, Rs/#imm6 |
conditional less-or-equal than | FLAG = (Rd <= Rs);
|
OR CLE Rd, Rs/#imm6 |
if(Rd <= Rs) { FLAG = true; }
| |
CAND Rd, Rs |
conditional AND | FLAG = (Rd && Rs);
|
COR RD, Rs |
conditional OR | FLAG = (Rd || Rs);
|
CNAND Rd, Rs |
conditional NAND | FLAG = !(Rd && Rs);
|
CNOR Rd, Rs |
conditional NOR | FLAG = !(Rd || Rs);
|
BAND Rd, Rs/#imm6 |
bitwise AND | Rd = (Rd & Rs);
|
BOR Rd, Rs/#imm6 |
bitwise OR | Rd |= Rs;
|
BXOR Rd, Rs |
bitwise XOR | Rd ^= Rs;
|
BNOR Rd, Rs |
bitwise NOR | Rd = ~(Rd | Rs);
|
SET Rd, #imm6 |
set bit | Rd |= (1 << arg);
|
CLR Rd, #imm6 |
clear bit | Rd &= ~(1 << arg);
|
TGL Rd, #imm6 |
toggle bit | Rd ^= (1 << arg);
|
CBIT Rd, #imm6 |
test bit | FLAG = (Rd & (1 << arg));
|
PSHB Rs |
push byte | SP -= 1; PUSH8(Rs);
|
POPB Rd |
pop byte | Rd = POP8(SP); SP += 1;
|
PSHW Rs |
push word | SP -= 2; PUSH16(Rs);
|
POPW Rd |
pop word | Rd = POP16(SP); SP += 2;
|
PSHS Rs |
push sesqui | SP -= 3; PUSH24(Rs);
|
POPS Rd |
pop sesqui | Rd = POP24(SP); SP += 3;
|
LDRB Rd, Rs |
load byte | Rd = READ8(Rs);
|
LDRW Rd, Rs |
load word | Rd = READ16(Rs);
|
LDRS Rd, Rs |
load sesqui | Rd = READ24(Rs);
|
STRB Rs, Rd |
store byte | STORE8(Rd, Rs & 0xff);
|
STRW Rs, Rd |
store word | STORE16(Rd, Rs & 0xffff);
|
STRS Rs, Rd |
store sesqui | STORE24(Rd, Rs & 0xffffff);
|
LDRx Rd, -Rs |
load with predecrement | Rs -= x; Rd = READx(Rs);
|
LDRx Rd, +Rs |
load with preincrement | Rs += x; Rd = READx(Rs);
|
LDRx Rd, Rs- |
load with postdecrement | Rd = READx(Rs); Rs -= x;
|
LDRx Rd, Rs+ |
load with postincrement | Rd = READx(Rs); Rs += x;
|
STRx Rs, ±Rd |
store with preinc/dec. | ...
|
STRx Rs, Rd± |
store with postinc/dec. | ...
|
FCNV Rd, Rs |
convert to float | Rd = f24(Rs);
|
FCST Rd, Rs |
cast from float | Rd = u24(R2);
|
FC0 Rd |
fp24 constant zero | Rd = 0.0;
|
FC1 Rd |
fp24 constant one | Rd = 1.0;
|
FC2 Rd |
fp24 constant two | Rd = 2.0;
|
FCSQ2 Rd |
fp24 constant â2 | Rd = 1.414213562;
|
FCPHI Rd |
fp24 constant Ï | Rd = 1.6180339;
|
FCPI Rd |
fp24 constant Ï | Rd = 3.1415926;
|
FTAU Rd |
fp24 constant Ï | Rd = 6.283185;
|
FCE Rd |
fp24 constant e | Rd = 2.718282;
|
FNEG Rd, Rs |
floating point negate | Rd = -f24(Rs);
|
FABS Rd, Rs |
floating point abs. value | Rd = std::abs(Rs);
|
FREC Rd, Rs |
floating point reciprocal | Rd = 1.0/Rs;
|
FADD Rd, Rs |
floating point add | Rd = f24(Rd) + f24(Rs);
|
FSUB Rd, Rs |
floating point sub | Rd = f24(Rd) - f24(Rs);
|
FMUL Rd, Rs |
floating point mul | Rd = f24(Rd) * f24(Rs);
|
FDIV Rd, Rs |
floating point div | Rd = f24(Rd) / f24(Rs);
|
RET |
return from jump-link | IP = POP24();
|
JMA @label/#imm9 |
jump absolute (to first 512B) | IP = label_addr;
|
JLA @label/#imm9 |
jump-link to first 512B | PUSH24(IP); IP = label_addr; |
JxO @label/±#imm5 |
jump with offset | IP += i6(arg);
|
JxR Rs |
jump to register | IP = Rs;
|
JxZO Rt, @label/±#imm5 |
jump with offset if zero | if(Rt == 0) { IP += i6(arg); }
|
JxZR Rt, Rs |
jump to register if zero | if(Rt == 0) { IP = Rs; }
|
JxNZO Rt, @label/±#imm5 |
jump with offset if nonzero | if(Rt) { IP += i6(arg); }
|
JxNZR Rt, Rs |
jump to register if nonzero | if(Rt) { IP = Rs; }
|
FAR JMO @label/±#imm13 |
far jump with offset | IP += i14(arg);
|
FAR JLO @label/±#imm13 |
far jump-link with offset | PUSH24(IP); IP += i14(arg); |