progress: new assembler works, instruction set fully implemented with call/ret and push/pop, bf.dsa works which means the dsa assembly language works reliably. still a few bugs to fix. might be able to squeeze out a tiny bit more performance

This commit is contained in:
2026-03-09 03:24:20 +00:00
parent fc972b9b7b
commit e01a9f2808
63 changed files with 4975 additions and 1579 deletions
+64
View File
@@ -0,0 +1,64 @@
use crate::{instructions::Instruction, register::Register};
impl Instruction {
#[inline]
pub fn opcode(self) -> u8 {
((self.0 >> 26) & 0x3F) as u8
}
// Src shared between R type and I type
#[inline]
pub unsafe fn src1_uc(self) -> Register {
unsafe { Register::from_u8_unchecked(((self.0 >> 21) & 0x1F) as u8) }
}
#[inline]
pub fn src1_checked(self) -> Register {
Register::from_u8(((self.0 >> 21) & 0x1F) as u8).unwrap_or(Register::Null)
}
// Dest shared between R type and I type
#[inline]
pub unsafe fn dest_uc(self) -> Register {
unsafe { Register::from_u8_unchecked(((self.0 >> 16) & 0x1F) as u8) }
}
#[inline]
pub fn dest_checked(self) -> Register {
Register::from_u8(((self.0 >> 16) & 0x1F) as u8).unwrap_or(Register::Null)
}
// Secondary Src for R type only
#[inline]
pub unsafe fn src2_uc(self) -> Register {
unsafe { Register::from_u8_unchecked(((self.0 >> 11) & 0x1F) as u8) }
}
#[inline]
pub fn src2_checked(self) -> Register {
Register::from_u8(((self.0 >> 11) & 0x1F) as u8).unwrap_or(Register::Null)
}
// Misc/Conditional reg for R type only
#[inline]
pub unsafe fn misc_uc(self) -> Register {
unsafe { Register::from_u8_unchecked(((self.0 >> 6) & 0x1F) as u8) }
}
#[inline]
pub fn misc_checked(self) -> Register {
Register::from_u8(((self.0 >> 6) & 0x1F) as u8).unwrap_or(Register::Null)
}
// Shift amount / misc data for R type only
#[inline]
pub fn shamt(self) -> u8 {
(self.0 & 0x3F) as u8
}
// 16 Bit immediate for I type only
#[inline]
pub fn imm16(self) -> u16 {
self.0 as u16
}
}
+334
View File
@@ -0,0 +1,334 @@
use std::ops::{Deref, DerefMut};
use crate::{
instructions::{Instruction, Opcode},
register::Register,
};
impl From<u32> for Instruction {
#[inline]
fn from(word: u32) -> Self {
Self(word)
}
}
impl Deref for Instruction {
type Target = u32;
#[inline]
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl DerefMut for Instruction {
#[inline]
fn deref_mut(&mut self) -> &mut Self::Target {
&mut self.0
}
}
impl Instruction {
// ---- R Type Builders ----
#[inline]
fn build_r(
opcode: Opcode,
src1: Register,
dest: Register,
src2: Register,
misc: Register,
shamt: u8,
) -> Self {
Self(
((opcode as u32) << 26)
| ((src1 as u32) << 21)
| ((dest as u32) << 16)
| ((src2 as u32) << 11)
| ((misc as u32) << 6)
| ((shamt as u32) & 0x3F),
)
}
#[inline]
fn build_i(opcode: Opcode, src: Register, dest: Register, imm: u16) -> Self {
Self(((opcode as u32) << 26) | ((src as u32) << 21) | ((dest as u32) << 16) | (imm as u32))
}
#[inline]
fn build_noarg(opcode: Opcode) -> Self {
Self((opcode as u32) << 26)
}
/// No operation.
pub fn nop() -> Self {
Self::build_noarg(Opcode::Nop)
}
// ---- Move ----
/// Move a value from `src` to `dest`.
pub fn mov(src: Register, dest: Register) -> Self {
Self::build_r(Opcode::Mov, src, dest, Register::Zero, Register::Zero, 0)
}
/// Conditional move: if `cmp` is true then move `src` to `dest`.
pub fn cmov(src: Register, dest: Register, cmp: Register) -> Self {
Self::build_r(Opcode::CMov, src, dest, cmp, Register::Zero, 0)
}
// ---- Load ----
/// Load byte from memory at `src + offset` into `dest`.
pub fn ldb(src: Register, dest: Register, offset: u16) -> Self {
Self::build_i(Opcode::Ldb, src, dest, offset)
}
/// Load signed byte (signextended) from memory at `src + offset` into `dest`.
pub fn ldbs(src: Register, dest: Register, offset: u16) -> Self {
Self::build_i(Opcode::Ldbs, src, dest, offset)
}
/// Load halfword from memory at `src + offset` into `dest`.
pub fn ldh(src: Register, dest: Register, offset: u16) -> Self {
Self::build_i(Opcode::Ldh, src, dest, offset)
}
/// Load signed halfword (signextended) from memory at `src + offset` into `dest`.
pub fn ldhs(src: Register, dest: Register, offset: u16) -> Self {
Self::build_i(Opcode::Ldhs, src, dest, offset)
}
/// Load word from memory at `src + offset` into `dest`.
pub fn ldw(src: Register, dest: Register, offset: u16) -> Self {
Self::build_i(Opcode::Ldw, src, dest, offset)
}
// ---- Store ----
/// Store byte from `src` to memory at `dest + offset`.
pub fn stb(src: Register, dest: Register, offset: u16) -> Self {
Self::build_i(Opcode::Stb, src, dest, offset)
}
/// Store halfword from `src` to memory at `dest + offset`.
pub fn sth(src: Register, dest: Register, offset: u16) -> Self {
Self::build_i(Opcode::Sth, src, dest, offset)
}
/// Store word from `src` to memory at `dest + offset`.
pub fn stw(src: Register, dest: Register, offset: u16) -> Self {
Self::build_i(Opcode::Stw, src, dest, offset)
}
// ---- Load Immediate ----
/// Load lower 16 bits of an immediate into `dest`.
pub fn lli(dest: Register, imm: u16) -> Self {
Self::build_i(Opcode::Lli, Register::Zero, dest, imm)
}
/// Load upper 16 bits of an immediate into `dest`.
pub fn lui(dest: Register, imm: u16) -> Self {
Self::build_i(Opcode::Lui, Register::Zero, dest, imm)
}
/// Load a full 32bit immediate using an `lli` + `lui` pair.
pub fn load_imm32(dest: Register, imm: u32) -> [Self; 2] {
[
Self::lli(dest, imm as u16),
Self::lui(dest, (imm >> 16) as u16),
]
}
// ---- Comparison ----
/// Set `dest` to 1 if `sr1 == sr2`, else 0.
pub fn ieq(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Ieq, sr1, dest, sr2, Register::Zero, 0)
}
/// Set `dest` to 1 if `sr1 != sr2`, else 0.
pub fn ine(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Ine, sr1, dest, sr2, Register::Zero, 0)
}
/// Set `dest` to 1 if `sr1 < sr2`, else 0.
pub fn ilt(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Ilt, sr1, dest, sr2, Register::Zero, 0)
}
/// Set `dest` to 1 if `sr1 <= sr2`, else 0.
pub fn ile(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Ile, sr1, dest, sr2, Register::Zero, 0)
}
/// Set `dest` to 1 if `sr1 > sr2`, else 0.
pub fn igt(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Igt, sr1, dest, sr2, Register::Zero, 0)
}
/// Set `dest` to 1 if `sr1 >= sr2`, else 0.
pub fn ige(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Ige, sr1, dest, sr2, Register::Zero, 0)
}
// ---- Jump ----
/// Unconditional jump to address in `addr` with offset.
pub fn jmp(addr: Register, offset: u16) -> Self {
Self::build_i(Opcode::Jmp, Register::Zero, addr, offset)
}
/// Jump if zero flag set (`jez`).
pub fn jez(cmp: Register, addr: Register, offset: u16) -> Self {
Self::build_i(Opcode::Jez, cmp, addr, offset)
}
/// Jump if zero flag clear (`jnz`).
pub fn jnz(cmp: Register, addr: Register, offset: u16) -> Self {
Self::build_i(Opcode::Jnz, cmp, addr, offset)
}
/// Jump if carry set (`jic`).
pub fn jic(addr: Register, offset: u16) -> Self {
Self::build_i(Opcode::Jic, Register::Zero, addr, offset)
}
/// Jump if carry clear (`jnc`).
pub fn jnc(addr: Register, offset: u16) -> Self {
Self::build_i(Opcode::Jnc, Register::Zero, addr, offset)
}
// ---- Bitwise ----
/// Bitwise AND of `sr1` and `sr2`, result in `dest`.
pub fn and(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::And, sr1, dest, sr2, Register::Zero, 0)
}
/// Bitwise NAND of `sr1` and `sr2`, result in `dest`.
pub fn nand(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Nand, sr1, dest, sr2, Register::Zero, 0)
}
/// Bitwise OR of `sr1` and `sr2`, result in `dest`.
pub fn or(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Or, sr1, dest, sr2, Register::Zero, 0)
}
/// Bitwise NOR of `sr1` and `sr2`, result in `dest`.
pub fn nor(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Nor, sr1, dest, sr2, Register::Zero, 0)
}
/// Bitwise XOR of `sr1` and `sr2`, result in `dest`.
pub fn xor(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Xor, sr1, dest, sr2, Register::Zero, 0)
}
/// Bitwise XNOR of `sr1` and `sr2`, result in `dest`.
pub fn xnor(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Xnor, sr1, dest, sr2, Register::Zero, 0)
}
/// Bitwise NOT of `src`, result in `dest`.
pub fn not(src: Register, dest: Register) -> Self {
Self::build_r(Opcode::Not, src, dest, Register::Zero, Register::Zero, 0)
}
// ---- Arithmetic ----
/// Add `sr1` and `sr2`, store result in `dest`.
pub fn add(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Add, sr1, dest, sr2, Register::Zero, 0)
}
/// Subtract `sr2` from `sr1`, store result in `dest`.
pub fn sub(sr1: Register, sr2: Register, dest: Register) -> Self {
Self::build_r(Opcode::Sub, sr1, dest, sr2, Register::Zero, 0)
}
/// Shift left logical: shift `src` by (`rshamt` + `ishamt`) bits into `dest`.
pub fn shl(src: Register, rshamt: Register, dest: Register, ishamt: u8) -> Self {
debug_assert!(ishamt < 64);
Self::build_r(Opcode::Shl, src, dest, rshamt, Register::Zero, ishamt)
}
/// Shift right logical: shift `src` by (`rshamt` + `ishamt`) bits into `dest`.
pub fn shr(src: Register, rshamt: Register, dest: Register, ishamt: u8) -> Self {
debug_assert!(ishamt < 64);
Self::build_r(Opcode::Shr, src, dest, rshamt, Register::Zero, ishamt)
}
/// Add immediate `imm` to `sr1`, store result in `dest`.
pub fn addi(sr1: Register, dest: Register, imm: u16) -> Self {
Self::build_i(Opcode::Addi, sr1, dest, imm)
}
/// Subtract immediate `imm` from `sr1`, store result in `dest`.
pub fn subi(sr1: Register, dest: Register, imm: u16) -> Self {
Self::build_i(Opcode::Subi, sr1, dest, imm)
}
// ---- Util -----
pub fn call(addr: Register, offset: u16) -> Self {
Self::build_i(Opcode::Call, Register::Zero, addr, offset)
}
pub fn ret() -> Self {
Self::build_noarg(Opcode::Ret)
}
pub fn push(reg: Register) -> Self {
Self::build_r(
Opcode::Push,
reg,
Register::Zero,
Register::Zero,
Register::Zero,
0,
)
}
pub fn pop(reg: Register) -> Self {
Self::build_r(
Opcode::Pop,
Register::Zero,
reg,
Register::Zero,
Register::Zero,
0,
)
}
// ---- System ----
/// Trigger an interrupt with the given 6bit code.
pub fn int(code: u8) -> Self {
debug_assert!(code < 64);
Self::build_i(Opcode::Int, Register::Zero, Register::Zero, code as u16)
}
/// Return from interrupt.
pub fn irt() -> Self {
Self::build_noarg(Opcode::IRet)
}
/// Halt execution.
pub fn hlt() -> Self {
Self::build_r(
Opcode::Hlt,
Register::Zero,
Register::Zero,
Register::Zero,
Register::Zero,
0,
)
}
/// Raw data (not an instruction)
pub fn data(value: u32) -> Self {
Self(value)
}
}
+235
View File
@@ -0,0 +1,235 @@
pub mod decode;
pub mod encode;
#[derive(Clone, Copy)]
#[repr(transparent)]
pub struct Instruction(pub u32);
#[repr(u8)]
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Opcode {
Nop = 0,
// move
Mov = 1,
CMov = 2,
// load
Ldb = 3,
Ldbs = 4,
Ldh = 5,
Ldhs = 6,
Ldw = 7,
// store
Stb = 8,
Sth = 9,
Stw = 10,
// load immediate
Lli = 11,
Lui = 12,
// comparison
Ieq = 13,
Ine = 14,
Ilt = 15,
Ile = 16,
Igt = 17,
Ige = 18,
// jump
Jmp = 19,
Jez = 20,
Jnz = 21,
Jic = 22,
Jnc = 23,
// bitwise
And = 24,
Nand = 25,
Or = 26,
Nor = 27,
Xor = 28,
Xnor = 29,
Not = 30,
// arithmetic
Add = 31,
Sub = 32,
Shl = 33,
Shr = 34,
Addi = 35,
Subi = 36,
// utility
Push = 37,
Pop = 38,
Call = 39,
Ret = 40,
// system
Int = 41,
IRet = 42,
Hlt = 43,
}
impl Opcode {
#[inline]
pub fn from_u8(val: u8) -> Option<Self> {
if val <= Self::Hlt as u8 {
Some(unsafe { std::mem::transmute(val) })
} else {
None
}
}
}
use std::fmt;
impl fmt::Debug for Instruction {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let opcode = Opcode::from_u8(self.opcode());
match opcode {
None => write!(f, "Instruction(INVALID OPCODE {:#04x})", self.opcode()),
Some(op @ (Opcode::Nop | Opcode::Hlt | Opcode::Ret | Opcode::IRet)) => {
write!(f, "{op:?}")
}
Some(op @ Opcode::Push) => {
write!(f, "{op:?} {src1:?}", op = op, src1 = self.src1_checked())
}
Some(op @ Opcode::Pop) => {
write!(f, "{op:?} {dest:?}", op = op, dest = self.dest_checked())
}
// I-type: single reg + immediate
Some(op @ (Opcode::Lli | Opcode::Lui)) => {
write!(
f,
"{op:?} {dest:?}, {imm:#06x}",
op = op,
dest = self.dest_checked(),
imm = self.imm16()
)
}
// I-type: no reg, immediate only
Some(op @ Opcode::Int) => {
write!(f, "{op:?} {imm:#06x}", op = op, imm = self.imm16())
}
// I-type: src + dest + immediate
Some(
op @ (Opcode::Ldb
| Opcode::Ldbs
| Opcode::Ldh
| Opcode::Ldhs
| Opcode::Ldw
| Opcode::Stb
| Opcode::Sth
| Opcode::Stw
| Opcode::Addi
| Opcode::Subi),
) => {
write!(
f,
"{op:?} {src:?}, {dest:?}, {imm:#06x}",
op = op,
src = self.src1_checked(),
dest = self.dest_checked(),
imm = self.imm16()
)
}
// I-type: cmp + addr + immediate (conditional jumps)
Some(op @ (Opcode::Jez | Opcode::Jnz)) => {
write!(
f,
"{op:?} {cmp:?}, {addr:?}, {imm:#06x}",
op = op,
cmp = self.src1_checked(),
addr = self.dest_checked(),
imm = self.imm16()
)
}
// I-type: addr + immediate only (unconditional/carry jumps)
Some(op @ (Opcode::Jmp | Opcode::Call | Opcode::Jic | Opcode::Jnc)) => {
write!(
f,
"{op:?} {addr:?}, {imm:#06x}",
op = op,
addr = self.dest_checked(),
imm = self.imm16()
)
}
// R-type: src + dest (two reg)
Some(op @ (Opcode::Mov | Opcode::Not)) => {
write!(
f,
"{op:?} {src:?}, {dest:?}",
op = op,
src = self.src1_checked(),
dest = self.dest_checked()
)
}
// R-type: src + dest + cmp (cmov)
Some(op @ Opcode::CMov) => {
write!(
f,
"{op:?} {src:?}, {dest:?}, {cmp:?}",
op = op,
src = self.src1_checked(),
dest = self.dest_checked(),
cmp = self.src2_checked()
)
}
// R-type: sr1 + sr2 + dest (arithmetic/bitwise/comparison)
Some(
op @ (Opcode::Add
| Opcode::Sub
| Opcode::And
| Opcode::Nand
| Opcode::Or
| Opcode::Nor
| Opcode::Xor
| Opcode::Xnor
| Opcode::Ieq
| Opcode::Ine
| Opcode::Ilt
| Opcode::Ile
| Opcode::Igt
| Opcode::Ige),
) => {
write!(
f,
"{op:?} {sr1:?}, {sr2:?}, {dest:?}",
op = op,
sr1 = self.src1_checked(),
sr2 = self.src2_checked(),
dest = self.dest_checked()
)
}
// R-type: src + rshamt + dest + ishamt
Some(op @ (Opcode::Shl | Opcode::Shr)) => {
write!(
f,
"{op:?} {src:?}, {dest:?}, r:{rshamt:?} + i:{ishamt}",
op = op,
src = self.src1_checked(),
dest = self.dest_checked(),
rshamt = self.src2_checked(),
ishamt = self.shamt()
)
}
}
}
}
+7
View File
@@ -0,0 +1,7 @@
pub mod instructions;
pub mod register;
pub mod prelude {
pub use crate::instructions::Instruction;
pub use crate::register::Register;
}
+151
View File
@@ -0,0 +1,151 @@
use std::fmt;
#[derive(Copy, Clone, Debug, PartialEq, Eq, Default)]
#[repr(u8)]
#[non_exhaustive]
pub enum Register {
// general purpose
Rg0 = 0,
Rg1 = 1,
Rg2 = 2,
Rg3 = 3,
Rg4 = 4,
Rg5 = 5,
Rg6 = 6,
Rg7 = 7,
Rg8 = 8,
Rg9 = 9,
Rga = 10,
Rgb = 11,
Rgc = 12,
Rgd = 13,
Rge = 14,
Rgf = 15,
// special purpose
Zero = 16,
Acc = 17,
Spr = 18,
Bpr = 19,
Ret = 20,
Idr = 21,
Mmr = 22,
// system - read only
Mar = 23,
Mdr = 24,
Sts = 25,
Cir = 26,
Pcx = 27,
#[default]
Null = 28,
}
impl Register {
#[must_use]
#[inline]
pub unsafe fn from_u8_unchecked(idx: u8) -> Self {
debug_assert!(idx <= Self::Null as u8);
unsafe { std::mem::transmute(idx) }
}
#[inline]
pub fn from_u8(idx: u8) -> Result<Self, ()> {
if idx > 28 {
return Err(());
}
Ok(unsafe { Self::from_u8_unchecked(idx) })
}
}
impl fmt::Display for Register {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"{}",
match self {
// general purpose
Self::Rg0 => "Rg0",
Self::Rg1 => "Rg1",
Self::Rg2 => "Rg2",
Self::Rg3 => "Rg3",
Self::Rg4 => "Rg4",
Self::Rg5 => "Rg5",
Self::Rg6 => "Rg6",
Self::Rg7 => "Rg7",
Self::Rg8 => "Rg8",
Self::Rg9 => "Rg9",
Self::Rga => "Rga",
Self::Rgb => "Rgb",
Self::Rgc => "Rgc",
Self::Rgd => "Rgd",
Self::Rge => "Rge",
Self::Rgf => "Rgf",
// special purpose
Self::Zero => "Zero",
Self::Acc => "Acc",
Self::Spr => "Spr",
Self::Bpr => "Bpr",
Self::Ret => "Ret",
Self::Idr => "Idr",
Self::Mmr => "Mmr",
// system - read only
Self::Mar => "Mar",
Self::Mdr => "Mdr",
Self::Sts => "Sts",
Self::Cir => "Cir",
Self::Pcx => "Pcx",
Self::Null => "Null",
}
)
}
}
impl std::str::FromStr for Register {
type Err = ();
fn from_str(s: &str) -> Result<Self, Self::Err> {
match s.to_ascii_lowercase().as_str() {
// general purpose
"rg0" => Ok(Self::Rg0),
"rg1" => Ok(Self::Rg1),
"rg2" => Ok(Self::Rg2),
"rg3" => Ok(Self::Rg3),
"rg4" => Ok(Self::Rg4),
"rg5" => Ok(Self::Rg5),
"rg6" => Ok(Self::Rg6),
"rg7" => Ok(Self::Rg7),
"rg8" => Ok(Self::Rg8),
"rg9" => Ok(Self::Rg9),
"rga" => Ok(Self::Rga),
"rgb" => Ok(Self::Rgb),
"rgc" => Ok(Self::Rgc),
"rgd" => Ok(Self::Rgd),
"rge" => Ok(Self::Rge),
"rgf" => Ok(Self::Rgf),
// special purpose
"zero" => Ok(Self::Zero),
"acc" => Ok(Self::Acc),
"spr" => Ok(Self::Spr),
"bpr" => Ok(Self::Bpr),
// "ret" => Ok(Self::Ret),
"idr" => Ok(Self::Idr),
"mmr" => Ok(Self::Mmr),
// system - read only
"mar" => Ok(Self::Mar),
"mdr" => Ok(Self::Mdr),
"sts" => Ok(Self::Sts),
"cir" => Ok(Self::Cir),
"pcx" => Ok(Self::Pcx),
"null" => Ok(Self::Null),
_ => Err(()),
}
}
}