mirror of
https://github.com/JanNeuendorf/SVC16.git
synced 2025-06-04 18:45:27 +00:00
Debug instruction (#12)
This combines the SET and INST instructions into one SET instruction and adds DEBUG for opcode 10. This is a breaking change, but the migration is very simple.
This commit is contained in:
parent
6abfcbc811
commit
cf3306eeb2
2
Cargo.lock
generated
2
Cargo.lock
generated
@ -539,7 +539,7 @@ checksum = "7da8b5736845d9f2fcb837ea5d9e2628564b3b043a70948a3f0b778838c5fb4f"
|
||||
|
||||
[[package]]
|
||||
name = "svc16"
|
||||
version = "0.8.0"
|
||||
version = "0.9.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"clap",
|
||||
|
@ -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"
|
||||
|
@ -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` |
|
||||
|
Binary file not shown.
BIN
examples/input_tester.svc16.gz
Normal file
BIN
examples/input_tester.svc16.gz
Normal file
Binary file not shown.
Binary file not shown.
BIN
examples/rectavoider.svc16.gz
Normal file
BIN
examples/rectavoider.svc16.gz
Normal file
Binary file not shown.
@ -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.
|
||||
|
@ -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(
|
||||
|
@ -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<Option<(u16, u16, u16)>, 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)
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user