diff --git a/Cargo.lock b/Cargo.lock index 5314c11..3dc6655 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -539,7 +539,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f" [[package]] name = "svc16" -version = "0.8.0" +version = "0.9.0" dependencies = [ "anyhow", "clap", diff --git a/Cargo.toml b/Cargo.toml index 01687a2..a86f7fd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "svc16" -version = "0.8.0" +version = "0.9.0" edition = "2021" authors = ["Jan Neuendorf"] description = "An emulator for a simple virtual computer" diff --git a/README.md b/README.md index cb81443..8c60932 100644 --- a/README.md +++ b/README.md @@ -122,13 +122,12 @@ If the opcode is greater than 15, ***and the code is run***, the system will abo > You can have data blobs in the binary that does not correspond with the opcodes. > This is fine **until and unless** you explicitly try to run this blob of data as code. -If one of the three arguments is not used, it can be set to any value, but it can not be omitted. When the instruction pointer advances, it does so by four positions. | Opcode | Name | Advances | Effect | | ------ | --------- | ---------- | ---------------------------------------------------------------------------- | -| 0 | **Set** | yes | `@arg1=arg2` | +| 0 | **Set** | yes | `if arg3{@arg1=inst_ptr}else{@arg1=arg2}` | | 1 | **GoTo** | if skipped | `if(not @arg3){inst_ptr=@arg1+arg2}` | | 2 | **Skip** | if skipped | `if(not @arg3){inst_ptr=inst_ptr+4*arg1-4*arg2}` | | 3 | **Add** | yes | `@arg3=(@arg1+@arg2)` | @@ -138,8 +137,8 @@ When the instruction pointer advances, it does so by four positions. | 7 | **Cmp** | yes | `@arg3=(@arg1<@arg2)` as unsigned | | 8 | **Deref** | yes | `@arg2=@(@arg1+arg3)` | | 9 | **Ref** | yes | `@(@arg1+arg3)=@arg2` | -| 10 | **Inst** | yes | `@arg1=inst_ptr` | -| 11 | **Print** | yes | Writes `value=@arg1` to `index=@arg2` of buffer `arg3` +| 10 | **Debug** | yes | Provides `arg1,@arg2,@arg3` as debug information | +| 11 | **Print** | yes | Writes `value=@arg1` to `index=@arg2` of buffer `arg3` | | 12 | **Read** | yes | Copies `index=@arg1` of buffer `arg3` to `@arg2` | | 13 | **Band** | yes | `@arg3=@arg1&@arg2` | | 14 | **Xor** | yes | `@arg3=@arg1^@arg2` | diff --git a/examples/input_tester.svc16 b/examples/input_tester.svc16 deleted file mode 100644 index 30b66a1..0000000 Binary files a/examples/input_tester.svc16 and /dev/null differ diff --git a/examples/input_tester.svc16.gz b/examples/input_tester.svc16.gz new file mode 100644 index 0000000..d060c73 Binary files /dev/null and b/examples/input_tester.svc16.gz differ diff --git a/examples/rectavoider.svc16 b/examples/rectavoider.svc16 deleted file mode 100644 index 0879794..0000000 Binary files a/examples/rectavoider.svc16 and /dev/null differ diff --git a/examples/rectavoider.svc16.gz b/examples/rectavoider.svc16.gz new file mode 100644 index 0000000..76c111e Binary files /dev/null and b/examples/rectavoider.svc16.gz differ diff --git a/specification/specification.typ b/specification/specification.typ index cf7d3b8..e9963e1 100644 --- a/specification/specification.typ +++ b/specification/specification.typ @@ -158,7 +158,8 @@ All instructions are 4 values long. A value is, of course, a `u16`. The instructions have the form `opcode` `arg1` `arg2` `arg3`. All instructions are listed in @instructions. -`@arg1` refers to the value at the memory address `arg1`. If the opcode is greater than 15, the system will abort. If one of the three arguments is not used, it can be set to any value, but it can not be omitted. +`@arg1` refers to the value at the memory address `arg1`. +If the opcode is greater than 15, the system will abort. #let instruction_table=table( @@ -167,7 +168,11 @@ All instructions are listed in @instructions. table.header( [*Opcode*], [*Name*],[*Effect*], ), - [0],[*Set*],[`@arg1=arg2`], + [0],[*Set*],[`if arg3{ + @arg1=inst_ptr + }else{ + @arg1=arg2 + }`], [1],[*GoTo*],[`if(not @arg3){inst_ptr=@arg1+arg2}`], [2],[*Skip*],[ ``` @@ -183,7 +188,7 @@ All instructions are listed in @instructions. [7],[*Cmp*],[`@arg3=(@arg1<@arg2)` (as unsigned)], [8],[*Deref*],[`@arg2=@(@arg1+arg3)`], [9],[*Ref*],[`@(@arg1+arg3)=@arg2`], - [10],[*Inst*],[`@arg1=inst_ptr`], + [10],[*Debug*],[Provides `arg1,@arg2,@arg3` as debug information], [11],[*Print*],[Writes `value=@arg1` to `index=@arg2` of buffer `arg3`], [12],[*Read*],[Copies `index=@arg1` of buffer `arg3` to `@arg2`.], [13],[*Band*],[`@arg3=@arg1&@arg2` (binary and)], @@ -200,6 +205,20 @@ Every instruction shown in @instructions advances the instruction pointer by fou When an argument refers to the name of a buffer, it means the screen buffer if it is 0 and the utility buffer otherwise. +== The Debug Instruction +The *Debug* instruction is special, as it does not change anything about the state of the system. +It still counts as an instruction for the maximum instruction count. +It is up to the implementation if, when and in what way the information is provided to the user. +This means that it is valid to not do anything when the instruction is triggered. +In fact, this might be necessary to run the emulator at the intended speed. + +The way to think about the signature of the instruction is that the first argument is a label and the other arguments are the values of variables/addresses. + +The instruction is meant only for debugging. +For the programmer that means that the output should not be needed for the use of the program or game as it might be shown in different ways or not at all. +For the emulator that means that there should be no functionality that depends on the *Debug* instruction. + + = Constructing the Program A program is really just the initial state of the main memory. diff --git a/src/cli.rs b/src/cli.rs index 71a9e7e..e690698 100644 --- a/src/cli.rs +++ b/src/cli.rs @@ -27,7 +27,7 @@ pub struct Cli { short, long, default_value_t = false, - help = "Show performance metrics" + help = "Show performance metrics and debug info" )] pub verbose: bool, #[arg( diff --git a/src/engine.rs b/src/engine.rs index b743314..9298673 100644 --- a/src/engine.rs +++ b/src/engine.rs @@ -13,7 +13,7 @@ const DIV: u16 = 6; const CMP: u16 = 7; const DEREF: u16 = 8; const REF: u16 = 9; -const INST: u16 = 10; +const DEBUG: u16 = 10; const PRINT: u16 = 11; const READ: u16 = 12; const BAND: u16 = 13; @@ -115,11 +115,15 @@ impl Engine { fn advance_inst_ptr(&mut self) { self.instruction_pointer = self.instruction_pointer.wrapping_add(4); } - pub fn step(&mut self) -> Result<(), EngineError> { + pub fn step(&mut self) -> Result, EngineError> { let [opcode, arg1, arg2, arg3] = self.read_instruction(); match opcode { SET => { - self.set(arg1, arg2); + let value = match arg3 { + 0 => arg2, + _ => self.instruction_pointer, + }; + self.set(arg1, value); self.advance_inst_ptr(); } GOTO => { @@ -177,10 +181,12 @@ impl Engine { self.set(self.get(arg1) + arg3, value); self.advance_inst_ptr(); } - INST => { - let value = self.instruction_pointer; - self.set(arg1, value); + DEBUG => { + let label = arg1; + let value1 = self.get(arg2); + let value2 = self.get(arg3); self.advance_inst_ptr(); + return Ok(Some((label, value1, value2))); } PRINT => { if arg3 == 0 { @@ -219,6 +225,6 @@ impl Engine { } _ => return Err(EngineError::InvalidInstruction(opcode)), } - Ok(()) + Ok(None) } } diff --git a/src/main.rs b/src/main.rs index a07b670..cb23554 100644 --- a/src/main.rs +++ b/src/main.rs @@ -77,7 +77,14 @@ async fn main() -> Result<()> { if !paused { ipf = 0; while !engine.wants_to_sync() && ipf <= MAX_IPF { - engine.step()?; + if let Some(debug_output) = engine.step()? { + if cli.verbose { + println!( + "DEBUG label: {} values: {}, {}", + debug_output.0, debug_output.1, debug_output.2 + ); + } + } ipf += 1; }