This thread is for short questions about Chip8 programming and the Octo assembly language. If you have a more involved question, feel free to start a separate thread.
That's a great question!
The Chip8 virtual machine runs at an arbitrary speed. On the original COSMAC VIP, different instructions took a different amount of time to execute, and execution was generally quite slow. On modern interpreters, including Octo, this is generally abstracted to simply executing some number of instructions per frame. Octo allows you to adjust this speed by using the "Speed" top menu. (30 or less could be considered loosely "realistic" if you want your program to run acceptably on an HP-48 calculator, and the VIP is a little closer to 15.)
While we don't know how fast instructions will execute, we do have one precise timing reference: the "delay" register. If nonzero, the delay register counts down 60 times a second. Thus, if we use delay as a reference, we can make a fast-running interpreter burn off excess time, and allow our games to run at the same apparent speed. There's an example of this delay loop in the Chip8 programming techniques guide, as well as in many examples.
Like the delay register, the Chip8 display is updated 60 times a second- no matter where the program is in execution, or how much progress it has made in updating the display for the main loop. If objects are drawn in time on some frames and too late on other frames, they flicker.
So what do we do about it? Here are a few approaches:
> Use a delay loop in combination with more speed.
It seems doing this you could guarantee no flicker... I just glanced at the source and the DT/ST (delay/sound) special registers are decremented right after the browser does a redraw (which is with a 60 FPS setTimeout)... so if you say you have a traditional update/render loop and just put off the render until immediately after DT hits 0 then as long as you're done drawing in 16ms you shouldn't see any flicker at all. IE for 60fps: render, set delay 1, process input, etc... when delay is 0, render... rinse repeat. That's my understanding from just a quick look.
To be clear I'm more interested in the VM on modern hardware... so if you're targeting say a HP-48 this might be much harder in practice.
Yep. If your interpreter is fast enough, and your rendering will never exceed the available time, doing what you describe will work. You could also initialize the delay timer before starting a render, so you're timing render+update and then burning off any excess in the sync loop.
How much speed a game requires is mostly up to the developer. Some very interesting ideas are possible with brute force. Games that have more modest requirements will be playable on a wider range of devices. Working within the performance envelope of old hardware can be a fun creative constraint, but you're welcome to ignore it if it isn't for you.
Something to consider, if you do happen to be targeting an HP48: actual pixel on time is very important. This is because the pixels of its LCD are quite slow to transition between on and off states, and you are effectively drawing directly to the display. If your processing time per frame is very high, it's very helpful to avoid the pattern of
and instead use a pattern that resembles
This approach could be helpful in other situations, too, such as when you're targeting eg 30 or even 15 fps due to massive processing requirements. While the above may seem obvious, the issue it would solve usually seems insignificant in Octo. Both eg my own game, Octopeg and the lander game Sub-Terr8nia, while perfect in Octo, were initially basically unplayable on a real HP48: each game's main sprite was basically invisible. Both games were modified from the first pattern to the second, and then both played perfectly.
I think this advice is relevant HP48 or not (as I read it)… though perhaps the meaning of “heavy calculations” is doing all the work then… since on modern hardware you can just crank up the cycles per frame making “heavy calculations” unimportant, where as I imagine on the HP48 you’re much more limited to what you have.
OR perhaps the HP48 is writing to the screen real-time, instead of buffering? That would make a huge difference also… do you know?
The modern stuff (Octo) is all buffered so you can do anything you like so long as the screen is “ready” before the next 60Hz tick.
It's important to note that Octo, while allowing us to ignore some things, does also serve as a very good platform for developing games that would run on real hardware (from the eras where such hardware existed). Even though written today, this jam does typically receives a few submissions that have been created with this intent - running at realistic speeds and with the appropriate interpreter quirks. A good number of people are fairly passionate about retro computing and enjoy this aspect in particular - myself included. Sometimes the jam has been able to have the games demonstrated on the appropriate hardware, but, emulators for the specific platforms do also exist, which allow us to verify this to some degree.
To answer your question about the HP48 display - it's a little complicated? The HP48 display is updated from a buffer in the calculator's memory, but, it is on a line by line basis & spread out evenly across the entire 'frame' time - there's eg no flip() that updates the entire display all at once. The update rate for the whole display is effectively 64Hz, however the 'next' line is drawn at a rate of 4096Hz by the CPU, interrupting away from eg the interpreter as it goes, but allowing our code to run in between. This allows us to eg turn off a sprite halfway through it being drawn.
This is not quite the same as writing directly to the screen, but it shares some of the issues. The point where the line-by-line update coincides with when you 'delay' may also strobe across the display, as we have little to anchor it in place. If you might only have a sprite drawn into the buffer for eg 10% of that 64Hz tick, that may be the cause of the issues observed.
Even if that assessment is incorrect on a technical level, the strategy proposed prior has been shown to make games significantly more playable on the HP48, besides which it's generally good advice that helps keep flickering to a minimum. If any given sprite is flickering in some way, it will significantly impact its vibrancy on screen on the HP48, as the pixels aren't super quick to respond in either direction. On the plus side, it also means doing pixel queries to the display are relatively hidden.
Well, I hope that was interesting! The jam certainly attracts all sorts so, feel free to focus on the aspects that are most appealing to you!
Hey, gearing up for the Jam and playing around with Octo a bit. One thing I'm trying to get my head around is macros and when and how they're allowed to be nested. As far as I can tell, if I try a macro invocation inside a calc or byte, it will be undefined - is that expected? Is there any way I can fully expand a (possibly nested) macro call, before it gets sent to a byte, or am I picturing things completely wrong?
Octo programs are assembled in one long linear pass. There are a few minor exceptions to this, like forward-referenced labels, but the conceptual model is not recursive or scoped. It works very much like a Forth.
A program is a sequence of tokens. Each statement, like "sprite" or "i := NNN", consumes some number of tokens from that sequence. The name of a previously-declared macro is a statement, (an invocation) which consumes zero or more tokens (as arguments), and then dumps a list of tokens (the macro body) into the program's token sequence, substituting arguments as it goes. Another way to think about it is that macro invocations are always the head of a statement.
Constant expressions (inside { }) are their own separate domain from normal assembly, and macros usually only interact with them while substituting arguments as part of their expansion process. It's also worth noting that bindings created by ":calc" are global (much like aliases), and can be redefined as many times as needed over the course of assembly; they are not evaluated specially in relation to macro bodies or constrained to the scope of a macro where they appear.
Does that all make sense? Could you provide more context for what you're trying to accomplish?
I'm weirdly interested in targeting the original CHIP8 implementation, with the goal being to ensure the game at least runs playably (if not particularly quickly) on the old hardware. It'll be a turn based game, so speed isn't critical. I of course lack the original hardware to test with. Questions about the "VIP" compatability mode:
- Sprite drawing trigger vblank: does this mean no more than roughly 60 sprite calls per second, but other instructions have more leeway?
- Is 15 cycles/frame a reasonable approximation of the original hardware?
And a more general question: have I missed something, or is the only way to update I with a runtime value really to use self-modifying code? The resulting code structures feel quite awkward.
Cool! I made two games last year that targeted the original COSMAC VIP CHIP-8 implementation, and I'm working on another now. Historical accuracy is definitely one of the things I enjoy most in Octojam.
"Sprite drawing trigger vblank" means that when Octo encounters a `sprite` instruction, it will then halt and wait for the next frame is drawn before continuing execution, so drawing sprites will be a bottleneck for speed.
I think 7 or 15 cycles per frame is reasonable. It's all an approximation, of course, since in Octo all instructions take one "cycle" (AFAIK) while it varies between instructions on COSMAC VIP. Here's a table of real-world timing for each instruction.
The best approach is probably to test your game in the Emma02 emulator, which emulates the COSMAC VIP faithfully. As far as I remember it's a little hard to understand how to use it though.
It's true that self-modifying code is necessary if you want to load an arbitrary 12-bit address in one cycle. Depending on the specific circumstances, there may be simpler alternatives:
I have some examples and more discussion in the Chip8 programming techniques guide
So, rather naively, I've just had an Octo tab open for a couple of days while I play about. However today after undoing something, the program text switched to "loading...", at which point I couldn't get anything back (I even inspected local storage through the web developer tools). I assume I'm out of luck, but just thought I'd ask if there as any magic to get my code back, before I move to a more robust editor.
Is there any more “developer like environment” hiding in the Octo repo somewhere? IE, I save my files on disk and the web browser auto-refreshes or at the very least I manually refresh it and my program is running the updated code from disk…
I could hack this together myself but wonder if anyone else has already done the work…
There are syntax definitions for a few text editors here https://github.com/JohnEarnest/Octo and you can use the command line mode if that suits you better.
Heh, possibly not for everyone, but behold (please don't judge my terrible Octo code, I'm trying, I'm trying):
Somebody has written an Emacs lisp CHIP-8 emulator so I can just work inside there where I'm most happy. Next steps would be to set up Flymake for on the fly compilation and error checking and then just hook it up to rebuild and rerun on every save, and seems like a nice productive environment. At the very least, I can't lose anything anymore.
Hey there,
I have a question regarding labels in Octo. I want to have a look-up table of labels that point to data:
: my-data image-1 image-2 image-3 : image-1 0xFF 0x00 0xFF # Et cetera
I use this table to look up the address of the right image and load that in "i" with some self-modifying code that adds a 0xA0 (load i) prefix. And it seemed to be working quite well so far.
However, now that my program has passed the 3,5k mark I'm getting an error "Value '<much>' for label 'image-1' cannot not fit in 12 bits!" (there's a small spelling mistake in that error, by the way). This has lead me to realise that my table of labels doesn't really compile to a table of addresses, but to a table of call statements. And the fact that I AND those statements with the right nibble in my self-modifying code has kept that fact hidden from me.
So the question is... does Octo have a way to just put the address of a given label in the output binary? I looked through the manual, but didn't really find anything.
This example shows a table like this:
: table i := 0x123 ; i := 0x456 ; i := 0x789 ; i := 0xABC ; i := 0xDEF ;
Which would work with "i := long <label>;" too, I guess. But that would be six bytes per entry, instead of two. Seems like a waste :)
Yes- constants and labels are the same thing!
The only major limitation is that once you start operating on addresses with macros and doing compile-time calculations, you must declare those labels before referencing them in expressions. In some instances this may require you to use `:org` to assemble your program in a non-linear fashion.
Ahh, so that's what's going wrong. That's pretty annoying :/ I have a shit-ton of data that I need to reference in this way. I guess the data is going to the top of the file, cluttering up the place, if there is no other way.
I was just getting used to the jump0 trick, until I realised that I have 48 entries in the table and 47 * 6 = 282, which is more than v0 can hold... So v0 is overflowing and giving me errors... o_0'
If I want to have the data before my code, can I do something like this?
:org end-of-code <data> :org 200 : main <code> : end-of-code
This particular code gives me an "Undefined name 'end-of-code'".
Edit: Never mind! I don't have to move all the code under the data, I just have to move the look-up table under the data. Problem solved! Thanks guys :)
:org can't deal with a forward reference either. It would kinda require the assembler to contain a constraint solver to support that sort of thing in full generality.
For an XO-CHIP game, I'd recommend using a pair of macros something like the following:
:calc CODE_POS { 0x200 } :calc DATA_POS { 0x1000 } :macro to-code { :calc DATA_POS { HERE } :org { CODE_POS } } :macro to-data { :calc CODE_POS { HERE } :org { DATA_POS } }
The idea is to reserve the low 4kb of RAM for code, and then position all of your data afterwards. By invoking these macros in turn, we can alternate between defining data and defining code, and thus keep related information together within our source code. Then it's easy enough to define "high RAM" addresses before we need to build pointers to them, for example.
: main jump draw-smile to-data : smile 0x50 0x00 0x88 0x70 to-code : draw-smile i := long smile sprite v0 v0 4
This will often waste a bit of space in the gap between "code" and "data", but you can manually tweak the initial value of "DATA_POS" if things get really tight.
Make sense?
edit: And, yes, you're absolutely right: for your specific case you just have to put the lookup table in the right place. :)
That's interesting! The behaviour is a bit different though: I can :unpack a label that occurs later in my code, but with this macro I can only use labels that are placed before it in my code.
The reason I want to have a 16-bit unpack is to be able to dynamically point to data in the lower 61.5k from the code in the top 3.5k. I guess the advice would then still be to restructure the code with the whole :org to-data to-code trick..? :/
Why is it that :unpack only does 12 bits? Is it also from before the 65k expansion?
Alright, I need to be able to do an "unpack-long" type of thing. Seeing as my file was ~3000 lines anyway, I decided to split it up into smaller files and recombine it in a build stage. This also allows me more flexibility in moving data and code around without getting annoyed by it.
So I'm trying do this:
:org 0x1000 # Data here :org 0x0200 : main # Code here loop again
But if you try this in Octo it will give you an error: "Data overlap. Address 0x0200 has already been defined". I guess there's an implicit ":org 0x0200" at the top of the file.
This works:
: main jump 0x0202 :org 0x1000 # Data here :org 0x0202 # Code here loop again
But I mean, that's just plain annoying ;P Especially since an "unpack-long" is pretty much the first thing I want to do in my ": main". Do you have any suggestions on how to clean this up..?
Your first example, or anything like
:org 0x210 0xAB 0xCD :org 0x200 : main loop again
Should work now.
I corrected a flaw in the assembler; give it another shot. It doesn't actually stem from any sort of implicit :org, but rather the fact that the "main" label is special. For programmer convenience, Octo will reserve space for a jump instruction at 0x200 to branch forward to main if it is not the first label defined in the program. Once you start re-orging things this gets a little fiddly, and it broke some assumptions I made very early in Octo's history. It would have worked fine if anything *except* main was the first definition in code-space, but now you can have those precious two bytes back.
As far as I understand it, which is probably not that far ;), random takes a bitmask as an argument. The bitmask is a value that gets ANDed with a random number from 0 to 255. So:
v0 := random 0xFF # v0 is a random number from 0 to 255 v0 := random 0x01 # v0 is randomly 0 or 1 v0 := random 0x07 # v0 is a random number from 0 to 7 v0 := random 0x08 # Careful: v0 is randomly 0 or 8 ;)
Treat i more like a pointer. You can advance it to get to another memory location. https://johnearnest.github.io/Octo/docs/Manual.html#statements
It’s easy enough to load it (using self modifying code)… but you can’t ever retrieve it’s value (that’s hidden inside the emulator)… so if you need that type of indirection then you manually need to use your OWN external pointer and track it’s value, loading it into i
when necessary… which of course is slower.
Try reading here, search for load and look at the examples how they are used: https://github.com/JohnEarnest/Octo/blob/gh-pages/docs/Chip8%20Programming.md
First, the simplest case: you have a single byte in memory and you want to load it into a register:
: data 0xAB ... i := data # set i to the base address load v0 # load from i into v0
Slightly more complex: you have an array of up to 256 bytes, and you want to load one by index into a register:
: data 1 1 2 3 5 8 11 ... i := data # set i to the base address i += v1 # add an offset (in this case, the value in v1) load v0 # load data[v1] into v0
Same as before, but now we want 2 bytes:
i := data # set i to the base address i += v1 # add an offset. items are two bytes wide... i += v1 # (we alternatively could have pre-doubled v1.) load v1 # v0=data[2*v1], v1=data[(2*v1)+1]
Are those cases clear?
Yes, I could. I thought about adding another "Press any key" to the first screen (that doesn't have any sound, and currently just transitions after a given time). But then I'd have to redesign that screen to make room for the extra text. Ah well, you have to know when to stop sometimes ;) This is not an issue with Octo or my program, it's just a little annoyance because of the fact that we run it in a browser. That's okay with me.
The Octo Repository has a few examples of different approaches to simple data structures and algorithms, and the FAQ has some simple examples of working with memory.
So long as the number of elements fit in a v-register, indexing into an "array" requires setting "i" to the base of the array, adding your offset, and then using load/save. A stack (FILO) requires persistent storage of the index of the top of the stack, perhaps in a reserved v-register. A queue (FIFO) is most easily implemented as a circular buffer, and requires two indices, for a "head" and "tail".