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

From Esolang
Jump to navigation Jump to search
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, JxNZy based on whether a register is (non)zero (cf. Aarch64 CBZ, 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);