The MSX has several layers of memory mapping hardware.
I don't guarantee any of this is right, it's just my best understanding after hours of trying to comprehend this mess.

The first is the primary slot system, configured via I/O port a8.
When reading or writing to the Z80 CPU, A14+A15 are used to select the primary slot.

Primary slot 0 is usually for the BIOS.
Primary slot 1 is usually for the first cartridge port.
Primary slot 2 is usually for the secondary expansion cartridge port.
Primary slot 3 is usually for the system RAM, but also for the BIOS sub ROM ...

From here, the secondary slot for said primary slot is also selected from A14+A15.

Generally speaking, the MSX1 only has secondary slots on primary slot 3.
What that means is that the secondary slot setting is ignored on said primary slots.
The MSX2 and beyond added secondary slots to primary slot 0, for extended ROMs (eg the kanji ROM.)

The MSX2 uses a secondary slot for the BIOS sub ROM, which contains MSX2 and later BIOS functions,
such as controlling the new Yamaha V9938 VDP. It's apparently not specified where it belongs, but
in general, primary slot 3 and any secondary slot is fine.

There are also I/O ports fc-ff on the MSX2 and later, which are used to control RAM banking.
This lets you control more than 64KB of RAM. This slot is also selected via A14+A15.

Finally, cartridge accesses have their own memory mappers. And there's a lot of them.
And there's no way to detect which mapper a given game needs to work. Isn't that fun?

A simple 8K Konami mapper will implement four 8K bank selection registers.
The game will set itself to be mapped into primary slots 1 and 2, making it available at 4000-bfff.
Writing to 4000-5fff sets mapper bank 0, 6000-7fff bank 1, 8000-9fff bank 2, a000-bfff bank 3.
Reading from the same ranges will take the lower 1fff and add the same bank * 8KB for the effective ROM address.

So putting it all together: A14+15 selects the primary slot and the memory slot for RAM.
The primary slot + A14+15 selects the secondary slot.
The primary slot + secondary slot form a 64K region of memory (eg slot 0-0 = BIOS ROM, 3-1 = BIOS sub ROM)
Secondary slots sometimes don't exist.
The 16-bit address is not actually transformed by the primary and secondary slots.
Cartridges don't know anything about primary or secondary slots, so they implement their own mappers.

Since textual descriptions are always garbage, here's code to emulate all of this:

...

struct CPU {
  ...
  struct Slot {
    uint8 memory;
    uint2 primary;
    uint2 secondary[4];
  } slot[4];
};

auto CPU::power() -> void {
  ...
  //exact RAM sizes vary ... try to be overly generous here
  if(Model::MSX()     ) ram.allocate (64_KiB);
  if(Model::MSX2()    ) ram.allocate(256_KiB);
  if(Model::MSX2Plus()) ram.allocate(256_KiB);

  slot[0] = {3, 0, {0, 0, 0, 0}};  //default memory slot, primary slot, secondary slots
  slot[1] = {2, 1, {0, 0, 0, 0}};
  slot[2] = {1, 2, {0, 0, 0, 0}};
  slot[3] = {0, 3, {0, 0, 0, 0}};
}

auto CPU::read(uint16 address) -> uint8 {
  if(address == 0xffff) return readSecondarySlot();

  uint2 page = address.bits(14,15);
  uint2 primary = slot[page].primary;
  uint2 secondary = slot[primary].secondary[page];

  if(primary == 0) {
    return system.bios.read(address);
  }

  if(primary == 1) {
    return cartridge.read(address);
  }

  if(primary == 2) {
    return expansion.read(address);
  }

  if(primary == 3) {
    if(secondary == 0) {
      if(Model::MSX()) return ram.read(address);
      uint22 logical = slot[page].memory << 14 | (uint14)address;
      return ram.read(logical);
    }
    if(secondary == 1 && system.sub) {
      return system.sub.read(address);
    }
  }

  return 0xff;
}

auto CPU::write(uint16 address, uint8 data) -> void {
  if(address == 0xffff) return writeSecondarySlot(data);

  uint2 page = address.bits(14,15);
  uint2 primary = slot[page].primary;
  uint2 secondary = slot[primary].secondary[page];

  if(primary == 0) {
    return;
  }

  if(primary == 1) {
    return cartridge.write(address, data);
  }

  if(primary == 2) {
    return expansion.write(address, data);
  }

  if(primary == 3) {
    if(Model::MSX()) return ram.write(address, data);
    uint22 logical = slot[page].memory << 14 | (uint14)address;
    return ram.write(logical, data);
  }
}

//

auto CPU::in(uint16 address) -> uint8 {
  switch((uint8)address) {
  case 0x98: return vdp.data();
  case 0x99: return vdp.status();
  case 0xa2: return psg.read();
  case 0xa8: return readPrimarySlot();
  case 0xa9: return keyboard.read();
  case 0xfc: case 0xfd: case 0xfe: case 0xff:
    if(Model::MSX()) return 0xff;  //MSX1 has a fixed 64KB RAM
    //note: reading these ports is said to be unreliable;
    //but since it's not specified how so, that is not emulated here
    return slot[(uint2)address].memory;
  }
  return 0xff;
}

auto CPU::out(uint16 address, uint8 data) -> void {
  switch((uint8)address) {
  case 0x98: return vdp.data(data);
  case 0x99: return vdp.control(data);
  case 0xa0: return psg.select(data);
  case 0xa1: return psg.write(data);
  case 0xa8: return writePrimarySlot(data);
  case 0xaa: return keyboard.write(data.bits(0,3));
  case 0xfc: case 0xfd: case 0xfe: case 0xff:
    slot[(uint2)address].memory = data;
    return;
  }
}

//

auto CPU::readPrimarySlot() -> uint8 {
  uint8 data;
  data.bits(0,1) = slot[0].primary;
  data.bits(2,3) = slot[1].primary;
  data.bits(4,5) = slot[2].primary;
  data.bits(6,7) = slot[3].primary;
  return data;
}

auto CPU::writePrimarySlot(uint8 data) -> void {
  slot[0].primary = data.bits(0,1);
  slot[1].primary = data.bits(2,3);
  slot[2].primary = data.bits(4,5);
  slot[3].primary = data.bits(6,7);
}

//

auto CPU::readSecondarySlot() -> uint8 {
  auto primary = slot[3].primary;
  uint8 data;
  data.bits(0,1) = slot[primary].secondary[0];
  data.bits(2,3) = slot[primary].secondary[1];
  data.bits(4,5) = slot[primary].secondary[2];
  data.bits(6,7) = slot[primary].secondary[3];
  return ~data;
}

auto CPU::writeSecondarySlot(uint8 data) -> void {
  auto primary = slot[3].primary;
  slot[primary].secondary[0] = data.bits(0,1);
  slot[primary].secondary[1] = data.bits(2,3);
  slot[primary].secondary[2] = data.bits(4,5);
  slot[primary].secondary[3] = data.bits(6,7);
}

//

uint8 B8K[4]={0,1,0,0};  //Konami 8K (non-SCC)

auto Cartridge::read(uint16 address) -> uint8 {
  if(!rom) return 0xff;
  if(address>>13==2)return rom.read(B8K[0]<<13|(uint13)address);
  if(address>>13==3)return rom.read(B8K[1]<<13|(uint13)address);
  if(address>>13==4)return rom.read(B8K[2]<<13|(uint13)address);
  if(address>>13==5)return rom.read(B8K[3]<<13|(uint13)address);
  return 0xff;
}

auto Cartridge::write(uint16 address, uint8 data) -> void {
  if(address>>13==2) B8K[0]=0;
  if(address>>13==3) B8K[1]=data;
  if(address>>13==4) B8K[2]=data;
  if(address>>13==5) B8K[3]=data;
}