RAM initialization

By fcoury

Rookie (18)

fcoury's picture

10-04-2023, 00:19

I am writing an MSX1 emulator and I am a bit confused about how to initialize RAM memory. I am trying to learn from openMSX behavior but it's a bit tricky.

I have the following configuration on openMSX:

      <primary slot="0">
        <ROM id="Temporary Machine">
          <mem base="0x0000" size="0x8000" />
          <rom>
            <sha1>2f997e8a57528518c82ab3693fdae243dbbcc508</sha1>
            <filename>/Users/fcoury/code/msx_emulator/cbios_main_msx1.rom</filename>
          </rom>
        </ROM>
      </primary>

      <primary external="true" slot="1" />

      <primary external="true" slot="2" />

      <primary slot="3">
        <RAM id="Main RAM">
          <mem base="0x0000" size="0x10000" />
        </RAM>
      </primary>

And on my emulator:

Slot #0: ROM path=Some("/Users/fcoury/code/msx_emulator/cbios_main_msx1.rom") base=0x0000 size=0x8000
Slot #1: Empty
Slot #2: Empty
Slot #3: RAM base=0x0000 size=0xFFFF

The primary slot config starts as 0 and so far my emulator has the same behavior:

Primary Slot Config: 00000000

Segment 0: 0x0000 - 0xFFFF - base: 0x0000 - (slot 0)

Memory page 0 (0x0000 - 0x3FFF): primary slot 0 (ROM path=Some("/Users/fcoury/code/msx_emulator/cbios_main_msx1.rom") base=0x0000 size=0x8000)
Memory page 1 (0x4000 - 0x7FFF): primary slot 0 (ROM path=Some("/Users/fcoury/code/msx_emulator/cbios_main_msx1.rom") base=0x0000 size=0x8000)
Memory page 2 (0x8000 - 0xBFFF): primary slot 0 (ROM path=Some("/Users/fcoury/code/msx_emulator/cbios_main_msx1.rom") base=0x0000 size=0x8000)
Memory page 3 (0xC000 - 0xFFFF): primary slot 0 (ROM path=Some("/Users/fcoury/code/msx_emulator/cbios_main_msx1.rom") base=0x0000 size=0x8000)

As openMSX:

At some later point, the value 0xF0 is written to the PPI's port 0xA8:

2023-04-09T22:08:00.578123Z  INFO msx::ppi: [PPI] [WR] [PrimarySlot] [A8] = F0

And the layout changes:

And the same happens on my emulator:

Primary Slot Config: 11110000

Segment 0: 0x0000 - 0x7FFF - base: 0x0000 - (slot 0)
Segment 1: 0x8000 - 0xFFFF - base: 0x0000 - (slot 3)

Memory page 0 (0x0000 - 0x3FFF): primary slot 0 (ROM path=Some("/Users/fcoury/code/msx_emulator/cbios_main_msx1.rom") base=0x0000 size=0x8000)
Memory page 1 (0x4000 - 0x7FFF): primary slot 0 (ROM path=Some("/Users/fcoury/code/msx_emulator/cbios_main_msx1.rom") base=0x0000 size=0x8000)
Memory page 2 (0x8000 - 0xBFFF): primary slot 3 (RAM base=0x0000 size=0xFFFF)
Memory page 3 (0xC000 - 0xFFFF): primary slot 3 (RAM base=0x0000 size=0xFFFF)

Now the tricky part is that after this layout change, the memory contents I see on openMSX is completely different.

For openMSX:

And for my emulator:

ffb0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff  ................
ffc0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff  ................
ffd0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff  ................
ffe0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff  ................
fff0: ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff ff  ................

Is this because of the default memory map defined here: The Memory - MSX Wiki?

I so, does anyone know the logic I should employ to the RAM type slot? Are there any good documents out there about it?

Thank you!

Login or register to post comments

By Manuel

Ascended (19678)

Manuel's picture

10-04-2023, 01:07

The BIOS is supposed to set up all that stuff in the RAM...

By fcoury

Rookie (18)

fcoury's picture

10-04-2023, 01:28

Manuel wrote:

The BIOS is supposed to set up all that stuff in the RAM...

I am sure my Z80 code that's to blame. I'm going to look into it closely.

Thank you!

By wouter_

Hero (535)

wouter_'s picture

10-04-2023, 09:32

Hi,

Like Manuel already said: the BIOS will initialize the RAM. The emulator itself doesn't need to know anything about the specific meaning of memory regions (like the memory layout image you showed).

To locate where the memory gets initialized (in openMSX) you can set a watchpoint. E.g. to see when memory location 0xFFD0 gets initialized (to 0xC9) you can execute:
debug set_watchpoint write_mem 0xffd0
When this watchpoint hits, execution will break. Then in the debugger you can see where in the code the initialization gets done (it's via an LDIR instruction).

Note that in openMSX, just like on most real machines, when you reset the machine, the RAM contents gets preserved. So it may look as if the RAM is already initialized, while actually you're seeing the result of a previous initialization (from before the reset).

Something else. Are you aware of the set cputrace on command in openMSX. This generates A LOT of output, but it might be useful for you to compare openMSX with your emulator. To get this activated from the very start, you can pass the command via the openMSX command line:
openmsx -command "set cputrace on"

By fcoury

Rookie (18)

fcoury's picture

10-04-2023, 15:35

Thanks again wouter_, I wasn't aware at all. Instead I was doing this every cycle:

    fn report_state(&mut self) -> anyhow::Result {
        let pc = self.send("reg pc")?.parse()?;
        let sp = self.send("reg sp")?.parse()?;
        let a = self.send("reg a")?.parse()?;
        let f = self.send("reg f")?.parse()?;
        let b = self.send("reg b")?.parse()?;
        let c = self.send("reg c")?.parse()?;
        let d = self.send("reg d")?.parse()?;
        let e = self.send("reg e")?.parse()?;
        let h = self.send("reg h")?.parse()?;
        let l = self.send("reg l")?.parse()?;
        let hl = self.send("reg hl")?.parse()?;
        let bc = self.send("reg bc")?.parse()?;
        let hl_contents = self
            .send(&format!("debug read memory 0x{:04X}", hl))?
            .parse()?;
        let opcode = self
            .send(&format!("debug read memory 0x{:04X}", pc))?
            .parse()?;

        Ok(InternalState {
            pc,
            sp,
            a,
            f,
            b,
            c,
            d,
            e,
            h,
            l,
            hl,
            bc,
            hl_contents,
            opcode,
        })
    }

And if this string mismatches my internal state, I was breaking the program. I'll take a look at cputrace now.

Thanks!

By fcoury

Rookie (18)

fcoury's picture

10-04-2023, 15:53

@_wouter, can I ask you something else I'm not 100% sure about how the slot mapping works?

Let's assume I have my emulated machine's memory layout like below:

Primary Slot Config: 11110000

Slot #0: ROM path=Some("/Users/fcoury/Downloads/expert1.rom") base=0x0000 size=0x8000
Slot #1: Empty
Slot #2: Empty
Slot #3: RAM base=0x0000 size=0xFFFF

Segment 0: 0x0000 - 0x7FFF - base: 0x0000 - (slot 0)
Segment 1: 0x8000 - 0xFFFF - base: 0x0000 - (slot 3)

Memory page 0 (0x0000 - 0x3FFF): primary slot 0 (ROM path=Some("/Users/fcoury/Downloads/expert1.rom") base=0x0000 size=0x8000)
Memory page 1 (0x4000 - 0x7FFF): primary slot 0 (ROM path=Some("/Users/fcoury/Downloads/expert1.rom") base=0x0000 size=0x8000)
Memory page 2 (0x8000 - 0xBFFF): primary slot 3 (RAM base=0x0000 size=0xFFFF)
Memory page 3 (0xC000 - 0xFFFF): primary slot 3 (RAM base=0x0000 size=0xFFFF)

When I the CPU writes to address 0xC000 in this scenario, the write goes to the RAM's 0x4000 address right? Since pages 2 and 3 are mapped to the same slot (3) 0x8000-0xBFFF maps to the RAM slot's 0x0000-0x3FFF and 0xC000-0xFFFF maps to the same RAM slot, but address 0x4000-0x7FFF. Is that how the primary slot mapping should be?

I wrote this test case below (in Rust) to make sure I was doing the right thing:

    #[test]
    fn test_address_translation() {
        let mut bus = Bus::new(&[
            SlotType::Rom(RomSlot::new(&[0; 0x8000], 0x0000, 0x8000)),
            SlotType::Empty,
            SlotType::Empty,
            SlotType::Ram(RamSlot::new(0x0000, 0xFFFF)),
        ]);

        bus.ppi.primary_slot_config = 0b00_11_00_00;
        assert_eq!(bus.translate_address(0x0000), (0, 0x0000));
        assert_eq!(bus.translate_address(0x4000), (0, 0x4000));
        assert_eq!(bus.translate_address(0x8000), (3, 0x0000));
        assert_eq!(bus.translate_address(0xC000), (0, 0x8000));

        bus.ppi.primary_slot_config = 0b00_00_00_00;
        assert_eq!(bus.translate_address(0x0FFF), (0, 0x0FFF));
        assert_eq!(bus.translate_address(0x4FFF), (0, 0x4FFF));
        assert_eq!(bus.translate_address(0x8FFF), (0, 0x8FFF));
        assert_eq!(bus.translate_address(0xFFFF), (0, 0xFFFF));

        bus.ppi.primary_slot_config = 0b11_11_00_00;
        assert_eq!(bus.translate_address(0x0FFF), (0, 0x0FFF));
        assert_eq!(bus.translate_address(0x4FFF), (0, 0x4FFF));
        assert_eq!(bus.translate_address(0x8FFF), (3, 0x0FFF));
        assert_eq!(bus.translate_address(0xCFFF), (3, 0x4FFF));

        bus.ppi.primary_slot_config = 0b11_11_01_00;
        assert_eq!(bus.translate_address(0x0FFF), (0, 0x0FFF));
        assert_eq!(bus.translate_address(0x4FFF), (1, 0x0FFF));
        assert_eq!(bus.translate_address(0x8FFF), (3, 0x0FFF));
        assert_eq!(bus.translate_address(0xFFFF), (3, 0x7FFF));

        bus.ppi.primary_slot_config = 0b11_11_01_10;
        assert_eq!(bus.translate_address(0x0FFF), (2, 0x0FFF));
        assert_eq!(bus.translate_address(0x4FFF), (1, 0x0FFF));
        assert_eq!(bus.translate_address(0x8FFF), (3, 0x0FFF));
        assert_eq!(bus.translate_address(0xFFFF), (3, 0x7FFF));
    }

Does this seem correct?

Another question: when I configure one of openMSX's primary slot to be external like below:

<primary external="true" slot="1" />

Does it mean that this slot is empty? And that writing to it has no effect and reading from it returns 0xFF?

Thank you again for your help!

By fcoury

Rookie (18)

fcoury's picture

10-04-2023, 16:31

Just wanted to update this post since _wouter helped me over at the openMSX librechat IRC server.

Basically here's where my assumptions were wrong:

I thought that there was this address translation that would take place when reading from one of the memory pages. Let's say that your slot configuration was 11 11 00 00, which in my example above maps as ROM ROM RAM RAM (since you read fro right to left).

In this case I was thinking that if I read 0x8000 which falls into memory page 2 that is mapped to RAM, you would actually read RAM's 0x0000 location. As I learned, there's no translation. You still read RAM's 0x8000, so each byte on the slot configuration is merely a flag bit indicating from which "chip" to read.

As in regards to my second question about external slots, the flag indicates that the user may insert a cartidge or another external device on the slot. If it's unpopulated, the reads are unpredictable based on the presence of pull up resistors. However, it's typical (but not 100% guaranteed) that reading from this slot will return 0xFF. And writing will have no effect.

Thanks _wouter.

By Accumulator

Champion (351)

Accumulator's picture

29-04-2023, 01:35

I encountered also problems with RAM and Mappers, working on real MSX, failing on OpenMSX....
Sounds stupid but, when in OpenMSX select one page, lower than needed.
If you need to run something in Page 1, slot 1, select slot 0 , and magically it works..

Exactly for the same reason, I would like to see real-time variables from the debugger, to know exactly which page, slot, etc was selected..... real-time..