You are browsing as a guest. Sign up (or log in) to start making projects!

max_silly

@max_silly

Joined June 3rd, 2026

  • 41Devlogs
  • 4Projects
  • 3Ships
  • 46Votes
I'm just a silly human who likes Rust :3. Also a recovering Python user and Zig learner. I also don't know how to Δ my pfp.
Open comments for this post

2h 1m 31s logged

RAM or ROM

Hello everyone! I’ve finally logged a normal amount of time again! Let’s dive right in!

MBC Say One!

I managed to bang out my first supported MBC (memory bank controller), a lot of it done while walking on the treadmill this morning. I had already implemented the ROM bank switching by this morning, so next I had to implement switching RAM banks, which has some pretty funky indexing.

Grace Hopper

Naturally, what does one do when they have a hopefully working MBC system, you try a rom!. I only found out tetris doesn’t use an mbc after removing my basic mbc substitute stuff that enabled me to run simple roms.
Speaking of Tetris, the screenshots are me attempting to run tetris iwth different themes (the themes work, tetris doesn’t which we’ll get to).
The themes in order

  • Wisteria
  • GBGRreen
  • Jungle
  • Developer
    (Don’t worry, there’s more themes, and you can always submit an issue or PR with a new theme)

Let’s get back to BUGSSSS!! When I tried to run tetris, it wasn’t accepting input and the arrow showing which options are selected was unpresent. These issues are the next things to resolve.

  1. I found that there was a critical error in the read methods on my button data stuct
    Here is the commit with the changes in case you are curious.

  2. I found out that there is a spot in memory you can write to to load a sprite from some address into OAM. This is the next thing I will be implementing before I try finding the next issue with input so I can at least see some pretty sprites.

Anyway, see y’all tomorrow after I hopefully get tetris working!

0
0
32
Open comments for this post

3h 17m 3s logged

You can switch the banks, switch banks, swi- swi- swi- swi- swi- — until you’re full of life

Hello everyone! I changed the name of my project directory, so I wasn’t seeing my hackatime or had the ability to write a devlog until I realised the problem, so let’s just dive right in!

Bank It

The next thing to deal with was bank switching and better behaviour for handling roms.

Cartridges are physical objects, and thus can have extra RAM and storage for the gameboy. These are controlled by MBCs (memory bank controllers). The most common ones are MBC1, MBC2, MBC3, and MBC5. For this project, for now, I am only emulating 1, 2, and 3. In the future, I will probably add more controllers once the full architecture is done for at least these three so I have an idea of hwo the skeletons should look.

The first step of bank switching was having the rom portion of the memory be read-only and writes to it do certain mbc actions, like enable ram or switch banks or switch ram. I am currently implementing the switching of banks. I need to get back to MBC1-specific flags and writes. See y’all next time!

0
0
19
Open comments for this post

7h 48m 37s logged

Human Error

Hello everyone! I got too much in the flow:3-blahaj-spinning: and in the RAGE, which is why I logged nearly 8 hours since the last devlog! Let’s get in the weeds!

Loading Bay

I added ROM loading!!!!! :3-pet-gabe: I can actually load a test ROM in now!!! Naturally, I immediately loaded one in, and that brings us to our first issue.

Speed? Issues

When I was loading in a test ROM, the text of the name of the test would peek from the bottom of the screen, then jump up and not show passed. I probably spent 2 HOURS on trying to fix this. I thought it was a speed issue, and I ended up making a ton of optimisations and added alternate screen switching.

BUT GUESS WHAT??? I zoomed out my terminal window, and there it was: Passed. I would share a screenshot, but I am currently working on changes to load_rom() that break the emulator currently (bank switching).

Don’t Stop

I mentioned a while ago that I had to emulate a bug in the hardware that caused the next instruction to occur twice if HALT is called when there is an interrupt pending and IME (interrupt master enable) is off. My implementation caused it to infinitely stall instead. So I fixed that bit, but the HALT bug test rom still wasn’t running the tests, so I found that LY couldn’t be read from memory, so fixing that fixed the thing, allowing the tests to run and FAIL. So I had to run a disassembler on the HALT bug test to find the exact mechanics of the HALT bug, then write it. Even though the values for the result were correct, it still wouldn’t pass. This was because of an oversight on my part when I was writing the interrupt handling. The IF byte (0xff0f, pending interrupts, whether or not they’re enabled) in hardware always has its highest 3 bits as 1, and I didn’t think it was necesary to implement this when I was writing interrupt handling, so I ignored it. And finally, fixing that, the HALT bug worked.

The screenshots are:
Gambatte failing the test.
mGBA passing.
RustBoy (May change to GameGirl/RustGirl for uniqueness and searchability) passing.

See y’all next time!

0
0
8
Open comments for this post

4h 58m 15s logged

You’ve Been Selected for a “Random” Screening

Hello Everyone! We are back! I don’t know how it’s already been over 4½ hours! Let’s dive straight into the progress!

They Know How to Push Each Other’s Buttons

After making constructors for the structs and refactoring the button data struct, I added a loop to handle all waiting button presses and set the data in the button data struct. Not particularly complicated, though it was fun to test something that actually did something.

Staaaaahhp

Instructions take a set amount of time, so it is necessary to have a pause, since even a TI-84 (buy the CG-50 or HP Prime G2 instead; don’t get the CG-100, it lacks plugin support, which is why I recommend the CG-50 at all) is faster. Since systems can’t pause for a single tick, it pauses frame-by-frame instead.

How to Exit Neovim

I didn’t take the time to add a break condition, so I added a check to see if the escape key had been pressed, and if it had, it quit the application.

Show Me the Money

The biggest development was the render loop. Now, there is actually a visual to see and play with.

Paint a Pretty Picture

I also felt it would be boring if there was only one colour theme, so I added an enum along with PixelValue => Colour matching so that you can pick a theme! Speaking of picking…

Staring at a Config File

Since the user has to be able to pick a theme, I felt it would be good to have config.toml support for ease of use.

0
0
3
Open comments for this post

5h 35m 6s logged

Building Buttons

Hi everyone!! It seems that time has slipped away from me yet again, so let’s dive straight into your scheduled content!

Pushy Pushy Buttons

After dealing with sprites (oh! the horror! the suffering! the despair!), it was time to deal with a new kind of beast: the joypad. Implementing joypad functionality meant it was time to make a frontend (ew) instead of just building subsystems and cobbling them together later.

This meant it was time to learn stuff about the crossterm crate and make some constructor functions.

The screenshot is my current struct for storing the button push data. It used to be 8 bools, but that was 7 bytes too many, so it’s just a u8 wrapper now.

Cross-multiply Those Terms!

Crossterm is a crate for dealing with terminal keyboard interrupt and such. The first and only thing I’ve done so far was make a wrapper with a custom destructor for Keyboard enhancement flags.

struct TerminalGuard {
    enhancement_pushed: bool,
}

impl TerminalGuard {
    fn new() -> std::io::Result<Self> {
        enable_raw_mode()?;

        let enhancement_pushed = supports_keyboard_enhancement().unwrap_or(false);
        if enhancement_pushed {
            execute!(
                stdout(),
                PushKeyboardEnhancementFlags(KeyboardEnhancementFlags::REPORT_EVENT_TYPES)
            )?;
        }

        Ok(Self { enhancement_pushed })
    }
}

impl Drop for TerminalGuard {
    fn drop(&mut self) {
        if self.enhancement_pushed {
            let _ = execute!(stdout(), PopKeyboardEnhancementFlags);
        }
        let _ = disable_raw_mode();
    }
}

Computah, Make me a New Computah

Since it was time to write a main() fn, it meant I also had to write constructor functions for CPU and all it’s subsidiaries, since I’ve not bothered to do it before.

That’s it folks! See y’all next time!

2
0
20
Open comments for this post

2h 41m 16s logged

Crack Open a Sprite

Hello everyone! This dvelopment took longer than expected, so let’s just dive right in!

County Rogues, Bring me lOAM

After I finished the window layer, it was time to deal with sprites. Where each sprite is on the screen, which tiles to use, and sprite-specific render attributes are stored in a section of memory called OAM. 40 sprites’ data can be stored in OAM, each 4 bytes: the x position, y position, which tile(s) to use, and the attributes of that sprite (height, when to render, etc.)

Unlike the background and window, sprites have a lot more moving parts. Here’s my code for rendering the sprites to the buffer.

    fn render_sprites(&mut self) {
        let lcdc = self.read_byte(0xff40);
        if lcdc >> 1 & 0x1 == 0 {
            return;
        }
        let mut indexes = (0..40).collect::<Vec<u16>>();
        indexes.sort_unstable_by_key(|n| (self.read_byte(0xfe01 + n * 4) as i16 - 8, *n));
        let indexes: Vec<&u16> = indexes.iter().rev().collect();
        let height = if lcdc >> 2 & 0x1 == 0 { 8 } else { 16 };

        for n in indexes {
            let entry = 0xfe00 + n * 4;
            let sprite_y = self.read_byte(entry) as i16 - 16;
            let sprite_x = self.read_byte(entry + 0x1) as i16 - 8;
            let tile_index = self.read_byte(entry + 0x2);
            let attributes = self.read_byte(entry + 0x3);
            let palette = if (attributes >> 4) & 0x1 == 0 {
                0xff48
            } else {
                0xff49
            };

            for row in sprite_y.clamp(0, 144) as u8..(sprite_y + height).clamp(0, 144) as u8 {
                for column in sprite_x.clamp(0, 160) as u8..(sprite_x + 8).clamp(0, 160) as u8 {
                    if (attributes >> 7) & 0x1 == 1 && self.bit_7_check(row, column) {
                        continue;
                    }

                    let row_in_sprite = if (attributes >> 6) & 0x1 == 0 {
                        (row as i16 - sprite_y) as usize
                    } else {
                        height as usize - (row as i16 - sprite_y) as usize - 1
                    };

                    let column_in_sprite = if (attributes >> 5) & 0x1 == 0 {
                        (column as i16 - sprite_x) as usize
                    } else {
                        7 - (column as i16 - sprite_x) as usize
                    };

                    let which_tile = if row_in_sprite < 8 { (0, 0) } else { (1, 8) };

                    let pixel = self.index_to_tile_unsigned(tile_index + which_tile.0)
                        [row_in_sprite - which_tile.1][column_in_sprite];
                    if pixel == TilePixelValue::Zero {
                        continue;
                    }
                    let value = self.palette_lookup(pixel, palette);
                    self.gpu.set_pixel(row, column, value);
                }
            }
        }
    }

    fn bit_7_check(&self, row: u8, column: u8) -> bool {
        if self.window_active_for_pixel(column, row) {
            if self.window_pixel_value(column, row) == TilePixelValue::Zero {
                return false;
            }
            return true;
        }
        if self.background_pixel_value(column, row) == TilePixelValue::Zero {
            return false;
        }
        true
    }

This also excludes any boilerplate for window and background that I had to reuse. The most annoying part was dealing with ordering indexes efficiently, because the x value of the sprite is used as the method for sprite precedence over layers. I didn’t realise that sort_unstable_by_key dealt with like tuples and you could use that for precedence and tiebreaking.

Anyway, that’s all folks! see y’all next time!

0
0
6
Open comments for this post

1h 54m 14s logged

Ogres are like Onions

Hello everyone! This time, I’ve fortunately logged a normal amount of time. Let’s dive in!

Close the Window!

After the background rendering was complete, it was time to work on the next layer: the window. The window is exactly as it sounds — the layer between sprites and the background that can have ui and stuff.

The window starts at a position dictated by wx and wy (0xff4b and 0xff4a in memory), and those two dictate the top-left corner of the window layer (though wx - 7 is the actual top-left x coord). If a game wants to have a window layer that ends, it will switch off the window layer BETWEEN scanlines, which my current implementation cannot do because I render the whole frame at VBlank (after all scanlines) for simplicity, and it’s not strictly necessary.

The code implementation of the window layer was relatively similar to background:

    fn window_active_for_pixel(&self, screen_x: u8, screen_y: u8) -> bool {
        if (self.read_byte(0xff40) >> 5) & 0x1 == 0 {
            return false;
        }
        let wy = self.read_byte(0xff4a);
        let wx = self.read_byte(0xff4b);
        if screen_y >= wy && screen_x >= wx.saturating_sub(7) {
            return true;
        }
        false
    }

    fn window_pixel_value(&self, screen_x: u8, screen_y: u8) -> TilePixelValue {
        let window_y = screen_y - self.read_byte(0xff4a);
        let window_x = screen_x + 7 - self.read_byte(0xff4b);
        let window_tile_map_index = (window_y / 8) as u16 * 32 + (window_x / 8) as u16;
        let window_tile_map_base = if (self.read_byte(0xff40) >> 6) & 0x1 == 0 {
            0x9800
        } else {
            0x9c00
        };

        let tile =
            &self.index_to_tile(self.read_byte(window_tile_map_base + window_tile_map_index));
        tile[(window_y % 8) as usize][(window_x % 8) as usize]
    }

The next step is the final layer: Sprites! Sprites will be more involved and unique compared to background and window. I still can’t believe the Sharp SM83 did all this for developers. See y’all next time!

0
0
51
Open comments for this post

4h 53m 39s logged

No More GPU Crisis

Hello everyone! It seems that, yet again, time has slipped away from me! Let’s dive back in!

STATistics

When the chip swaps rendering modes, depending on whether the STAT register enables that specific interrupt, it will trigger a STAT interrupt (priority level 2). I had to set up a loop to shift modes so that each mode can easily be fully handled (see screenshot). This was just a matter of finding out how long each mode and line lasted, relatively simple.

Render Engines

After this, I had to start adding rendering features. There are three components to the rendering:

  • Background
  • Window
  • Sprites
    The first, and easiest part, was the background. Which tiles compose the background is dictated by a series of bytes after either 0x9800 or 0x9c00, which have references to tiles in memory. Pretty simple.
    fn render_to_buffer(&mut self) {
        for row in 0..144 {
            for column in 0..160 {
                self.gpu.set_pixel(
                    row,
                    column,
                    self.bgp_palette_lookup(self.pixel_value(column, row)),
                );
            }
        }
    }

    fn pixel_value(&self, screen_x: u8, screen_y: u8) -> TilePixelValue {
        let map_y = self.read_byte(0xff42).wrapping_add(screen_y);
        let map_x = self.read_byte(0xff43).wrapping_add(screen_x);
        let tile_map_index = (map_y / 8) as u16 * 32 + (map_x / 8) as u16;
        let tile_map_base = if (self.read_byte(0xff40) >> 3) & 0x1 != 0 {
            0x9c00
        } else {
            0x9800
        };

        let tile: &Tile = &self.index_to_tile(self.read_byte(tile_map_base + tile_map_index));
        tile[(map_y % 8) as usize][(map_x % 8) as usize]
    }

    fn index_to_tile(&self, index: u8) -> Tile {
        let unsigned = (self.read_byte(0xff40) >> 4) & 0x1 != 0;
        if unsigned {
            return self.gpu.tile_set[index as usize];
        }
        let signed_index = (256 + (index as i8 as i16)) as usize;
        self.gpu.tile_set[signed_index]
    }

    fn bgp_palette_lookup(&self, value: TilePixelValue) -> PixelValue {
        let index = value as u8;
        match (self.read_byte(0xff47) >> (index * 2)) & 0b11 {
            0 => PixelValue::White,
            1 => PixelValue::LightGray,
            2 => PixelValue::DarkGray,
            3 => PixelValue::Black,
            _ => panic!("Unknown shade"),
        }
    }

The next step is the window, then the sprites. Sprites will be hard due to their varied size. See y’all next time!

0
0
4
Open comments for this post

2h 0m 27s logged

Div(isions of time)

Hello everyone! This time, I actually remembered to make a devlog! Let’s dive in!

Div(vy up Labour)

Remember how I added the number of ticks each instruction took as a return value from the execute() fn? Well, it actually got put to use now! There is an internal 16-bit timer in the Sharp SM83, the upper 8 bits of which are exposed at memory address 0xff04. Programming this was pretty easy once I realised I could have read_byte() and write_byte() directly access the counter (Originally, I was going to have an update_div thingy probably and have the bte in memory and the accumulator be separate rather than this simpler approach).

    pub fn read_byte(&self, address: u16) -> u8 {
        let address = address as usize;
        match address {
            VRAM_BEGIN..=VRAM_END => self.gpu.read_vram(address - VRAM_BEGIN),
            0xff04 => (self.div_accumulator >> 8) as u8,
            _ => self.memory[address],
        }
    }

    pub fn write_byte(&mut self, address: u16, byte: u8) {
        let address = address as usize;
        match address {
            VRAM_BEGIN..=VRAM_END => self.gpu.write_vram(address - VRAM_BEGIN, byte),
            // Any write to div resets it and its accumulator
            0xff04 => self.div_accumulator = 0,
            _ => self.memory[address] = byte,
        };
    }

Now, last time, I set up interrupts, so with div set up, it was time to set up timer interrupts.

TIMA Time

What actually triggers timer interrupts, rather than div, is a separate timer called TIMA (0xff05 in memory). TIMA has settings set in TAC (0xff07 in memory). Bits 0 and 1 of TAC dictate which bit of the DIV accumulator (let’s call it divacc) to watch to see when TIMA should be incremented. Bit 2 dictates whether TIMA should be incremented at all.

TIMA gets incremented whenever the indicated bit of divacc falls from 1 → 0. This can sometimes be as few as every 16 ticks, and since instructions can take up to 24 ticks, this can cause the bit to go from 1 → 0 → 1, which means that you can’t just check the new divacc against the old one to see whether the bit fell, though I tried this as a temporary solution. This is what I had to do instead (see screenshot). I wish it could be prettier, but what can you do?

Anyways, y’all, see you next time!

0
0
5
Open comments for this post

2h 45m 34s logged

Interrupting Cow

Hello everyone! This took a lot longer than I expected due to a large amount of me getting confused by how interrupts work. Let’s dive in!

Halt!

After dealing with tick returns, the next step was dealing with interrupts. There are two bytes in memory that concern interrupts. One is IE (0xffff), which tells which interrupts to listen for, and the other is IF (0xff0f), which tells which interrupts are currently pending. Dealing with these was easy enough, but there was another thing that needed addressing:

The Halt Bug

The halt bug is a known bug that, should Halt be ran when there are interrupts pending and the IME (interrupt master enable I think) is off, it doesn’t halt and the next instruction runs twice. I was both confused by how this behaved and how it was triggered, which caused issues with my code that had to be addressed.

I wasn’t able to deal with anything besides interrupt handling, so that’s all for now!

0
0
9
Open comments for this post

3h 44m 20s logged

Ticking Time Bomb

Hello everyone! It seems that, yet again, I have forgotten to make a devlog for a few hours, so let’s dive in! Fortunately, not much has happened.

16 GB of VRAM

As I said in my last devlog, it was time to get started on graphical stuff. I set up a GPU struct with a separate VRAM, and set up the memory to write to that VRAM instead of the regular bus when it’s supposed to.

This was also the last part of the docs I’ve been following, so I’ve been on my own since this point. It’s a little pesky that my best resources on how the GB works are now Claude Code and a random opcode table, but what can you do?

Tick-Tock Goes the Clock

Different operations in the GB’s processor take different numbers of clock cycles to do, so I had to add this as a second return value to execute to add slowing in the future. This was the majority of the hours I logged, staring at the opcode table and trying to figure out the patterns in the timings.

Anyway, see y’all next time! These are some silly things that will probably land for at least some of you.

0
0
7
Open comments for this post

1h 18m 56s logged

Where are my keys?

Hello everyone! We are back with a little news, so let’s dive right in!

Hashmap, was not, in fact, the solution

The mapping from opcode to instruction enum (fn from_byte()) is finally done. Not much to say except that it was a very tedious process. While working, I realised I hadn’t implemented ADD SP, i8 (addassign a signed integer to the stack pointer) yet, so I quickly added that.

Unfortunately, that’s all folks! Next, I’m going to start dealing with graphics.

0
0
51
Open comments for this post

1h 54m 50s logged

Miscellany

Hello everyone! Let’s keep this quick because I have much tedium to get through.

What the Hecc is JR?!

I finished most of instructions, so I needed to figure out which ones I forgot. I was looking at the opcode table and I realised I had no idea what JR or DAA did, so I had to figure out what they did. JR is just a relative jump rather than an absolute one, and DAA does some number formatting after an arithmetic op for better display.

Let’s Table this Discussion

Since the instructions that I can implement right now (very nearly all of them, except for stop and interrupts, etc.), it’s been time to update the Instruction::from_byte() fn with all the new instructions. I’m still doing this, and it is purely looking at the opcode table, entering something, looking at the opcode table again, looking at what I wrote, and repeating this over and over and over again. I’m not even done with this!

Anyway, enjoy a hashmap meme that doesn’t apply to me b/c I am using a match

0
0
7
Open comments for this post

1h 38m 33s logged

Pushpops

Hello everyone! This time, I actually logged a normal amount of time! Let’s dive in!

Loading Shipments

I finally finished up the LD instruction! once I got through the weird targets, everything was smooth sailing.

Stack Overflow

Once LD was done, it was time to implement the stack. If you don’t know what (a/the) stack is, reference the wikipedia screenshot. For the GB, the two methods are PUSH and POP, and the stack actually goes down in memory from the end of the memory bus rather than from the beginning.

Dear Functional Bros

When I finished implementing the stack, which I though was going to be a lot harder, it was time to add functions to it. This was pretty simple: a CALL instr. that jumps to a point and puts the place where it left off on the stack and a RET instr. that goes to the last location on the stack.

0
0
46
Open comments for this post

3h 14m 27s logged

Loading Jump Time

Hello everyone! I lost track of time, and it appears I have once more logged too many hours with no devlog, yet no excuses I have. Let’s get right to it!

HL and D8

A lot of Instructions that I had already made have an HL target, in which they use the value in the joint register HL as an address in memory, and perform the operation on that byte. Up until this point, all HL targets were todos, but, since I had created the memory bus, I was able to quickly wire up those targets. A lot of those same instructions could also take the next byte in memory as the input (D8) so I wired those up too (though before HL). I actually wasn’t sure whether HL targets used a different memory bus than the one used for operations.

JP Morgan

The next instruction to deal with was JP (jump). Based on different conditions (the state of the zero flag or carry flag, or always) it would or wouldn’t jump to the address encoded over the next two bytes. Probably one of the easier instructions I’ve made.

Loading…

After JP was load. LD just copies data from one place to another. There are so many variations that I can’t talk about all of them, and there my code is too long for this devlog, so:

Byte: Load a byte from point a to point b
Word: just like the Byte type except with 16-bit valuesAFromIndirect: load the A register with the contents from a value from a memory location whose address is stored in some locationIndirectFromA: load a memory location whose address is stored in some location with the contents of the A registerAFromByteAddress: Just like AFromIndirect except the memory address is some address in the very last byte of memory.ByteAddressFromA: Just like IndirectFromA except the memory address is some address in the very last byte of memory.

This is the opcode table I’ve been using

0
0
44
Open comments for this post

4h 6m 54s logged

Timeliness

Rather unfortunately, a lot of work has been done since the last devlog due to yesterday’s hackatime outage.

Let’s get started!

House of Memories

As I mentioned in the last devlog, the next step was to create a program counter (pc, marks where in the program we are) and the memory bus (with all the instructions we have been emulating for the GB to run, and others)

Bite the Code

The instructions are not stored as plain text of enums, they are stored as bytes, one byte for each combination of instruction and target. I thus have to map each bytecode to each instruction that I have added so far.

Not English Class! Anything but English!

There are much more than 256 different bytecodes; there are actually nearly 512. To accomplish this and still have memory be 1 byte, there is a bytecode prefix, 0xcb, which tells the chip that the real instruction is the next one, and to look it up on a different table.

PCMR

To go through each instruction, I added a step function:

    fn step(&mut self) {
        let (instruction_byte, prefixed) = {
            let temp_byte = self.bus.read_byte(self.pc);
            if temp_byte == 0xcb {
                (self.bus.read_byte(self.pc + 1), true)
            } else {
                (temp_byte, false)
            }
        };

        let next_pc = if let Some(instruction) = Instruction::from_byte(instruction_byte, prefixed)
        {
            self.execute(instruction)
        } else {
            panic!("Unknown instruction found for: 0x{:x}", instruction_byte);
        };

        self.pc = next_pc;
    }

As you can see, execute returns the next pc value, something it didn’t do before. So, I had to go and make all the match arms blocks that do their associated action and also return self.pc.wrapping_add(1) for non-0xcb functions and self.pc.wrapping_add(2) for 0xcb functions, since no function that modifies the pc has been added yet.

Next is added yet more instructions! See y’all next time!

0
0
21
Open comments for this post

2h 20m 41s logged

Registering New Data

Hello Everyone! You know what they say: mo’ code mo’ devlogs.

To the right, to the right, to the right, the right, the right

I finished up all the instructions that act on register data!!! Specifically, these ones:

ADD - Add the value of the target register to the A register.
ADDHL (add to HL) - just like ADD except that the target is added to the HL register
ADC (add with carry) - just like ADD except that the value of the carry flag is also added to the number
SUB (subtract) - subtract the value stored in a specific register with the value in the A register
SBC (subtract with carry) - just like ADD except that the value of the carry flag is also subtracted from the number
AND (logical and) - do a bitwise and on the value in a specific register and the value in the A register
OR (logical or) - do a bitwise or on the value in a specific register and the value in the A register
XOR (logical xor) - do a bitwise xor on the value in a specific register and the value in the A register
CP (compare) - just like SUB except the result of the subtraction is not stored back into A
INC (increment) - increment the value in a specific register by 1
DEC (decrement) - decrement the value in a specific register by 1
CCF (complement carry flag) - toggle the value of the carry flag
SCF (set carry flag) - set the carry flag to true
RRA (rotate right A register) - bit rotate A register right through the carry flag
RLA (rotate left A register) - bit rotate A register left through the carry flag
RRCA (rotate right A register) - bit rotate A register right (not through the carry flag)
RLCA (rotate left A register) - bit rotate A register left (not through the carry flag)
CPL (complement) - toggle every bit of the A registerBIT (bit test) - test to see if a specific bit of a specific register is set
RES (bit reset) - set a specific bit of a specific register to 0
SET (bit set) - set a specific bit of a specific register to 1
SRL (shift right logical) - bit shift a specific register right by 1
RR (rotate right) - bit rotate a specific register right by 1 through the carry flag
RL (rotate left) - bit rotate a specific register left by 1 through the carry flag
RRC (rorate right) - bit rotate a specific register right by 1 (not through the carry flag)
RLC (rorate left) - bit rotate a specific register left by 1 (not through the carry flag)
SRA (shift right arithmetic) - arithmetic shift a specific register right by 1
SLA (shift left arithmetic) - arithmetic shift a specific register left by 1
SWAP (swap nibbles) - switch upper and lower nibble of a specific register

Some of the bitwise ops for rotation were a little difficult, but I was able to troubleshoot.

Next is the game ROM, the program counter, prefix instructions, and jump instructions!

0
0
29
Open comments for this post

1h 35m 33s logged

Untargeted Improvements

Hello everyone! New day, new devlog!

I added some new instructions!
CCF — Clear Carry Flag
SCF — Set Carry Flag
RRA — Rotate Right A
RLA — Rotate Left A
RRCA — Rotate Right Circular A
RLCA — Rotate Left Circular A
CPL — Complement Bits of A Register

Figuring out some of the bitwise logic for the rotation command, especially RLA (and because I was working in the car) was kind of difficult.

    fn rra(&mut self) -> u8 {
        let carry = if self.registers.f.carry { 1 } else { 0 };
        let value = self.registers.a;
        self.registers.f.zero = false;
        self.registers.f.subtract = false;
        self.registers.f.carry = value & 0x1 == 1;
        self.registers.f.half_carry = false;
        (value >> 1) | (carry << 7)
    }

    fn rla(&mut self) -> u8 {
        let carry = if self.registers.f.carry { 1 } else { 0 };
        let value = self.registers.a;
        self.registers.f.zero = false;
        self.registers.f.subtract = false;
        self.registers.f.carry = (value >> 7) & 0x1 == 1;
        // Originally I was doing the bit isolation before the bitshift,
        // and I forgot about the bitshift initially,
        // so I was just doing a bit isolation and then clippy complained b/c it would never be true.
        self.registers.f.half_carry = false;
        (value << 1) | carry
    }

Next are BIT, RES, and SET, and then more instructions!

The image is a question I saw on Protobowl a few months back.

0
0
71
Open comments for this post

2h 20m 29s logged

Target Practise

Hello everyone! We’re back with another devlog!

Instructor

I managed to finish up all the instructions (minus the HL sections of those because that uses the value in the combined HL register as a pointer to memory, which I did not yet add) that were already in the enum (screenshot from last devlog). I also just added the INC and DEC instructions, which increment or decrement the value in the target register or byte in memory pointed to by HL.

It was a bit of a hassle to figure out how I should do the target enums, since there is an 8 and 16 bit version of each INC and DEC, each with different targets. INC8 can target the byte of memory pointed to by HL, whereas INC16 can point to HL itself and the other 3 16 bit registers.

See y’all next time!

0
0
12
Open comments for this post

1h 43m 22s logged

We’ve Brought Some New Additions

After finishing up the LZU and its associated rating, it’s time to get back to work on this project!

There are no Antarctic Pyramids

Ever since I heard about the wonderful site that is Codeberg, I’ve wanted to swap to there, but the thing stopping me was this challenge, because I thought it only worked with GitHub, but when I was rating projects, I found that one of them was on Codeberg. Because of that, I swapped over this repo to Codeberg, which it made very easy, and continued on my coding journey.

Yesop

Like I said in my last devlog, I have to implement the instructions for the CPU in code! I managed to create most of the implementations for AddHL and Adc, only stopped by the fact that I still need to emulate memory. The next step is to finish Sub and the rest of the ops in this enum, then the rest of the instructions!

See y’all next time! :3-blahaj-spinning:

0
0
44
Loading more…

Followers

Loading…