@@ -81,6 +81,7 @@ name = "ceres-asm" | |||||
version = "0.1.0" | version = "0.1.0" | ||||
dependencies = [ | dependencies = [ | ||||
"logos 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | "logos 0.11.4 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | ] | ||||
[[package]] | [[package]] | ||||
@@ -428,7 +429,7 @@ version = "0.1.0" | |||||
dependencies = [ | dependencies = [ | ||||
"image 0.23.5 (registry+https://github.com/rust-lang/crates.io-index)", | "image 0.23.5 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
"minifb 0.16.0 (git+https://github.com/emoon/rust_minifb)", | "minifb 0.16.0 (git+https://github.com/emoon/rust_minifb)", | ||||
"thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | ] | ||||
[[package]] | [[package]] | ||||
@@ -829,15 +830,15 @@ dependencies = [ | |||||
[[package]] | [[package]] | ||||
name = "thiserror" | name = "thiserror" | ||||
version = "1.0.19" | |||||
version = "1.0.20" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
"thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)", | |||||
] | ] | ||||
[[package]] | [[package]] | ||||
name = "thiserror-impl" | name = "thiserror-impl" | ||||
version = "1.0.19" | |||||
version = "1.0.20" | |||||
source = "registry+https://github.com/rust-lang/crates.io-index" | source = "registry+https://github.com/rust-lang/crates.io-index" | ||||
dependencies = [ | dependencies = [ | ||||
"proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", | "proc-macro2 1.0.18 (registry+https://github.com/rust-lang/crates.io-index)", | ||||
@@ -1109,8 +1110,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" | |||||
"checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" | "checksum syn-mid 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7be3539f6c128a931cf19dcee741c1af532c7fd387baa739c03dd2e96479338a" | ||||
"checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" | "checksum tempfile 3.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "7a6e24d9338a0a5be79593e2fa15a648add6138caa803e2d5bc782c371732ca9" | ||||
"checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" | "checksum textwrap 0.11.0 (registry+https://github.com/rust-lang/crates.io-index)" = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060" | ||||
"checksum thiserror 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "b13f926965ad00595dd129fa12823b04bbf866e9085ab0a5f2b05b850fbfc344" | |||||
"checksum thiserror-impl 1.0.19 (registry+https://github.com/rust-lang/crates.io-index)" = "893582086c2f98cde18f906265a65b5030a074b1046c674ae898be6519a7f479" | |||||
"checksum thiserror 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "7dfdd070ccd8ccb78f4ad66bf1982dc37f620ef696c6b5028fe2ed83dd3d0d08" | |||||
"checksum thiserror-impl 1.0.20 (registry+https://github.com/rust-lang/crates.io-index)" = "bd80fc12f73063ac132ac92aceea36734f04a1d93c1240c6944e23a3b8841793" | |||||
"checksum tiff 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f3b8a87c4da944c3f27e5943289171ac71a6150a79ff6bacfff06d159dfff2f" | "checksum tiff 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)" = "3f3b8a87c4da944c3f27e5943289171ac71a6150a79ff6bacfff06d159dfff2f" | ||||
"checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" | "checksum unicode-segmentation 1.6.0 (registry+https://github.com/rust-lang/crates.io-index)" = "e83e153d1053cbb5a118eeff7fd5be06ed99153f00dbcd8ae310c5fb2b22edc0" | ||||
"checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" | "checksum unicode-width 0.1.7 (registry+https://github.com/rust-lang/crates.io-index)" = "caaa9d531767d1ff2150b9332433f32a24622147e5ebb1f26409d5da67afd479" | ||||
@@ -2,6 +2,7 @@ | |||||
a shitty fantasy console written in rust using a proprietary MIPS based asm instruction set. a lot of inspiration from PICO-8. ceres is structured of these crates: | a shitty fantasy console written in rust using a proprietary MIPS based asm instruction set. a lot of inspiration from PICO-8. ceres is structured of these crates: | ||||
- ceres-sys: the core system structure of ceres-16 | - ceres-sys: the core system structure of ceres-16 | ||||
- ceres-asm: the assembler for ceres-16 | |||||
### Graphics | ### Graphics | ||||
@@ -24,4 +25,18 @@ all registers are unsigned 16 bit | |||||
### Memory map and info | ### Memory map and info | ||||
god oh fuck what am i even doing | |||||
god oh fuck what am i even doing | |||||
### instructions | |||||
##### load - ld:signifier | |||||
can be vram/cram/imed | |||||
- immediate `ld:immd $dest immediate` | |||||
| opcode | signifier | destination | padding | immediate | | |||||
| ------- | --------- | ----------- | ------- | ------------------ | | |||||
| `00001` | `100` | `0000` | `0000` | `0000000000000000` | | |||||
@@ -8,3 +8,4 @@ edition = "2018" | |||||
[dependencies] | [dependencies] | ||||
logos = "0.11.4" | logos = "0.11.4" | ||||
thiserror = "1.0.20" |
@@ -1,14 +1,107 @@ | |||||
use logos::Logos; | |||||
// izzy do not forget to remove these later dummy | |||||
#![allow(dead_code)] | |||||
#![allow(unused_imports)] | |||||
#![allow(unused_variables)] | |||||
// namespacing | |||||
use logos::{Lexer, Logos}; | |||||
/// ceres-asm result type | |||||
pub type Result<T> = std::result::Result<T, CeresAsmError>; | |||||
/// ceres-asm error type | |||||
#[derive(Debug, thiserror::Error)] | |||||
pub enum CeresAsmError { | |||||
#[error("wrong token found:\nexpected: {expected:?}\n found: {found:?}")] | |||||
BadToken { expected: Token, found: Token }, | |||||
#[error("bad token: {token:?}")] | |||||
LazyBadToken { token: Token }, | |||||
#[error("was looking for token, found nothing ???")] | |||||
NoToken, | |||||
} | |||||
/// assembler struct | |||||
pub struct Assembler<'a> { | |||||
lexer: Lexer<'a, Token>, | |||||
} | |||||
impl<'a> Assembler<'a> { | |||||
/// create a new assembler | |||||
pub fn new(data: &'a str) -> Assembler<'a> { | |||||
let lexer = Token::lexer(data); | |||||
Assembler { lexer } | |||||
} | |||||
/// assemble | |||||
pub fn assemble(&mut self) -> Result<Vec<u32>> { | |||||
let mut machine_code: Vec<u32> = Vec::new(); | |||||
while let Some(token) = self.lexer.next() { | |||||
match token { | |||||
// cases that should continue | |||||
Token::Comment => continue, | |||||
Token::Newline => continue, | |||||
// cases that like, actually make sense to process | |||||
Token::Load => { | |||||
if let Some(new_token) = self.lexer.next() { | |||||
let signifier = Signifier::from_token(new_token)?; | |||||
machine_code.push(self.load(signifier)?); | |||||
} else { | |||||
return Err(CeresAsmError::NoToken); | |||||
} | |||||
} | |||||
// cases that should straight up not happen | |||||
_ => return Err(CeresAsmError::LazyBadToken { token }), | |||||
} | |||||
} | |||||
Ok(machine_code) | |||||
} | |||||
// load instruction assembly | |||||
fn load(&mut self, signifier: Signifier) -> Result<u32> { | |||||
let opcode: u32 = 0b00001; | |||||
let signifier: u32 = signifier.as_bits() as u32; | |||||
let instruction: u32 = (opcode << 27) | (signifier << 24); | |||||
Ok(instruction) | |||||
} | |||||
} | |||||
enum Signifier { | |||||
VideoMemory, | |||||
CartMemory, | |||||
Immediate, | |||||
} | |||||
impl Signifier { | |||||
fn from_token(token: Token) -> Result<Self> { | |||||
match token { | |||||
Token::VideoMemory => Ok(Self::VideoMemory), | |||||
Token::CartMemory => Ok(Self::CartMemory), | |||||
Token::Immediate => Ok(Self::Immediate), | |||||
_ => Err(CeresAsmError::LazyBadToken { token }), | |||||
} | |||||
} | |||||
// returns the signifier as bits stored in a u8 | |||||
fn as_bits(self) -> u8 { | |||||
match self { | |||||
Self::VideoMemory => 0b010, | |||||
Self::CartMemory => 0b001, | |||||
Self::Immediate => 0b100, | |||||
} | |||||
} | |||||
} | |||||
/// token | |||||
#[derive(Logos, Debug, PartialEq)] | #[derive(Logos, Debug, PartialEq)] | ||||
pub enum Token { | pub enum Token { | ||||
// general stuff | // general stuff | ||||
#[regex(";.+")] | #[regex(";.+")] | ||||
Comment, | Comment, | ||||
#[token("@")] | |||||
At, | |||||
#[regex("/[a-z-_]+:/g")] | |||||
Label, | |||||
#[token("\n")] | |||||
Newline, | |||||
// registers | // registers | ||||
#[token("$t0")] | #[token("$t0")] | ||||
ZeroRegister, | ZeroRegister, | ||||
@@ -25,13 +118,21 @@ pub enum Token { | |||||
#[token("$a2")] | #[token("$a2")] | ||||
ArgumentTwo, | ArgumentTwo, | ||||
#[token("$v0")] | #[token("$v0")] | ||||
ReturnOne, | |||||
ReturnZero, | |||||
#[token("$v1")] | #[token("$v1")] | ||||
ReturnTwo, | |||||
#[regex("\\$t[0-6]")] | |||||
TemoraryRegister, | |||||
#[regex("\\$[0-9]+")] | |||||
RegisterIndex, | |||||
ReturnOne, | |||||
#[regex("\\$t[0-6]", |lex| lex.slice()[2..].parse::<u16>())] | |||||
TemporaryRegister(u16), | |||||
#[regex("\\$[0-9]+", |lex| lex.slice()[1..].parse::<u16>())] | |||||
RegisterIndex(u16), | |||||
// types | |||||
#[token("immd")] | |||||
Immediate, | |||||
#[token("vram")] | |||||
VideoMemory, | |||||
#[token("cram")] | |||||
CartMemory, | |||||
// literals | // literals | ||||
#[regex("0x[a-fA-F0-9]+", |lex| u16::from_str_radix(&lex.slice()[2..], 16).unwrap() )] | #[regex("0x[a-fA-F0-9]+", |lex| u16::from_str_radix(&lex.slice()[2..], 16).unwrap() )] | ||||
@@ -42,18 +143,80 @@ pub enum Token { | |||||
DecimalLiteral(u16), | DecimalLiteral(u16), | ||||
// instructions | // instructions | ||||
#[token("add")] | |||||
Add, | |||||
#[token("sub")] | |||||
Sub, | |||||
#[token("mul")] | |||||
Mul, | |||||
#[token("div")] | |||||
Div, | |||||
#[token("jmp")] | |||||
Jmp, | |||||
#[token("ld:")] | |||||
Load, | |||||
// logos error | // logos error | ||||
#[error] | #[error] | ||||
#[regex(" +", logos::skip)] | |||||
Error, | Error, | ||||
} | } | ||||
impl Token { | |||||
// returns the token as a str | |||||
fn as_str(&self) -> String { | |||||
match self { | |||||
// general stuff | |||||
Self::Comment => "a comment".to_owned(), | |||||
Self::Newline => "\\n".to_owned(), | |||||
// registers | |||||
Self::ZeroRegister => "$z0".to_owned(), | |||||
Self::ProgramCounter => "$pc".to_owned(), | |||||
Self::StackPointer => "$sp".to_owned(), | |||||
Self::ReturnAddress => "$ra".to_owned(), | |||||
Self::ArgumentZero => "$a0".to_owned(), | |||||
Self::ArgumentOne => "$z1".to_owned(), | |||||
Self::ArgumentTwo => "$a2".to_owned(), | |||||
Self::ReturnZero => "$v0".to_owned(), | |||||
Self::ReturnOne => "$v1".to_owned(), | |||||
Self::TemporaryRegister(idx) => format!("$t{}", idx), | |||||
Self::RegisterIndex(idx) => format!("${}", idx), | |||||
// types | |||||
Self::Immediate => "immd".to_owned(), | |||||
Self::VideoMemory => "vram".to_owned(), | |||||
Self::CartMemory => "cram".to_owned(), | |||||
// literals | |||||
Self::HexLiteral(_) => "hex lit".to_owned(), | |||||
Self::BinaryLiteral(_) => "bin lit".to_owned(), | |||||
Self::DecimalLiteral(_) => "dec lit".to_owned(), | |||||
// instructions | |||||
Self::Load => "ld:".to_owned(), | |||||
// errors | |||||
Self::Error => "ERR".to_owned(), | |||||
} | |||||
} | |||||
// returns true if given token is a register | |||||
fn is_register(&self) -> bool { | |||||
match self { | |||||
Self::ZeroRegister | |||||
| Self::ProgramCounter | |||||
| Self::StackPointer | |||||
| Self::ReturnAddress | |||||
| Self::ArgumentZero | |||||
| Self::ArgumentOne | |||||
| Self::ArgumentTwo | |||||
| Self::ReturnZero | |||||
| Self::ReturnOne | |||||
| Self::TemporaryRegister(_) | |||||
| Self::RegisterIndex(_) => true, | |||||
_ => false, | |||||
} | |||||
} | |||||
// returns true if given token is a literal | |||||
fn is_literal(&self) -> bool { | |||||
match self { | |||||
Self::HexLiteral(_) | Self::BinaryLiteral(_) | Self::DecimalLiteral(_) => true, | |||||
_ => false, | |||||
} | |||||
} | |||||
// returns true if it's a valid memory location or immediate | |||||
fn is_signifier(&self) -> bool { | |||||
match self { | |||||
Self::VideoMemory | Self::CartMemory | Self::Immediate => true, | |||||
_ => false, | |||||
} | |||||
} | |||||
} |
@@ -8,6 +8,16 @@ impl VideoMemory { | |||||
pub fn init() -> VideoMemory { | pub fn init() -> VideoMemory { | ||||
VideoMemory { data: [0x0000; crate::VIDEO_MEMORY_LEN] } | VideoMemory { data: [0x0000; crate::VIDEO_MEMORY_LEN] } | ||||
} | } | ||||
/// an iterator | |||||
pub fn iter(&self) -> impl Iterator<Item = &u16> { | |||||
self.data.iter() | |||||
} | |||||
/// mutable iterator | |||||
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut u16> { | |||||
self.data.iter_mut() | |||||
} | |||||
} | } | ||||
impl std::ops::Index<u16> for VideoMemory { | impl std::ops::Index<u16> for VideoMemory { | ||||
@@ -0,0 +1,10 @@ | |||||
alias r := run | |||||
alias b := build | |||||
build: | |||||
clear | |||||
cargo build | |||||
run: | |||||
clear | |||||
cargo run |
@@ -6,12 +6,18 @@ use structopt::StructOpt; | |||||
struct Opt { | struct Opt { | ||||
/// times the screen should be scaled by | /// times the screen should be scaled by | ||||
#[structopt(short = "s", long = "scale", default_value = "4")] | #[structopt(short = "s", long = "scale", default_value = "4")] | ||||
scale_factor: usize, | |||||
_scale_factor: usize, | |||||
} | } | ||||
fn wrapper() -> Result<()> { | fn wrapper() -> Result<()> { | ||||
let opt = Opt::from_args(); | |||||
// load options | |||||
let _opt = Opt::from_args(); | |||||
// initialize the system | |||||
let _sys = ceres_sys::System::init(); | |||||
/* | |||||
// create a new graphics context | |||||
let mut ctx = offbrand::Context::new( | let mut ctx = offbrand::Context::new( | ||||
ceres_sys::SCREEN_WIDTH, | ceres_sys::SCREEN_WIDTH, | ||||
ceres_sys::SCREEN_HEIGHT, | ceres_sys::SCREEN_HEIGHT, | ||||
@@ -19,25 +25,12 @@ fn wrapper() -> Result<()> { | |||||
Some(opt.scale_factor), | Some(opt.scale_factor), | ||||
)?; | )?; | ||||
let mut sys = ceres_sys::System::init(); | |||||
let mut colors: Vec<u16> = Vec::new(); | |||||
for r in 0x0..0xf { | |||||
for g in 0x0..0xf { | |||||
for b in 0x0..0xf { | |||||
colors.push((r << 8) | (g << 4) | b); | |||||
} | |||||
} | |||||
} | |||||
colors.iter().enumerate().for_each(|(addr, color)| { | |||||
sys.video_memory[addr as u16] = *color; | |||||
}); | |||||
// loop while the context window is open | |||||
while ctx.is_open() { | while ctx.is_open() { | ||||
// clear the context | |||||
ctx.clear(None); | ctx.clear(None); | ||||
// print a pixel for ever word in the video memory buffer | |||||
for x in 0..ceres_sys::SCREEN_WIDTH { | for x in 0..ceres_sys::SCREEN_WIDTH { | ||||
for y in 0..ceres_sys::SCREEN_HEIGHT { | for y in 0..ceres_sys::SCREEN_HEIGHT { | ||||
let color = | let color = | ||||
@@ -46,19 +39,14 @@ fn wrapper() -> Result<()> { | |||||
} | } | ||||
} | } | ||||
// render the context | |||||
ctx.present()?; | ctx.present()?; | ||||
} | } | ||||
*/ | |||||
Ok(()) | Ok(()) | ||||
} | } | ||||
fn color_from_u16(color: u16) -> Color { | |||||
let r = (((color >> 8) & 0x000f) as u8) * 17; | |||||
let g = (((color >> 4) & 0x000f) as u8) * 17; | |||||
let b = ((color & 0x000f) as u8) * 17; | |||||
Color { r, g, b } | |||||
} | |||||
fn main() { | fn main() { | ||||
if let Err(err) = wrapper() { | if let Err(err) = wrapper() { | ||||
eprintln!("error: {:?}", err); | eprintln!("error: {:?}", err); | ||||
@@ -1,5 +1,2 @@ | |||||
; a simple test application | |||||
main: | |||||
load $t0 0xcccc | |||||
load $t1 0xaaaa | |||||
sub $t0 $t1 $t3 | |||||
; a simple test | |||||
ld:immd $t0 0x5a5a |