7 ms·
Implement an emulator for a fantasy CPU in JavaScript
- mikestaszel 6y agoIf you’re looking for a good next step I recommend CHIP-8.
- RodgerTheGreat 6y agoAnd if you've written a CHIP-8 interpreter, I recommend playing around with Octo[0] and then participating in the Octojam[1] this October! [0] https://github.com/JohnEarnest/Octo https://github.com/JohnEarnest/Octo [1] http://octojam.com http://octojam.com
- eatonphil 6y agoTangent but much larger audience is the original GB game jam coming up next week. https://itch.io/jam/gbjam-8 https://itch.io/jam/gbjam-8
- RodgerTheGreat 6y agoThe 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.
- eatonphil 6y agoGotcha, 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.
- djxfade 6y agoShameless plug, but if you are going to participate in this game jam, may I recommend checking out GB Studio (https://www.gbstudio.dev/ https://www.gbstudio.dev/). It's a wysiwyg game maker, that produces actual Gameboy ROM files
- Hnrobert42 6y agoReading through that was a great refresher. Thanks. One question: would a real CPU have a JL instruction or would that be implemented some other way, like a series of smaller instructions?
- thequux 6y agoIt 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.
- charlesdaniels 6y agoThat depends on the CPU. 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. Source: https://riscv.org/technical/specifications/ 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 https://news.ycombinator.com/item?id=24256275
- charlesdaniels 6y agoNice 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. 0 - https://en.wikipedia.org/wiki/CHIP-8 https://en.wikipedia.org/wiki/CHIP-8
- triyambakam 6y agoDo you have any suggested resources for learning to implement a CPU emulator, such as for RISC-V?
- cptnapalm 6y agoI just started doing a Chip-8 after finding this: http://www.multigesture.net/articles/how-to-write-an-emulator-chip-8-interpreter/ http://www.multigesture.net/articles/how-to-write-an-emulato... It seems to be well regarded.
- thethirdone 6y agoI'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. [0]: https://github.com/TheThirdOne/rars https://github.com/TheThirdOne/rars [1]: https://github.com/riscv/riscv-isa-manual/releases/download/draft-20200727-8088ba4/riscv-spec.pdf https://github.com/riscv/riscv-isa-manual/releases/download/... [2]: https://github.com/riscv/riscv-software-list#simulators https://github.com/riscv/riscv-software-list#simulators
- MuffinFlavored 6y agoI'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. >>> 0 in JavaScript just doesn't cut it.
- dnautics 6y agoYou can use typedarrays, no? I built a fantasy emulator several years ago, and that was my strategy.
- MuffinFlavored 6y agolike this? const a = new Uint8Array([0xFE]) const b = new Uint8Array([0x02]) a[0] = a[0] + b[0]
- dnautics 6y agoyes, although IIRC what I did was more like this: const r_idx = {ax: 0, bx: 1...} const registers = new Uint8Array([...]) add = (r1, r2) => registers[r_idx[r1]] = registers[r_idx[r1]] + registers[r_idx[r2]] which lets you do: add("ax", "bx") which is one step closer to making it look a lot like asm syntax.
- Jasper_ 6y agoI've written quite a number of emulators and file format parses in JavaScript. What was missing for you?
- _nalply 6y agoMany 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 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.
- tn1 6y agoYou'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.
- vorticalbox 6y agoLow level javascript [1] has a great set of videos on this and other topics [1] https://www.youtube.com/playlist?list=PLP29wDx6QmW5DdwpdwHCRJsEubS5NrQ9b https://www.youtube.com/playlist?list=PLP29wDx6QmW5DdwpdwHCR...
- re 6y agoA 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. https://github.com/paladin-t/fantasy https://github.com/paladin-t/fantasy
- pnonplussed 6y agookok
- pnonplussed 6y agoasdf
- pnonplussed 6y agoasdfasdfas
- gent1010 6y agocbx,vnbjk