The GBJam isn't about making software that runs on a legacy platform; you're using modern game engines, languages, and techniques while imitating the aesthetic limitations of the original Game Boy. (And pretty loosely, at that.)
It's a very different style of creative constraint compared to programming a specific virtual machine with limited RAM and not a multiplier or floating-point unit in sight.
Gotcha, I assumed that it was programming against the original exactly. But I guess the emulators they allow are more flexible/modern takes on the original.
It depends on the CPU, though in most CPUs, the comparison is a separate instruction from the branch. For example, on x86,
CMP RAX, RBX
JL label
would jump if RAX < RBX.
Similarly, on ARM:
CMP r1, r2
BLT label /* jump if r1<r2 */
Some processors do have a conditional jump that takes the registers to compare though, such as the PDP-10's bewildering array of skip instructions:
CAML A, B
ADDI C, 100 ; This instruction will be skipped if A < B
Finally, it's also fairly common to have branch instructions that compare against 0, such as PDP-10:
JUMPL A, label ; jump if A<0
That said, off the top of my head, I don't know of any real processors with something equivalent to the fantasy CPU's JL, which combines a comparison between two registers with a jump to an arbitrary immediate.
In RISC-V's case, you cannot jump to an arbitrary address conditionally at all, you have to use a branch instruction. B-type instructions have only a 12-bit immediate value (technically 13 bits with an implied 0 in the lowest order bit), which means it can only code for jumps up to +- 1024 words away (IIRC, this 13 bit value codes for a jump up to +- 2048 half-words away, but RISC-V instructions are full words, hence 1024... using the 16-bit extensions would change things I think).
RISC-V J-types have a 20 bit jump target addressing relative half-words giving you a range of 2^20 / (2 * 2) = +- 262144 full words. Alternative you could use JALR, which will let you jump to any 32-bit address, but then you would have potentially up to 4 instructions for a conditional jump (blt, luo, ori, jalr).
To my recollection, MIPS is similar but has a larger immediate field for J-types. I'm not familiar enough with other arches to be comment on them.
The way you would code for a conditional jump to a far away target would be to use a conditional branch, which either skips over or runs an unconditional jump. If using JALR as described, you might have something like:
...
blt t1, t2, jump_not_taken
lui t3, 0x1234 # upper 20 bits of jump target
ori t3, 0x456 # lower 12 bits of jump target
jalr t3
jump_not_taken:
...
To answer the question more concisely, something like JL would probably end up being an assembler pseudo-instruction. For example in RISC-V there is no BGE instruction, it compiles to a BLE with the operands swapped. This reduces overhead in the instruction encoding, and is equivalent.
AMD64/IA32 are probably different, but I don't know. And of course you could always define a CPU architecture with something like "conditional jump to register value" (JALRLT for example). I don't know if anybody has though.
`JL` and other conditional jumps/branches are a common type of instruction [0][1][2].
Depending on the ISA [3][4][5], there can be jumps, branches, and subroutine transfers (or even a subset of those). Some can be based (conditional) on what value is in a primary register (often traditionally called the accumulator), a condition/flag/status register, a memory location, and even well known constants (0, 1, -1). Many ISAs have ways branching to a function/subroutine/procedure that can include adjusting a calling stack or transferring control to an OS kernel. x86 is notable in that you can cause a branch or jump without calling a branch or jump instruction.
> x86 is notable in that you can cause a branch or jump without calling a branch or jump instruction.
I'm curious what you are referring to here. Are you talking about CMOV? Or INT/SYSCALL/SYSENTER? Or just the ability to trigger a fault handler? Or semantic games like push followed by ret?
Nice article! I would definitely recommend for everyone to write a CPU emulator at least once. It will give you a lot of perspective on how computers really work. As someone else pointed out CHIP-8[0] is a good one that's not too complicated.
If you want something more "real", MIPS and RISC-V are also not too hard, if you just do a subset of the instruction set and not the whole thing. You can skip most of the instructions that deal in bytes and half-words. It's also easier to implement main memory as being word-addressable only (but this can introduce incompatibilities with real assembler programs).
I wrote a cycle-accurate, pipeline simulator for MIPS for a course, which totaled only about 1200 SLOC. If you didn't need to simulate all the pipeline registers (you probably don't) and use a higher level language, this is probably a <750SLOC project.
I'm not the person you asked, but I made an educational simulator for RISC-V [0].
The most critical resource even if it is not super convenient is the official RISC-V specification [1] (this is the latest draft as of writing this comment).
There are a bunch of open source simulators [2] to look at for reference if you are unsure of how something is suppose to work. I would recommend compiling a C program to RISC-V and looking at disassembled machine code to get an idea for how things are normally done in terms of RISC-V assembly.
There isn't any particularly nice documentation that I am aware of, but enough universities teach using it that there should be some resources that would be helpful for making a simulator. I can probably come up with some links if you are serious about making a simulator, but its a little too much effort to do for a drive-by comment.
Hey, thanks for making RARS! I am TA-ing a class this semester where we are using RARS. It's my university's "build a CPU with Verilog class", and we just switched to RISC-V. We previously used MIPS with MARS, and lacking a MARS alternative was one of our blockers for switching to RISC-V.
This book will tell you how to efficiently generate condition flag values such as carry and overflow. It's easy when when you have extra bits available (such as emulating an 8 or 16 bit processor on a 32-bit processor), but less obvious when you are emulating a 32-bit processor on a 32-bit processor from a high level language.
I don't have a specific suggestion for learning to make CPU emulators in particular.
As thethirdone said in his comment, the official specification is the most important resource.
The actual body of the simulator is mostly going to be a lot of lookup tables. You'll probably have a struct or class that stores the state of the CPU at a specific instant in time. Your main loop will be calling a next_state() function on that object. For your sanity, I would highly suggest making your state object immutable.
I have found breaking out the ALU, instruction decoder, and control logic into separate functions is a good way to split things apart.
It ultimately depends on the level of abstraction ("emulating a CPU" is ambiguous): emulating the logical gates of a CPU is an entirely different matter from emulating the instruction set; besides, generally one emulates an entire system, not just the CPU.
If you're looking for the former, the canonical course is "NAND2Tetris". If you take the class on Coursera, the first (of two parts) covers the hardware.
I've also seen two very interesting books, but I haven't read them - "The ZX Spectrum Ula: How to Design a Microcomputer", and "Designing Video Game Hardware in Verilog".
Courses at this level are generally exactly defined.
In the second case - emulating at the ISA level, the canonical entry is to emulate a CHIP-8, since it's simple and relatively well defined.
At this level, targets are rarely exactly defined. CHIP-8 for example is well-defined in theory, however, for example, looking at the Wikipedia page won't suffice, because there are many extensions. Additionally, CHIP-8 has no standard clock speed. But it's easy to find the resources, and you can end with an exactly working system (the same can't be easily say, for example, of a NES or a C64 - you'll find conflicting information).
> If you want something more "real", MIPS and RISC-V are also not too hard,...
I personally think that MIPS is just not relevant enough in the real world anymore to deserve being taught at school. But I do get that there already is a lot of material out there.
I'd personally go with a risc-v subset at first and expand on it.
> I personally think that MIPS is just not relevant enough in the real world anymore to deserve being taught at school. But I do get that there already is a lot of material out there.
From a pedagogy perspective, the course materials / textbooks haven't really caught up yet. Also, the project I was referring to was in an Architecture class; I would contend that the underlying concepts of how a pipeline works can be taught pretty much any architecture. MIPS is easy to understand, which makes it well suited for teaching.
That said, at my university, we are slowly transitioning to RISC-V, so hopefully within a few years it will have propagated to all of the courses.
I've written a SuperH and TriCore emulator from scratch (qemu wouldn't cut it). I couldn't do it in JavaScript because it falls apart when it comes to bit-shifting and integer arithmetic. A lot of processors rely on the C-like functionality of int32_t, uint32_t, etc.
Many years ago when I was bored I «designed» a fantasy CPU with 6 (six) bits and only one usable register, the accumulator. I thought why not. The result:
18 bit address bus
12 bit stack pointer
6 flags zero, negative, carry, overflow, float and user
7 addressing modes accumulator, immediate, absolute, relative, stack, stack without pop, stack special
64 opcodes like NOP, OR, JMP, ROL, STO, ...
I discovered that it is well possible to have a 6 bit CPU but of course this is just a game and probably not useful at all. I wonder whether I would be able to create the circuit for the CPU in a simulator. Probably not even if I omit the floating point handling or only if I dedicated about 10 years of free time. I would need to design a microcode or PLA (like MOS 6502, see https://news.ycombinator.com/item?id=5353198) system.
I find it amusing that this is the one CPU where octal base is really the primary way to display numbers.
You'd be surprised, it may not take quite that long. With something like Chisel you can create that idea with little friction. Plus, it's "just" Scala so if you already know that it's a lot easier than, say, learning Verilog which has a very different syntax.
A related topic (that the title initially made me think of) is fantasy consoles like the PICO-8, although those appear to commonly use high-level programming languages like Lua.
34 comments
[ 3.2 ms ] story [ 31.7 ms ] thread[0] https://github.com/JohnEarnest/Octo
[1] http://octojam.com
https://itch.io/jam/gbjam-8
It's a very different style of creative constraint compared to programming a specific virtual machine with limited RAM and not a multiplier or floating-point unit in sight.
It's a wysiwyg game maker, that produces actual Gameboy ROM files
One question: would a real CPU have a JL instruction or would that be implemented some other way, like a series of smaller instructions?
Similarly, on ARM:
Some processors do have a conditional jump that takes the registers to compare though, such as the PDP-10's bewildering array of skip instructions: Finally, it's also fairly common to have branch instructions that compare against 0, such as PDP-10: That said, off the top of my head, I don't know of any real processors with something equivalent to the fantasy CPU's JL, which combines a comparison between two registers with a jump to an arbitrary immediate.In RISC-V's case, you cannot jump to an arbitrary address conditionally at all, you have to use a branch instruction. B-type instructions have only a 12-bit immediate value (technically 13 bits with an implied 0 in the lowest order bit), which means it can only code for jumps up to +- 1024 words away (IIRC, this 13 bit value codes for a jump up to +- 2048 half-words away, but RISC-V instructions are full words, hence 1024... using the 16-bit extensions would change things I think).
RISC-V J-types have a 20 bit jump target addressing relative half-words giving you a range of 2^20 / (2 * 2) = +- 262144 full words. Alternative you could use JALR, which will let you jump to any 32-bit address, but then you would have potentially up to 4 instructions for a conditional jump (blt, luo, ori, jalr).
To my recollection, MIPS is similar but has a larger immediate field for J-types. I'm not familiar enough with other arches to be comment on them.
The way you would code for a conditional jump to a far away target would be to use a conditional branch, which either skips over or runs an unconditional jump. If using JALR as described, you might have something like:
To answer the question more concisely, something like JL would probably end up being an assembler pseudo-instruction. For example in RISC-V there is no BGE instruction, it compiles to a BLE with the operands swapped. This reduces overhead in the instruction encoding, and is equivalent.AMD64/IA32 are probably different, but I don't know. And of course you could always define a CPU architecture with something like "conditional jump to register value" (JALRLT for example). I don't know if anybody has though.
Source: https://riscv.org/technical/specifications/
Edit: in the time it too me to write this, somebody did the same thing for x86: https://news.ycombinator.com/item?id=24256275
Depending on the ISA [3][4][5], there can be jumps, branches, and subroutine transfers (or even a subset of those). Some can be based (conditional) on what value is in a primary register (often traditionally called the accumulator), a condition/flag/status register, a memory location, and even well known constants (0, 1, -1). Many ISAs have ways branching to a function/subroutine/procedure that can include adjusting a calling stack or transferring control to an OS kernel. x86 is notable in that you can cause a branch or jump without calling a branch or jump instruction.
[0] https://www.tutorialspoint.com/assembly_programming/assembly...
[1] https://thinkingeek.com/2013/01/19/arm-assembler-raspberry-p...
[2] https://www.ibm.com/developerworks/library/l-powasm3/index.h...
[3] https://en.wikipedia.org/wiki/Instruction_set_architecture
[4] https://en.wikipedia.org/wiki/Branch_(computer_science)
[5] https://en.wikipedia.org/wiki/Subroutine
I'm curious what you are referring to here. Are you talking about CMOV? Or INT/SYSCALL/SYSENTER? Or just the ability to trigger a fault handler? Or semantic games like push followed by ret?
If you want something more "real", MIPS and RISC-V are also not too hard, if you just do a subset of the instruction set and not the whole thing. You can skip most of the instructions that deal in bytes and half-words. It's also easier to implement main memory as being word-addressable only (but this can introduce incompatibilities with real assembler programs).
I wrote a cycle-accurate, pipeline simulator for MIPS for a course, which totaled only about 1200 SLOC. If you didn't need to simulate all the pipeline registers (you probably don't) and use a higher level language, this is probably a <750SLOC project.
0 - https://en.wikipedia.org/wiki/CHIP-8
It seems to be well regarded.
The most critical resource even if it is not super convenient is the official RISC-V specification [1] (this is the latest draft as of writing this comment).
There are a bunch of open source simulators [2] to look at for reference if you are unsure of how something is suppose to work. I would recommend compiling a C program to RISC-V and looking at disassembled machine code to get an idea for how things are normally done in terms of RISC-V assembly.
There isn't any particularly nice documentation that I am aware of, but enough universities teach using it that there should be some resources that would be helpful for making a simulator. I can probably come up with some links if you are serious about making a simulator, but its a little too much effort to do for a drive-by comment.
[0]: https://github.com/TheThirdOne/rars
[1]: https://github.com/riscv/riscv-isa-manual/releases/download/...
[2]: https://github.com/riscv/riscv-software-list#simulators
https://www.amazon.com/Hackers-Delight-2nd-Henry-Warren/dp/0...
This book will tell you how to efficiently generate condition flag values such as carry and overflow. It's easy when when you have extra bits available (such as emulating an 8 or 16 bit processor on a 32-bit processor), but less obvious when you are emulating a 32-bit processor on a 32-bit processor from a high level language.
As thethirdone said in his comment, the official specification is the most important resource.
The actual body of the simulator is mostly going to be a lot of lookup tables. You'll probably have a struct or class that stores the state of the CPU at a specific instant in time. Your main loop will be calling a next_state() function on that object. For your sanity, I would highly suggest making your state object immutable.
I have found breaking out the ALU, instruction decoder, and control logic into separate functions is a good way to split things apart.
If you're looking for the former, the canonical course is "NAND2Tetris". If you take the class on Coursera, the first (of two parts) covers the hardware.
I've also seen two very interesting books, but I haven't read them - "The ZX Spectrum Ula: How to Design a Microcomputer", and "Designing Video Game Hardware in Verilog".
Courses at this level are generally exactly defined.
In the second case - emulating at the ISA level, the canonical entry is to emulate a CHIP-8, since it's simple and relatively well defined.
At this level, targets are rarely exactly defined. CHIP-8 for example is well-defined in theory, however, for example, looking at the Wikipedia page won't suffice, because there are many extensions. Additionally, CHIP-8 has no standard clock speed. But it's easy to find the resources, and you can end with an exactly working system (the same can't be easily say, for example, of a NES or a C64 - you'll find conflicting information).
I personally think that MIPS is just not relevant enough in the real world anymore to deserve being taught at school. But I do get that there already is a lot of material out there.
I'd personally go with a risc-v subset at first and expand on it.
From a pedagogy perspective, the course materials / textbooks haven't really caught up yet. Also, the project I was referring to was in an Architecture class; I would contend that the underlying concepts of how a pipeline works can be taught pretty much any architecture. MIPS is easy to understand, which makes it well suited for teaching.
That said, at my university, we are slowly transitioning to RISC-V, so hopefully within a few years it will have propagated to all of the courses.
>>> 0 in JavaScript just doesn't cut it.
const a = new Uint8Array([0xFE])
const b = new Uint8Array([0x02])
a[0] = a[0] + b[0]
18 bit address bus
12 bit stack pointer
6 flags zero, negative, carry, overflow, float and user
7 addressing modes accumulator, immediate, absolute, relative, stack, stack without pop, stack special
64 opcodes like NOP, OR, JMP, ROL, STO, ...
I discovered that it is well possible to have a 6 bit CPU but of course this is just a game and probably not useful at all. I wonder whether I would be able to create the circuit for the CPU in a simulator. Probably not even if I omit the floating point handling or only if I dedicated about 10 years of free time. I would need to design a microcode or PLA (like MOS 6502, see https://news.ycombinator.com/item?id=5353198) system.
I find it amusing that this is the one CPU where octal base is really the primary way to display numbers.
[1] https://www.youtube.com/playlist?list=PLP29wDx6QmW5DdwpdwHCR...
https://github.com/paladin-t/fantasy