1
0
ain48/docs/isa.typ
2026-06-01 18:33:05 +02:00

653 lines
20 KiB
Typst

#import ".template.typ": *
#show: conf
#title[AIN-48 Instruction Set Architecture]
#align(center, grid(
columns: 4,
column-gutter: .5em,
align: left + horizon,
row-gutter: .5em,
grid.cell(rowspan: 2)[Architectúra],
[Internátionális],
[Nórmae],
grid.cell(rowspan: 2)[--- 48 bit],
[Instrúctiónum],
[Numerátiónum],
))
#show heading: set text(weight: 900)
#show heading: it => [
#set par(justify: false)
#grid(columns: (auto, 1fr), column-gutter: .25em, if it.numbering != none {
context [#counter(heading).display(it.numbering)]
} else [], it.body)
]
= Conventions
A *byte* (singulus) is the smallest addressable part of memory, that is, a 12-bit
binary number.
A *word* (quaternus) is a binary number composed of 4 bytes, that is, a 48 bit
binary number.
In memory, multibyte numbers are laid out with the least significant byte at the
lowest memory address.
= Registers
The main registers of the architecture are all word-length:
#columns(2)[
/ %0/%nul:Constant zero
/ %01: General purpose
/ %02: General purpose
/ %03: General purpose
/ %04: General purpose
/ %05: General purpose
/ %06: General purpose
/ %07: General purpose
/ %ia: Index ad actiónem\ (Instruction pointer)
#colbreak()
/ %10: General purpose
/ %11: General purpose
/ %12: General purpose
/ %13: General purpose
/ %14: General purpose
/ %15: General purpose
/ %16: General purpose
/ %17/%it: Index ad turrim\ (Stack pointer)
]
There is also a 3-bit status register.
= Instructions as encoded
In instruction diagrams, each large rectangle represents a byte, that are
disposed in memory as implied by the reading order, starting at the lowest
memory address.
#let amal = [Actió Machinae Arithméticae Logicaeque]
== #amal
#align(
center,
cetz.canvas(
{
import cetz.draw: *
scale(.65)
byte_diag((
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [i#sub[2]\ 0]),
(1, [i#sub[1]\ 0]),
(1, [s]),
(4, [ac]),
), ((4, [r#sub[f]]), (4, [r#sub[2]]), (4, [r#sub[1]])))
content((12.25, -1))[or]
byte_diag(voff: -5, (
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [i#sub[2]\ 0]),
(1, [i#sub[1]\ 1]),
(1, [s]),
(4, [ac]),
), ((4, [r#sub[f]]), (4, [r#sub[2]]), (4, text(size: .9em)[imm[0:3]])))
content((12.25, -6))[or]
byte_diag(voff: -10, (
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [i#sub[2]\ 1]),
(1, [i#sub[1]\ 0]),
(1, [s]),
(4, [op]),
), ((4, [r#sub[f]]), (4, text(size: .9em)[imm[0:3]]), (4, [r#sub[1]])))
content((12.25, -11))[or]
byte_diag(voff: -15, (
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [i#sub[2]\ 1]),
(1, [i#sub[1]\ 1]),
(1, [s]),
(4, [ac]),
), ((4, [r#sub[f]]), (8, [imm[0:7]])))
},
),
)
#let iml = [Immediátum Magnum Lectura]
== #iml
#align(
center,
cetz.canvas(
{
import cetz.draw: *
scale(.65)
byte_diag((
(1, [0]),
(1, [0]),
(1, [0]),
(1, [1]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [s]),
(4, [r]),
), ((12, [imm[0:11]]),))
byte_diag(((12, [imm[12:23]]),), ((12, [imm[24:35]]),), voff: -3)
byte_diag(((12, [imm[36:47]]),), voff: -6)
},
),
)
#let ipl = [Immediátum Parvum Lectura]
== #ipl
#align(center, cetz.canvas({
import cetz.draw: *
scale(.65)
byte_diag((
(1, [0]),
(1, [0]),
(1, [0]),
(1, [1]),
(1, [1]),
(1, [0]),
(1, [0]),
(1, [s]),
(4, [r]),
), ((12, [imm[0:11]]),))
}))
#let amls = [Ad Memóriam Lectura Scripturaque]
== #amls
#align(center, cetz.canvas({
import cetz.draw: *
scale(.65)
byte_diag((
(1, [0]),
(1, [0]),
(1, [1]),
(1, [s]),
(4, [r#sub[d]]),
(4, [r#sub[l]]),
), ((12, [imm[0:11]]),))
}))
#let asss = [Actió Sequentiae Singulí Simplicis]
== #asss
#align(center, cetz.canvas({
import cetz.draw: *
scale(.65)
byte_diag((
(1, [1]),
(1, [1]),
(1, [1]),
(1, [1]),
(1, [1]),
(1, [1]),
(1, [1]),
(1, [1]),
(1, [1]),
(3, [ac]),
))
}))
#let asli = [Actió Sequentiae Locí Immediátí]
== #asli
#align(
center,
cetz.canvas(
{
import cetz.draw: *
scale(.65)
byte_diag((
(1, [1]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [0]),
(1, [C]),
(1, [E]),
(1, [N]),
(1, [U]),
(1, [R]),
), ((12, [imm[0:11]]),))
byte_diag(((12, [imm[12:23]]),), ((12, [imm[24:35]]),), voff: -3)
byte_diag(((12, [imm[36:47]]),), voff: -6)
},
),
)
#let aslr = [Actió Sequentiae Loci Recordandí]
== #aslr
#align(center, cetz.canvas({
cetz.draw.scale(.65)
byte_diag((
(1, [1]),
(1, [0]),
(1, [1]),
(4, [r#sub[l]]),
(1, [I]),
(1, [E]),
(1, [N]),
(1, [U]),
(1, [R]),
))
}))
#let asrd = [Actió Sequentiae Recordandó Dépositus]
== #asrd
#align(center, cetz.canvas({
cetz.draw.scale(.65)
byte_diag((
(1, [1]),
(1, [1]),
(1, [0]),
(4, [r#sub[l]]),
(1, [I]),
(1, [E]),
(1, [N]),
(1, [U]),
(1, [R]),
), ((12, [imm[0:11]]),))
}))
#pagebreak(weak: true)
= Instructions by their mnemonics
Common Notations:
/ ```ain48 %a, %b, %c, ...```: any register a, b, c
/ ```ain48 #imm[n]```: An immediate (max: n bits)
/ ```ain48 %0, %nul```: Writes to ```ain48 %0 / %nul``` are discarded
/ ```ain48 (%a)```: The memory at the address contained by %a
/ ```ain48 (#imm[n])```: The memory at the address represented by the immediate (max n bits)
/ ```ain48 a_label```: The memory address corresponding to the label
#let ristr(mnem) = [*#raw(mnem, lang: "ain48")* (#ref(label("ins:" + mnem), form: "page"))]
#let istr(mnem) = strong(raw(mnem, lang: "ain48"))
#let opt(a, b) = box[
*#a* = #b
]
#let optgrid(..opts) = block({
text(weight: 900)[Instruction parameters]
linebreak()
h(1em)
opts.pos().join(h(2em))
})
#let aludoc(mnemonic, opnum, opsym, commutative, s) = [
#optgrid(opt[op][#opnum], opt[s][#s])
/ Instruction format: #amal
/ #raw(mnemonic + " %a, %b, %c", lang: "ain48"): %a %b #opsym %c
#optgrid(
opt[i#sub[1]][0],
opt[i#sub[2]][0],
opt[r#sub[f]][a],
opt[r#sub[1]][b],
opt[r#sub[2]][c],
)
/ #raw(mnemonic + " %a, %b, #imm[4]", lang: "ain48"): %a %b #opsym imm
#optgrid(
opt[i#sub[1]][0],
opt[i#sub[2]][1],
opt[r#sub[f]][a],
opt[r#sub[1]][b],
opt[imm][imm],
)
#if not commutative [
/ #raw(mnemonic + " %a, #imm[4], %b", lang: "ain48"): %a imm #opsym %b
#optgrid(
opt[i#sub[1]][1],
opt[i#sub[2]][0],
opt[r#sub[f]][a],
opt[r#sub[2]][b],
opt[imm][imm],
)
]
/ #raw(mnemonic + " %a, #imm[8]", lang: "ain48"): %a %a #opsym imm
#optgrid(opt[i#sub[1]][1], opt[i#sub[2]][1], opt[r#sub[f]][a], opt[imm][imm])
]
#let instructions = (
(mnem: "STR", name: "Subtrahere", sign: true, description: [
Subtract one signed number from another.
#aludoc("STR", "01", "-", false, 1)
/ Instruction format: #amal
]),
(mnem: "ADD", name: "Addere", sign: true, description: [
Add two signed numbers together.
#aludoc("ADD", "00", "+", true, 1)
/ Instruction format: #amal
]),
(mnem: "MPL", name: "Multiplicáre", sign: true, description: [
Multiply two signed numbers together.
#aludoc("MPL", "02", "×", true, 1)
/ Instruction format: #amal
]),
(mnem: "RSD", name: "Residérí", sign: true, description: [
Remainder of the Euclidean division of two signed integers.
Will raise an arithmetic exception if the right operand is zero.
#aludoc("RSD", "05", "mod", false, 1)
/ Instruction format: #amal
]),
(mnem: "DVD", name: "Dívidere", sign: true, description: [
Integer part of the Euclidean division of two signed integers.
Will raise an arithmetic exception if the right operand is zero.
#aludoc("DVD", "04", "/", false, 1)
/ Instruction format: #amal
]),
(mnem: "DEM", name: "Ad Dextró Movére", sign: true, description: [
Shift a signed number right by a signed number.
Will raise an arithmetic exception if the right operand is negative.
#aludoc("DEM", "06", ">>", false, 1)
/ Instruction format: #amal
]),
(mnem: "SIM", name: "Ad Sinistró Movére", description: [
Shift a word to the left.
Will raise an arithmetic exception if the right operand is negative.
#aludoc("SIM", "07", "<<", false, 0)
/ Instruction format: #amal
]),
(
mnem: "ILG",
name: "Immediátum Legere",
sign: true,
description: [
Load a signed immediate value into a register.
/ ```ain48 ILG %a, #imm[12]```: %a imm
/ Instruction format: #ipl
#optgrid(opt[s][1], opt[r][a], opt[imm][imm])
/ ```ain48 ILG %a, #imm[48]```: %a imm
/ Instruction format: #iml
#optgrid(opt[s][1], opt[r][a], opt[imm][imm])
The assembler has freedom to choose the appropriate format given the size of the
immediate. The mnemonics #ristr("IMLG") and #ristr("IPLG") also exist to allow
the programmer to explicitly request one format or the other: #istr("IMLG") uses #iml and
#istr("IPLG") uses #ipl.
],
also: (
("IPLG", "Immediátum Parvum Legere"),
("IMLG", "Immediátum Magnum Legere"),
),
),
(
mnem: "I",
name: "Íre",
description: [
Go to (jump to) an instruction other than the one immediately following this instruction.
/ ```ain48 I a_label```: %ia a_label
/ Instruction format: #asli
#optgrid(
opt[imm][a_label],
)
Some assemblers may opt to assemble calls to nearby labels as if they were of
the form `IRE %0, #imm[12]` with no loss of correctness.
/ ```ain48 I #imm[48]```: %ia imm\
/ Instruction Format: #asli
#optgrid(
opt[imm][a_label],
)
/ ```ain48 I %a```: %ia %a\
/ Instruction Format: #aslr
#optgrid(
opt[r#sub[a]][a],
)
/ ```ain48 I %nul, #imm[12]```: %ia %ia + %a
/ Instruction Format: #asrd
#optgrid(
opt[r#sub[a]][0],
opt[imm][imm],
)
This is a special case of the format just below, where because offsetting from
%nul is useless, we instead interpret it as an offset from %ia.
/ ```ain48 I %a, #imm[12]```: %ia %a + imm
/ Instruction Format: #asrd
#optgrid(
opt[r#sub[a]][a],
opt[imm][imm],
)
],
also: (
(
"UI",
"Si nullus est íre",
[
Used like #istr("I"), but only jump if the zero bit of the status flag is set.\
#optgrid(opt[I][0], opt[U][1])
],
),
(
"NUI",
"Si nón nullus est íre",
[
Used like #istr("I"), but only jump if the zero bit of the status flag is not set.\
#optgrid(opt[I][1], opt[U][1])
],
),
(
"NI",
"Si negatívus est íre",
[
Used like #istr("I"), but only jump if the negative bit of the status flag is set.\
#optgrid(opt[I][0], opt[N][1])
],
),
(
"NNI",
"Si nón negatívus est íre",
[
Used like #istr("I"), but only jump if the negative bit of the status flag is not set.\
#optgrid(opt[I][1], opt[N][1])
],
),
(
"EI",
"Si excedit íre",
[
Used like #istr("I"), but only jump if the carry bit of the status flag is set.\
#optgrid(opt[I][0], opt[E][1])
],
),
(
"NEI",
"Si nón excedit íre",
[
Used like #istr("I"), but only jump if the carry bit of the status flag is not set.\
#optgrid(opt[I][1], opt[C][1])
],
),
(
"PI",
"Si positívus est íre",
[
Used like #istr("I"), but only jump if neither the zero nor negative flag of the status
flag is set.
#optgrid(opt[I][1], opt[N][1], opt[U][1])
],
),
(
"NPI",
"Si nón positívus est íre",
[
Used like #istr("I"), but only jump if either the zero or negative flag of the status
flag is set.
#optgrid(opt[I][0], opt[N][1], opt[U][1])
],
),
("IVC", "Invocáre", [
Used like #istr("I"), but sets up a function environment prior to jumping:\
(%it) %ia; %it %it - 4
#optgrid(opt[R][1])
],),
),
),
(
mnem: "NCIVC",
name: "In Nucleó Invocáre",
description: [
Invoke a kernel space function, see your operating system's ABI for more
details.
/ Instruction format: #asss
#optgrid(opt[ac][3])
/ ```ain48 NCIVC```: Perform a kernel call.
],
),
(mnem: "IRRV", name: "De Interruptó Reveníre", description: [
*Privileged*
Instruction used to exit from the interrupt handler.
/ Instruction format: #asss
#optgrid(opt[ac][4])
/ ```ain48 IRRV```: Return from an interrupt.
]),
(mnem: "NCRV", name: "De Nucleó Reveníre", description: [
*Privileged*
Return to user space after a system call.
/ Instruction Format: #asss
#optgrid(opt[ac][2])
/ ```ain48 IRRV```: Return from kernelspace.
]),
(mnem: "VEL", name: "Vel", description: [
Bitwise disjunction between two words.
#aludoc("VEL", [11], sym.or, true, 0)
]),
(mnem: "NEC", name: "Nec", description: [
Bitwise negated disjunction between two words
#aludoc("NEC", [15], sym.not + sym.or, true, 0)
]),
(mnem: "AUT", name: "Aut", description: [
Bitwise difference between two numbers.
#aludoc("AUT", [12], sym.xor, true, 0)
]),
(mnem: "AEQ", name: "Aequí", description: [
Bitwise equality between two numbers.
#aludoc("AEQ", [16], [=], true, 0)
]),
(mnem: "ET", name: "Et", description: [
Bitwise logical conjunction between two numbers.
#aludoc("ET", [10], sym.and, true, 0)
]),
(mnem: "NAM", name: "Non ambó", description: [
Bitwise negated logical conjunction between two numbers.
#aludoc("NAM", [14], sym.not + sym.and, true, 0)
]),
(
mnem: "LG",
name: "Legere",
description: [
Read a word from memory.
/ Instruction Format: #amls
/ ```ain48 LG %a, (%b), #imm[12]```: %a (%b + imm)
#optgrid(opt[s][0], opt[r#sub[d]][a], opt[r#sub[a]][b], opt[imm][imm])
/ ```ain48 LG %a, (%b)```: This is a special case of ```ain48 LG %a, (%b), #imm[12]``` where imm = 0.
],
),
(
mnem: "SC",
name: "Scríbere",
description: [
Write a word to memory.
/ Instruction Format: #amls
/ ```ain48 SC %a, (%b), #imm[12]```: (%b + imm) %a
#optgrid(opt[s][1], opt[r#sub[d]][a], opt[r#sub[a]][b], opt[imm][imm])
/ ```ain48 SC %a, (%b)```: This is a special case of ```ain48 SC %a, (%b), #imm[12]``` where imm = 0
],
),
(mnem: "CESS", name: "Cessáre", description: [
*Privileged* Halts the CPU.
/ Instruction Format: #asss
#optgrid(opt[ac][7])
/ ```ain48 CESS```: Pause execution.
]),
(mnem: "RV", name: "Reveníre", description: [
Returns from a subroutine.
/ Instruction Format: #asss
#optgrid(opt[ac][0])
/ ```ain48 RV```: %it %it - 4; %ia (%it)
]),
(mnem: "CST", name: "Construere", description: [
Push the contents of a register onto the stack.
/ ```ain48 CST %a```: This instruction is a macro implemented as follows:\
#raw(block: true, "SC %a, (%it)\nPSTR %it, #4", lang: "ain48")
]),
(mnem: "DST", name: "Destruere", description: [
Pop the contents of the top of the stack into a register.
/ ```ain48 DST %a```: This instruction is a macro implemented as follows:\
#raw(block: true, "PADD %it, #4\nLG %a, (%it)", lang: "ain48")
]),
(mnem: "TSC", name: "Transcríbere", description: [
Copy between registers.
/ ```ain48 TSC %a, %b```: %a %b.
This instruction is a macro implemented as ```ain48 VEL %a, %b, %nul```.
]),
(mnem: "AN", name: "Actió Nulla", description: [
Do nothing except increment the instruction counter.
/ ```ain48 AN```: Do nothing.
This instruction is a macro implemented as ```ain48 IPLG %0, #0```.
]),
)
#instructions.map(
it => {
let arr = ((mnem: it.mnem, desc: block(breakable: false, width: 100%)[
== #it.mnem | #it.name
#label("ins:" + it.mnem)
#it.description
#if it.keys().contains("sign") [See also #ristr("P" + it.mnem)]
]),)
if it.keys().contains("sign") {
arr.push((mnem: "P" + it.mnem, desc: [
== P#(it.mnem) | Positívós #it.name
#label("ins:P" + it.mnem)
Same form(s) as #istr(it.mnem) but with s = 0\
Unsigned version of #ristr(it.mnem).
]))
}
if it.keys().contains("also") {
for (m, n, ..r) in it.also {
arr.push((mnem: m, desc: [
== #m | #n
#label("ins:" + m)
#r.join()
See #ristr(it.mnem)
]))
if it.keys().contains("sign") {
arr.push(
(
mnem: "P" + m,
desc: [
== P#m | Positivós #n
Unsigned version of #m; see #ristr("P" + it.mnem) and #ristr(it.mnem).
],
),
)
}
}
}
arr
},
).join().sorted(key: it => it.mnem).map(it => it.desc).join()
#pagebreak(weak: true)
#outline(depth: 2)