# `triscv`, a trinary riscv-like ISA simulator

An attempt at creating a trinary computer simulator capable of running simple
operating systems, with virtual memory and user/system separation.

Natural bases of three used as trit groupings, i.e.
3^0 = trit
3^1 = nit (likely not actually used?)
3^2 = tryte (base size of characters etc, similar to byte)
3^3 = tri (base word size)

Ideally I would like to extend support up to 3^4 (tril?) that would be
equivalent to 64 bit groupings on conventional hardware, but the my trinary
arithmetic library underlying the simulator doesn't support 81 trits due to
efficiency/portability concerns. 128 bit integers could be usable on at least
x86, but not really portable. Something like `gmp` could also be used, but would
be very slow. Alternatively, consider 45 trit as an equivalence to 64 bit
computers, as they are approximately the same in value and divisible by three.

Heavy inspiration taken from riscv.

M-mode/S-mode/U-mode fits pretty nicely into trinary, heh.

# Operation set

27 trit ops, registers 3^4?

```
 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
|-----------------------------------------------------|
| fn5     | rs2   | rs1   | fn0     | rd    | opcode  | R-type
|-----------------------------------------------------|
| imm9            | rs1   | fn0     | rd    | opcode  | I-type
|-----------------------------------------------------|
| imm5    | rs2   | rs1   | fn0     | imm4  | opcode  | S-type
|-----------------------------------------------------|
| imm18                             | rd    | opcode  | U-type
|-----------------------------------------------------|
| imm5    | rs2   | rs1   | immd    | rd    | opcode  | D-type
|-----------------------------------------------------|
```

(D is a variant of R, where immd is actually fn0, but used as an immediate)

Compressed instructions probably possible to implement in future but left alone
for now.

Exact instructions still TBD, but R type probably reserves one fn trit value for
diops. It's unclear to me how readily diops can be implemented in hardware, so
consider this more exploratory than anythin else.

Similarly, I-type reserved on fn trit value to unops.

Turns out it's literally just a mapping, i.e. unops use a three-way mux with
the input value as the selection trit and the three control bits inputs to the
mux. Similarly, diops are just a nine-way mux.

# Registers

81, of which x0 is constant 0. ABI TBD but probably similar to riscv.

# Virtual memory

Physical memory consists of PPN[2], PPN[1], PPN[0] and page offset. 27 trits is enough
for about 7 teratrytes of memory, which probably doesn't need range extensions
like 32bit computers tend to use.

Page table entries:

```
 2 2 2 2 2 2 2 1 1 1 1 1 1 1 1 1 1 0 0 0 0 0 0 0 0 0 0
 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0 9 8 7 6 5 4 3 2 1 0
|-----------------------------------------------------|
| PPN[2]     | PPN[1]    | PPN[0]     |RSW|G|U|X|A|R|V|
|------------------------------------------------D-W--|
| TAG                                                 |
|-----------------------------------------------------|
| Reserved                                            |
|-----------------------------------------------------|
```

TAG is a tag that allows us to separate one address space into multiple regions,
similar to CODOMS system but scaled down a lot. If left 0, code executing in the
region is allowed to access other regions but outside regions are not allowed to
access the zero region, except executing a migration jump.

The reserved region might come
in handy for 'extensions' in the future, but can probably be set to zero for
now.

R/W and A/D work by the following table:

```
N => both (R and W, A and D)
O => none
P => top (R, A)
```

RSW is reserved for S-mode usage.

The idea here is a page marked write must also be marked read, and a dirty page
must also have been accessed. Whether an interrupt is raised when A/D is updated
is set by a status CSR, to be determined later. Three-level page tables.

Each base page is 3^8 = 6561, and since each PTE
takes up 9 trytes, each base page can fit 3^6 PTEs. Therefore, to cover the full
27 bit address space, we need three `satp` CSRs, `satpn`, `satpo`, `satpp`,
corresponding to the first trit in the address. This can give reasonably
efficient separation of user/kernelspace, for example -1 for kernel and 1 for
user. Still about 2.5 tera per space.

This can be extended to 81 bits, although with no nice whole number that maps to
81. Not a huge deal, by any means, although kind of annoying that the number of
elements per PTE shrinks to 243. Maybe not a huge issue, but might limit
useability somewhat as the number of page levels has to be increased much more
rapidly to get larger virtual address spaces.

Requiring physical addresses to be the same width as virtual addresses
simplifies hypervisor stuff somewhat, could be useful? Not that allocating some
pages in series is too big of an issue, but still feels like an unnecessary
complication in the RISC-V specs. Apparently the explanation given is that is
can be useful to emulate larger physical address spaces than virtual spaces,
though I'd like to see some examples. I guess easier masking of some addresses?
Dunno.

I might want to add in a bus error to separate it from pmp errors, there's been
some discussion about it in riscv mailing lists.

# CSRs

TDB

# Extensions

## I

addi, slti
```
|-----------------------------------------------------|
| imm0            | rs1   | fn0     | rd    | OP-IMM  | I-type
|-----------------------------------------------------|
```

unop (some macro instructions might be available in assembler)
```
|-----------------------------------------------------|
| 0         |N|O|P| rs1   | fn0     | rd    | OP-IMM  | I-type
|-----------------------------------------------------|
```

slli, srli (shifts work differently in balanced forms, have to be careful)
	(shmt is 'unsigned', i.e. the actual shift amount is shmt + 13)
```
|-----------------------------------------------------|
| imm0      |shmt | rs1   | fn0     | rd    | OP-IMM  | I-type
|-----------------------------------------------------|
```

lui
```
|-----------------------------------------------------|
| imm18                             | rd    | LUI     | U-type
|-----------------------------------------------------|
```

auipc
```
|-----------------------------------------------------|
| imm18                             | rd    | AUIPC   | U-type
|-----------------------------------------------------|
```

add, slt, sge, seq, sne, sll, srl, sub
```
|-----------------------------------------------------|
| 0       | rs2   | rs1   | fn0     | rd    | OP      | R-type
|-----------------------------------------------------|
```

diop
```
|-----------------------------------------------------|
|N|O|P|N|O| rs2   | rs1   |P|N|O|P|0| rd    | DIOP    | D-type
|-----------------------------------------------------|
```

nop
```
|-----------------------------------------------------|
| 0               | x0    | 0       | x0    | OP-IMM  | I-type
|-----------------------------------------------------|
```

jal
```
|-----------------------------------------------------|
| imm9                              | rd    | JAL     | U-type
|-----------------------------------------------------|
```

jalr
```
|-----------------------------------------------------|
| imm0            | rs1   | 0       | rd    | JALR    | I-type
|-----------------------------------------------------|
```

beq, bne, blt, bge
```
|-----------------------------------------------------|
| imm0    | rs2   | rs1   | fn0     | imm4  | BRANCH  | S-type
|-----------------------------------------------------|
```

load (w = width, i = 1 tryte, 0 = 3 trytes, 1 = 9 trytes)
```
|-----------------------------------------------------|
| imm0            | rs1   |w| 0     | rd    | LOAD    | I-type
|-----------------------------------------------------|
```

store
```
|-----------------------------------------------------|
| imm5    | rs2   | rs1   |w| 0     | imm4  | STORE   | S-type
|-----------------------------------------------------|
```

fence (todo)
```
|-----------------------------------------------------|
| imm0            | rs1   | fn0     | rd    | MEM     | I-type
|-----------------------------------------------------|
```

ecall, ebreak, pcall
```
|-----------------------------------------------------|
| imm0            | x0    | fn0     | x0    | SYSTEM  | I-type
|-----------------------------------------------------|
```

hints, any OP-IMM or OP with rd = x0. Additionally,
a number of maybe-instructions in SYSTEM for future use, if unimplemented
just write 0 to rd.

## M

mul
```
|-----------------------------------------------------|
| fn5     | rs2   | rs1   | fn0     | rd    | OP      | R-type
|-----------------------------------------------------|
```

div
```
|-----------------------------------------------------|
| fn5     | rs2   | rs1   | fn0     | rd    | OP      | R-type
|-----------------------------------------------------|
```

rem
```
|-----------------------------------------------------|
| fn5     | rs2   | rs1   | fn0     | rd    | OP      | R-type
|-----------------------------------------------------|
```

## A

stt (start transaction)
cst (checked store)
ent (end transaction)
cat (cancel transaction)

Each system has a buffer with at least three slots for checked transactions.
Each checked transaction is written to the buffer when an stc is encountered.
stc clears out this buffer and ent checks if all stores are valid and flushes
the buffer to real memory, or cache or wherever. A system is allowed to give
each core a buffer or use a global buffer, but the size should be given in a CSR
somewhere. Platforms may require at least n slots. Maybe? Reads inside
transaction read from memory. Slow commits, at least somewhat, but probably
somewhat simpler. Spinlock (and by extension, mutex) would then be

```
spinlock:
	// spin until a zero appears
	lw x1, (x2)
	bnez x1, spinlock

	// start transaction, try to write 1
	stt
	li x1, 1
	stc x1, (x2)
	ent x1
	// someone else tried at the same time, wait for next zero
	// preferably also do a spin for a random but increasing time to try to
	// avoid livelock, or rely on whatever system we're dealing with to
	// provide it for us
	beqz x1, spinlock
```

could probably also provide simple atomics, like add or something, but eh

## Zicsr

csrrw, csrrs, csrrc
```
|-----------------------------------------------------|
| imm0            | rs    | fn0     | rd    | SYSTEM  | I-type
|-----------------------------------------------------|
``

The CSR numbers are specified in the future.

Also, possibly floating point stuff in the future, though I haven't chosen a
floating point format yet.
