Эмуляция отображения памяти игровой консоли, доступ к разным локациям на основе предоставленного адреса

Я реализую эмулятор для старой игровой консоли, в основном в учебных целях.

Эта консоль сопоставляет ромы и многое другое с регионами в своем адресном пространстве. Некоторые местоположения также зеркалируются, поэтому несколько адресов могут соответствовать одному и тому же физическому местоположению. Я хотел бы подражать этому, но я не уверен, что было бы хорошим подходом для этого (а также понятия не имею, как называется этот процесс, отсюда этот несколько общий вопрос).

Одна вещь, которая действительно работает, - это простая неупорядоченная карта. Он должен содержать абсолютные адреса и соответствующие указатели на мои структуры данных. Таким образом, я могу легко отобразить все, что мне нужно, в адресное пространство системы. Проблема с этим подходом в том, что он явно потребляет много памяти. Даже с маленькими ромами я получаю почти десять миллионов записей благодаря вышеупомянутому зеркалированию. Конечно, это не самое правильное решение?

Любая помощь очень ценится.

Редактировать:

Чтобы предоставить некоторые подробности о том, как именно я это делаю:

Речь идет, конечно же, о SNES. Используя эту вики в качестве основного ресурса, я реализовал то, что упомянул выше, следующим образом:

  • Создайте std::unordered_map<uint32,uint8_t*> mMemoryMap;
  • Проверьте, является ли ROM LoRom или HiRom
  • For each byte in the rom
    • Calculate the address where it should be mapped and emplace both the address and a pointer to said byte in the map
    • Если раздел необходимо отразить где-то еще, повторите вышеизложенное.
  • Это будет применяться ко всему, что мне нужно сделать доступным, например, к видео- или системной памяти.

Если теперь я хочу получить доступ к чему-либо в адресном пространстве, я могу просто использовать адрес, который система будет использовать для внутренних целей.


person ZeroSum    schedule 13.01.2015    source источник
comment
Я думаю, вам следует добавить код для этого   -  person Irrational Person    schedule 14.01.2015


Ответы (3)


Я предполагаю, что для непрерывных адресов физические местоположения также являются смежными, в определенных блоках памяти или «кусках». То есть, если адрес 0x0000 отображается на 0xFF00, то 0x0004 отображается на 0xFF04.

введите описание изображения здесь

Если они работают таким образом, вы можете составить список, содержащий информацию об этих фрагментах. Сказать:

struct Chunk
{
   int addressStart, memoryStart, size;
}

Чанки могут быть упорядочены по addressStart, так что вы можете найти правильный блок, который вам понадобится для любого адреса. Это требует, чтобы вы перебирали список, но если у вас всего несколько фрагментов, это может быть приемлемо.

person Andres Urquijo    schedule 13.01.2015
comment
Такое очевидное решение, но я его совсем не видел. Очень доволен результатами. Большое спасибо! - person ZeroSum; 14.01.2015

Вместо того, чтобы использовать простые карты (которые даже с диапазонами могут увеличиваться до больших размеров), вы можете вместо этого использовать более интеллектуальную карту.

Например, если консоль отображает 0x10XXXX - 0x1FXXXX на один и тот же 0x20XXXX, вы можете разработать структуру, которая будет содержать это повторение (начало 0x100000 конец 0x1FFFFF повтор 0x010000, хотя вы можете использовать битовую маску, а не повторять).

person Guvante    schedule 13.01.2015

В настоящее время я нахожусь в одной лодке с вами и делаю эмулятор NES в учебных целях. Полная карта памяти объявляется как массив указателей на байты. Каждый указатель может указывать на другой массив, что позволяет мне иметь указатели на одни и те же данные в случае зеркалирования.

byte *memoryMap[cpuMemSize];

Я перебираю повторяющиеся адреса и сопоставляю их так, чтобы они указывали на байты в следующем массиве. Массив оперативной памяти - это память, которая отображается 4 раза на карту памяти ЦП.

byte ram[ramSize];

Следующий код проходит через массив RAM и отображает его на карте памяти CPU 4 раза.

// map RAM 4 times to emulate mirroring
mem_address memAddr = 0;
for (int x = 0; x < 4; ++x)
{
    for (mem_address ramAddr = 0; ramAddr < ramSize; ++ramAddr.value)
    {
        memoryMap[memAddr.value++] = &ram[ramAddr.value];
    }
}

Затем вы можете записать значение a в 256-й байт, используя что-то вроде этого, что, конечно же, будет распространено на другие части карты памяти, поскольку они указывают на тот же байт в памяти:

*memoryMap[0x00ff] = 10;

Я на самом деле не тестировал это и хочу провести еще несколько тестов в отношении использования и производительности кэша ЦП. Я явно искал другие способы сделать это, когда наткнулся на ваш вопрос и решил, что вложу свои (непроверенные) два цента. Надеюсь, это имеет смысл.

person nenchev    schedule 27.03.2018
comment
Большую часть времени гостевой код, работающий на вашей эмулируемой NES, будет выполнять большую часть своих загрузок / сохранений в обычной памяти, где смежные адреса являются смежными в физической ОЗУ, и с использованием того же сопоставления памяти, которое я предполагал. Но выбранное вами решение не обеспечивает дополнительной эффективности и добавляет дополнительный уровень косвенности для каждого доступа к памяти, выполняемого отдельно для каждого байта. - person Peter Cordes; 28.03.2018
comment
Если вы скомпилируете свой эмулятор для 64-битной системы, каждый байт эмулируемой ОЗУ будет занимать 4 8-байтовых указателя или 32 дополнительных места. Это нормально для NES с процессором от 3 до 4 ГГц с более быстрой кэш-памятью L2 на ядро, чем у NES с оперативной памятью (включая типичные картриджи), но определенно в области уморительной неэффективности. Учтите, что оборудование MMU в ЦП сопоставляет виртуальный адрес с физическим с таблицей страниц, которая занимает ~ 8 байтов на сопоставленную страницу размером 4 КБ. - person Peter Cordes; 28.03.2018
comment
Я предполагаю, что вам нужна более тонкая детализация, чем страницы 4k, но если вы хотите сделать его более сложным и эффективным, вы можете проверить специальные адреса или получить доступ к эмулированной физической памяти через один из 4 указателей на один и тот же массив. (Или просто замаскируйте некоторые адресные биты гостевых адресов после проверки, являются ли они адресами MMIO или другими специальными. Затем используйте их непосредственно в качестве индекса для вашего физического массива. Если 4 сопоставления не имеют размера степени двойки, тогда это может быть не так просто, но использование по модулю константы времени компиляции также довольно эффективно. - person Peter Cordes; 28.03.2018
comment
Именно поэтому я искал / экспериментировал с другими идеями для создания этого сопоставления. Я понимаю, что он имеет косвенный характер и не упаковывает данные удобным для кеша способом, но я решил, что в любом случае отбросил бы этот подход здесь, так как это было бы нормально для такой системы низкого уровня, как NES. - person nenchev; 28.03.2018
comment
Я упомянул кеш не потому, что он недружелюбен к кешу, а просто в целом расточителен. И чтобы показать, насколько мощны современные процессоры, чтобы у них было достаточно кеша, чтобы справиться с этим. Но в любом случае, разве вам все равно не нужно проверять специальные адреса, которые на самом деле являются регистрами MMIO? Вы можете сделать эту проверку полного адреса, а затем замаскировать старшие биты memAddr, чтобы получить ramAddr, верно? Или, по крайней мере, используйте некоторые биты memAddr для индексации таблицы указателей на блоки ОЗУ, например, таблицы страниц, предполагая, что некоторая мощность в 2 размера блока равномерно делит ваше пространство ОЗУ. - person Peter Cordes; 29.03.2018
comment
Я упомянул кеш, потому что потратил много времени на изучение того, как использовать преимущества предварительной выборки и кэширования ЦП для системы компонентов сущности для игрового движка. Что касается этого, я хочу выяснить способ эффективного сопоставления памяти, не прибегая к цепочке блоков if / else if. - person nenchev; 29.03.2018
comment
Значит, нет никаких специальных адресов MMIO, которые запускают какое-то особое действие и должны обрабатываться иначе, чем загрузка или сохранение? И будет ли работать &, чтобы очистить некоторые старшие биты и оставить вам адрес RAM? - person Peter Cordes; 29.03.2018
comment
Да, есть MMIO, до которого я еще не успел. Не был уверен, как я собираюсь решать взаимодействие с вводом-выводом через отображение памяти, у меня есть некоторые (возможно, наивные) идеи. Я нашел несколько хороших ресурсов от других авторов эмуляторов, поэтому я перейду на RTFM и продолжу. Спасибо за ответ. - person nenchev; 29.03.2018
comment
Если все адреса MMIO находятся в пределах некоторого диапазона, то это одна быстрая проверка, прежде чем делать обычные вещи. Ничего страшного, если в этом диапазоне есть нормальная память, смешанная с адресами MMIO, разбирайтесь с этим только в особом случае, когда код обращается к этой памяти. Вы можете использовать растровое изображение, чтобы проверить, является ли адрес адресом MMIO или нет. И только если это так, тогда найдите указатель на функцию из хеш-таблицы для ее обработчика или чего-то еще ... Но в обычном случае ваша первая проверка обнаруживает, что это определенно не MMIO, тогда вы выполняете простую вещь быстрого пути . - person Peter Cordes; 29.03.2018