Emuladores SNES a apenas alguns pixels da perfeição absoluta


Estamos tão perto de criar um emulador que possa recriar perfeitamente todas as funções de hardware real e software SNES.

Nos últimos 15 anos, como codificador do emulador de bsnes, tentei aperfeiçoar a emulação de Super Nintendo, mas agora estamos diante do último problema: o tempo preciso dos ciclos de clock dos processadores de vídeo SNES. Para alcançar esse estágio final de precisão da emulação, é necessária a ajuda de toda a comunidade, e espero seu apoio. Mas, primeiro, vou contar o que já alcançamos.

Estado atual


Hoje, a situação com emulação de SNES é muito boa. Além dos periféricos incomuns que resistem à emulação (por exemplo, um taco de golfe com sensor de luz , simulador de bicicleta e modem dial-up, usado no Japão para apostas em corridas de cavalos no Japão), todos os jogos SNES oficialmente licenciados são totalmente jogáveis, e nenhum jogo tem problemas óbvios.

A emulação SNES tornou-se tão precisa que até tive que dividir o emulador em duas versões: higan , que busca precisão e consistência absolutas com a documentação do hardware, e bsnes , que busca velocidade, amplos recursos e facilidade de uso.

Recentemente, no campo da emulação SNES, muitas conquistas interessantes foram recebidas, incluindo:


… e muito mais!

Então, está feito? Todos trabalharam bem, tchau, e obrigado pelo peixe? Bem, não exatamente.

Hoje, alcançamos precisão no nível de batida de quase todos os componentes do SNES. As únicas exceções foram a PPU (unidade de processamento de imagem, módulos de processamento de imagem) usada para gerar quadros de vídeo transmitidos para a tela. Sabemos principalmente como as PPUs funcionam, mas, para algumas funções, precisamos adivinhar, o que leva à precisão imperfeita.

Em uma escala geral, os problemas restantes são bem menores. Se você não se esforça pela idealidade absolutamente perfeita da emulação pelo amor à arte, não posso convencê-lo da necessidade de melhorar ainda mais a emulação de PPU. Como em qualquer campo, quanto mais próximos estivermos do ideal, menor será o retorno.

Mas posso dizer por que isso é importante para mim : esse é o trabalho de toda a minha vida e não quero que diga que cheguei tão perto da conclusão sem dar o último passo. Estou envelhecendo e não sou eterna. Quero que a última peça do quebra-cabeça seja resolvida, para que, depois de me aposentar, tenha certeza de que o legado do SNES seja confiável e totalmente preservado, graças à emulação. Eu quero dizer que o problema está resolvido .

Se você ainda estiver intrigado, continue lendo para se familiarizar com o histórico de um problema e as soluções oferecidas por mim.

Modelagem da arquitetura SNES


Vamos começar listando os componentes que compõem o SNES:


Diagrama do sistema Super NES.

As setas indicam as direções nas quais vários processadores SNES podem trocar dados entre si e as linhas pontilhadas indicam as conexões com os chips de memória.

O mais importante para nós agora é notar que a saída de vídeo e som é transmitida diretamente do PPU e DSP. Isso significa que eles agem como "caixas pretas" e não podemos ver o que está acontecendo dentro delas. Mais tarde, isso se tornará importante para nós.

Correção


Imagine que emulamos o comando da CPU “multiplicar”, que pega dois registradores (variáveis), multiplica-os, recebe o resultado e vários sinalizadores indicando o estado do resultado (por exemplo, estouro ).

Podemos escrever um programa que multiplique qualquer valor possível de 0 a 255 como um fator e um multiplicador. Em seguida, podemos derivar os resultados de multiplicação numérica e de sinalização. Assim, obtemos duas tabelas de 65 536 elementos.

Ao analisar essas tabelas, podemos determinar com precisão como e onde os resultados dos cálculos da CPU são definidos de uma certa maneira. Em seguida, podemos modificar os emuladores para que, ao executar o mesmo teste, obtenhamos exatamente as mesmas tabelas ao mesmo tempo.

Agora, digamos que a CPU possa fazer a multiplicação de 16 bits x 16 bits. Ao testar todos os valores possíveis, serão gerados 4 bilhões de resultados, quase impossíveis de serem testados em um período de tempo razoável. Se a CPU tiver multiplicações de 32 bits x 32 bits, na prática, não será possível testar todas as combinações de valores de entrada antes da morte térmica do Universo (pelo menos no nível atual de tecnologia).

Nesses casos, agimos de maneira mais seletiva nos testes e tentamos determinar quando os sinalizadores podem mudar exatamente, quando os resultados podem transbordar e assim por diante. Caso contrário, teríamos que executar testes que nunca terminariam.

A multiplicação é uma operação bastante trivial, mas o mesmo princípio pode ser estendido a todo o processo de engenharia reversa, incluindo operações mais complexas, por exemplo, transmissão de dados via DMA (acesso direto à memória) durante o retorno horizontal do feixe. Criamos testes que tentam determinar o que acontece em casos limítrofes e, em seguida, verificamos se nossa emulação se comporta de maneira idêntica ao comportamento do SNES real.

Geradores de sinais e batidas


O SNES possui dois geradores de sinal (osciladores): um oscilador de cristal operando a uma frequência de aproximadamente 21 MHz (controla os módulos CPU e PPU) e um ressonador de cerâmica operando a uma frequência de aproximadamente 24 MHz, que controla SMP e DSP. Nos coprocessadores de cartucho, às vezes é usado um oscilador de cristal de 21 MHz e, às vezes, seus próprios geradores de sinal trabalhando com outras frequências.


Recriar esta placa de circuito Super Famicom no código é mais difícil do que parece.

O relógio é o elemento básico do tempo de qualquer sistema, e o SNES foi projetado para executar várias tarefas com determinadas frequências e intervalos de tempo.

Se você imaginar um relógio de 100 hertz, será um dispositivo com uma saída binária alternando para um estado lógico alto do sinal (por exemplo, +5 V) e depois para um estado baixo do sinal (0 V ou terra) 100 vezes por segundo. Ou seja, a cada segundo a tensão na saída flutua 200 vezes: aumentando 100 vezes e 100 vezes diminuindo a frente do sinal do relógio.

Um ciclo de clock é geralmente considerado uma transição completa, ou seja, um ciclo de 100 Hz gera 100 ciclos de clock por segundo. Alguns sistemas exigem uma distinção entre arestas ascendentes e descendentes e, para eles, dividimos o ciclo em semiciclos para indicar cada fase (alta ou baixa) do sinal do relógio.

A tarefa mais importante de um emulador preciso é concluir as tarefas exatamente da mesma maneira e exatamente ao mesmo tempo que em equipamentos reais. No entanto, não é muito importante como as tarefas são executadas. A única coisa importante é que o emulador, recebendo os mesmos sinais de entrada, gere os mesmos sinais de saída ao mesmo tempo que no hardware real.

Horários


Às vezes, as operações levam tempo. Tomemos, por exemplo, multiplicação na CPU do SNES. Em vez de pausar e aguardar a conclusão da multiplicação, a CPU do SNES calcula o resultado da multiplicação um bit por vez em segundo plano para oito ciclos de clock dos códigos de operação da CPU. Isso potencialmente permite que o código execute outras tarefas enquanto aguarda a conclusão da multiplicação.

Provavelmente, qualquer software comercial aguardará esses oito ciclos, porque se você tentar ler o resultado antes que esteja pronto, obteremos um resultado parcialmente concluído. No entanto, antes dos emuladores SNES fornecerem resultados corretos instantaneamente , sem esperar por esses ciclos adicionais de clock.

Quando os fãs de consoles começaram a criar e testar software auto-escrito em emuladores, essa discrepância começou a causar certos problemas. Parte do software, por exemplo, muitos dos primeiros hacks da ROM do Super Mario World , funcionavam corretamente apenas nesses emuladores antigos, mas não no hardware SNES real. Isso aconteceu porque eles foram desenvolvidos levando em consideração a obtenção instantânea (não confiável do ponto de vista do equipamento real) dos resultados da multiplicação.

No processo de aprimoramento dos emuladores, a compatibilidade do software antigo foi interrompida e, portanto, tivemos que adicionar opções de compatibilidade aos novos emuladores para não perder esses programas. Sim, por mais surreal que pareça, mas hoje os emuladores devem emular outros emuladores!

A conveniência desse atraso de multiplicação na CPU reside no fato de ser muito previsível: oito ciclos de cálculo de relógio começam imediatamente após a solicitação da operação de multiplicação. Ao escrever o código que lê os resultados após cada ciclo, conseguimos verificar se a CPU do SNES usa o algoritmo Booth para multiplicação .

Sincronização de relógio


Outras operações não são fáceis de modelar porque são executadas de forma assíncrona em segundo plano. Um desses casos é a atualização DRAM do processador SNES central.

Durante a renderização de cada linha de varredura, toda a CPU do SNES em um determinado estágio suspende sua operação por um curto período de tempo enquanto o conteúdo do chip de RAM é atualizado. Isso é necessário porque, para reduzir o custo no SNES, a RAM dinâmica (em vez de estática) foi usada como a memória principal da CPU. Para salvar o conteúdo da RAM dinâmica, ela deve ser atualizada periodicamente.


Criar um emulador verdadeiramente perfeito não é suficiente para garantir a jogabilidade de todos os três mil e meio de jogos SNES. Também é necessário obter a simulação de cada função do sistema com perfeita precisão de tato.

O fator chave na análise dos tempos exatos dessas operações foi a possibilidade de usar contadores de PPU horizontais e verticais. Esses contadores realizam incrementos e são redefinidos após cada deslocamento horizontal e vertical inverso do feixe. No entanto, sua precisão é apenas um quarto da frequência do gerador de sinal da CPU SNES; em outras palavras, o contador horizontal aumenta a cada quatro ciclos de clock.

Lendo várias vezes os valores dos contadores, consegui determinar com qual trimestre do ciclo do relógio o contador está alinhado. Combinando esse conhecimento com funções especialmente criadas que podem dar um passo em direção ao número exato de ciclos de clock indicado pelo usuário, consegui combinar perfeitamente a CPU do SNES com qualquer posição exata do ciclo de clock de que preciso.

Graças a uma passagem iterativa de muitos ciclos de clock, pude determinar quando certas operações estão acontecendo exatamente (por exemplo, atualização de DRAM, transmissão de HDMA, interrupção de sondagem etc.) Depois disso, eu pude recriar exatamente tudo isso em emulação.

Chip SMPO console do SNES também possui seus próprios temporizadores, e a engenharia reversa bem-sucedida também foi realizada para esse processador. Posso dedicar um artigo inteiro apenas ao registro SMP TEST, que permite aos programadores controlar o divisor de frequência SMP e seu timer, sem mencionar outras coisas terríveis. Basta dizer que não foi um processo fácil e rápido, mas no final vencemos.

Coletamos coprocessadores



O chip SuperFX é apenas um dos muitos coprocessadores de cartucho que o emulador SNES pode suportar.

Há vários coprocessadores SNES usados ​​em vários cartuchos de jogos que também precisamos domar. De CPUs individuais de uso geral como SuperFX e SA-1 , processadores de sinais digitais como DSP-1 e Cx4 a aceleradores de descompressão como S-DD1 e SPC7110 ou relógios em tempo real da Sharp e Epson e muito mais ...

Isso significa que o emulador SNES deve lidar com instruções SuperFX e caches de pixels; com o esquema de resolução de conflitos do barramento de memória SA-1 (permitindo que as CPUs SNES e SA-1 usem os mesmos chips de ROM e RAM simultaneamente); com firmware integrado DSP-1 e Cx4; com codificadores aritméticos baseados em previsão S-DD1 e SPC7110; bem como com casos ímpares limítrofes de BCD (decimal com código binário) em geradores em tempo real. Lenta mas seguramente, usando todas as técnicas para determinar a correção e os tempos descritos acima, conseguimos aprender a emular todos esses chips quase perfeitamente.

Foram necessários muito esforço e milhares de dólares para remover as tampas dos chips e remover o firmware dos processadores de sinais digitais usados ​​em jogos diferentes. Em um caso, a emulação NEC uPD772x permitiuuse o código de higan para salvar a voz do falecido Stephen Hawking! .

Em outro caso, precisávamos fazer engenharia reversa de todo um conjunto de instruções para a arquitetura Hitachi HG51B, porque ninguém havia publicado a documentação dessa arquitetura. Em outro caso, descobriu-se que um jogo ( Hayazashi Nidan Morita Shougi 2 ) possui um poderoso CPU ARM6 de 32 bits com uma frequência de 21 MHz, o que acelera o jogo de shogi japonês!

Apenas salvar todos os coprocessadores SNES acabou sendo um processo de longo prazo, cheio de dificuldades e surpresas.

Processamento de sinal digital


O chip Sony S-DSP (Processador de sinal digital), que não deve ser confundido com o coprocessador de cartucho DSP-1, gerou um som SNES exclusivo. Nesse chip, oito canais de áudio com codificação ADPCM de 4 bits foram conectados, o que garantiu a criação de um sinal estéreo de 16 bits.

Externamente, e a partir do diagrama do sistema apresentado acima, a princípio parece que o DSP é uma "caixa preta": ajustamos os canais de som e os parâmetros do mixer, após o que o chip gera som transmitido aos alto-falantes.

Mas uma função importante permitiu que o desenvolvedor, sob o apelido blargg, realizasse uma engenharia reversa completa desse chip: era um buffer de eco. O SNES DSP possui uma função que combina a saída de amostras anteriores para criar um efeito de eco. Isso acontece no final do processo de geração de som (além do último sinalizador de bloqueio de som, que pode ser usado para desativar toda a saída de som.)

Ao escrever código com o tempo correto das medidas e rastrear o eco resultante, conseguimos determinar a ordem exata das operações executadas pelo DSP para gerar de cada amostra e criando som perfeito e precisão de batida.

Salvando PPU


Tudo isso nos levou à última parte do esquema arquitetural do SNES: chips PPU-1 e PPU-2. Graças a John McMaster, temos varreduras dos chips S-PPU1 (revisão 1) e S-PPU2 (revisão 3) com um aumento de vinte vezes.


Digitalização vinte vezes do cristal do primeiro SNES PPU ...


... e o segundo PPU

Ambas as varreduras de cristal nos permitem saber que os chips obviamente não são CPUs de uso geral, nem arquiteturas especializadas que executam códigos de operação a partir da ROM interna do programa de firmware. Estes são circuitos lógicos separados com lógica codificada que recebem sinais de entrada de diferentes registros e memória e criam um sinal de vídeo para o monitor, uma linha de varredura por vez.

As PPUs continuam sendo o último obstáculo à emulação do SNES porque, diferentemente de todos os componentes descritos acima, as PPUs são na verdade uma caixa preta. Podemos configurá-los para qualquer estado, mas a CPU do SNES não pode monitorar diretamente o que eles geram.

Se usarmos o exemplo anterior com multiplicação como analogia, imagine que você solicitou o resultado 3 * 7, mas, em vez da resposta binária, você obtém uma imagem analógica difusa dos números "21" na tela. Qualquer pessoa que execute seu software poderá ver 21, mas você não pode escrever um programa de teste para verificar automaticamente se ele vê a resposta correta. A verificação manual de uma pessoa desses resultados não pode ser escalada para mais de vários milhares de testes, e milhões serão necessários para maximizar o comportamento da PPU.

Sei o que você pensou: “Mas é mais fácil usar uma placa de captura, executar o processamento de imagens, compará-las aproximadamente com a imagem na tela digital do emulador e realizar testes com base nisso?”

Bem, sim, é possível! Especialmente se o teste for verificar dois números enormes que ocupam a tela inteira.

Mas e se o teste tiver muitas nuances, e estamos tentando reconhecer a diferença de cores da reticulação de um pixel? E se queremos executar um milhão de testes em ordem e nem sempre sabemos o que vamos gerar, mas ainda queremos comparar o resultado com a saída da nossa emulação?

Nada supera a conveniência e a precisão dos dados digitais - um fluxo preciso de bits que podem apenas corresponder ou não. A natureza analógica de um sinal CRT não pode nos fornecer isso.

Por que isso é importante?


Com exceção de um jogo ( Air Strike Patrol ), todos os softwares SNES licenciados oficialmente (deveriam ter sido) baseados em strings de varredura. Esses jogos não tentam alterar o estado da renderização de PPU no meio da linha raster atual renderizada (esse truque dos programadores é chamado de "efeito raster"). Isso significa que o tempo de execução da grande maioria dos jogos não precisa ser particularmente preciso; se você tiver tempo para a próxima linha completa de varredura, tudo estará em ordem.

Mas isso é importante para um único jogo.




Esta série de imagens mostra um efeito de emulação complexo usado na mensagem de “Boa Sorte” do Air Strike Patrol .

Nas imagens acima, você vê o texto quadro a quadro "Boa sorte" do Air Strike Patrol . O jogo o implementa, alterando a posição de rolagem vertical da camada de fundo 3 (BG3). No entanto, a tela do painel à esquerda (onde você pode ver que o jogador tem 39 mísseis) também está na mesma camada de fundo.

O jogo consegue realizar essa separação alterando a posição do pergaminho BG3 em cada linha de varredura após renderizar o painel esquerdo, mas antes que o texto "Boa sorte" comece a ser renderizado. Isso pode ser feito porque fora do painel e do texto, o BG3 é transparente e não há nada para desenhar entre esses dois pontos, independentemente do valor do registro de rolagem vertical. Esse comportamento nos mostra que os registros de rolagem podem ser alterados em qualquer estágio da renderização.


Essa pequena sombra sob o avião causou muitas dores de cabeça ao desenvolvedor de emuladores obcecados pela precisão.

A imagem acima mostra a sombra infame de um avião. Esse efeito é renderizado alterando o registro de brilho da tela com pequenas ondulações em cinco linhas de varredura.

Durante o jogo, você pode ver que essa sombra é bastante caótica. Na imagem acima, ela se parece um pouco com a letra "c", mas sua forma em cada linha de varredura muda de comprimento e ponto de partida para cada quadro. Os desenvolvedores do Air Strike Patrol esboçaram aproximadamente onde a sombra deveria aparecer e resolveram esse problema diretamente. Na maioria dos casos, isso funciona.

A emulação correta de tal comportamento requer um timing perfeito, o que é absolutamente extremamente difícil de obter no emulador .


Na tela de pausa do Air Strike Patrol , são utilizados efeitos de varredura que não foram intencionalmente usados ​​em nenhum outro jogo SNES.

Agora vamos falar sobre a tela de pausa. Ele liga o BG3 enquanto desenha uma borda amarela-preta à esquerda e a desliga novamente durante a mesma borda à direita para desenhar linhas cinza na tela. Ele também alternadamente, através do quadro, alterna as linhas de varredura nas quais essas linhas cinza são exibidas para criar o efeito de uma tremulação de sobreposição.

Se você ampliar a imagem emulada mostrada acima, notará que durante o par de linhas rasterizadas no canto esquerdo dessas linhas cinzas, existem vários pixels ausentes. Isso aconteceu porque minha emulação de PPU é 100% imperfeita nos ciclos de clock. Nesse caso, causa o efeito de ativar o BG3 um pouco mais tarde do que deveria.

Eu posso muito facilmente mudar os tempos para que esta imagem seja renderizada corretamente. Mas essa mudança provavelmente afetará adversamente outros jogos que alteram os registros de exibição da PPU no meio da linha de varredura. Embora o Air Strike Patrol seja o único jogo que faz isso de propósito, há pelo menos uma dúzia de jogos nos quais isso acontece por acaso (talvez o IRQ seja acionado muito cedo ou mais tarde).

Às vezes, isso causa um breve dano perceptível na imagem, que não é prestada atenção durante o desenvolvimento (por exemplo, em Full Throttle Racingdurante a transição entre a loja e o jogo). Às vezes, a gravação é executada enquanto a tela é transparente e, portanto, não causa anomalias visuais (por exemplo, no caso de exibição do status da HP no Dai Kaijuu Monogatari II ). que são usados ​​nos emuladores mais produtivos.

Mesmo que você ignore o Air Strike Patrol , todos esses efeitos aleatórios (mas válidos) de varredura no software SNES não permitem projetar funcionalmente um renderizador de PPU que gera toda a linha de varredura com precisão perfeita do relógio.

No caso de bsnes ao longo dos anos de tentativa e erro, criamos uma lista desses jogos com "efeitos raster". Também criamos posições de renderização individuais que permitem renderizações muito mais rápidas com base em linhas raster para exibir corretamente todos esses jogos (exceto o Air Strike Patrol , é claro). Mas, em essência, este é um monte de hacks desagradáveis ​​para nós, projetados para jogos específicos.

Também tenho um renderizador PPU baseado em relógio que não precisa de todos esses hacks, mas, de tempos em tempos, cria pequenas diferenças (de um a quatro pixels) com a renderização deste equipamento, como na captura de tela acima do Air Strike Patrol .

Registros de trava interna


A razão para todas essas pequenas falhas se resume a intervalos de tempo.

Digamos que o SNES renderize seu famoso modo 7 , que é uma transformação de textura afim com alterações de parâmetros em cada linha de varredura. Para determinar qualquer pixel da tela, é necessário executar cálculos semelhantes:

px = a * clipe (hoffset - hcenter) + b * clipe (voffset - vcenter) +
b * y + (hcenter << 8)

py = c * clipe (hoffset - hcenter) + d * clipe (voffset - vcenter) +
d * y + (vcenter << 8)

O SNES real não poderá concluir todas essas seis multiplicações com rapidez suficiente para cada pixel que é renderizado no quadro. Mas nenhum desses valores muda para cada pixel (ou, pelo menos, não deve mudar), portanto, apenas precisamos calcular px e py uma vez no início de cada linha raster. Ou seja, o PPU armazena em cache os resultados estáticos em travas, que são essencialmente cópias dos registros PPU. No futuro, eles podem ser transformados ou permanecer inalterados.

Em seguida, as coordenadas x, y são transformadas pelo modo 7 da seguinte maneira:

ox = (px + a * x) >> 8

oy = (py + c * x) >> 8

Embora x varie para cada pixel, sabemos que o incremento é realizado um por vez. Graças ao armazenamento de unidades internas, podemos simplesmente adicionar valores constantes a e c a ox e oy para cada pixel, em vez de realizar duas multiplicações para cada pixel.

Então surge a questão diante de nós: em que posição específica do ciclo do relógio a PPU lê os valores de a e c nos registros externos da PPU aos quais a CPU tem acesso?

Se os levarmos muito cedo, isso pode quebrar alguns jogos. Se demorarmos muito, pode quebrar outros jogos.

A maneira mais fácil é esperar pelos relatórios de bugs e ajustar essas posições para corrigir problemas em cada jogo específico. Mas, neste caso, nunca encontraremos as posições exatas , apenas suas aproximações.

E toda vez que alteramos uma dessas variáveis, é irrealista testar novamente todos os três mil e quinhentos jogos da biblioteca SNES para detectar a deterioração que nossas alterações podem causar.

Fora da frigideira para o fogo



Interpretação artística do processo de eliminação de erros de emulação.

Um estilo semelhante de metodologia de teste, "apenas criaremos o jogo em que estamos interessados ​​em trabalhar a qualquer custo" levou a um fenômeno que chamo de emulação "do fogo para o fogo".

No começo do desenvolvimento da emulação SNES, quando surgiam problemas no jogo, qualquer correção nesse jogo que permitia que ele funcionasse era aceita e adicionada ao emulador. Essa correção necessariamente quebrou outro jogo. E então eles corrigiram este jogo, após o qual o terceiro quebrou. Consertar o terceiro jogo novamente quebrou o primeiro. Isso continuou por muitos anos.

O erro aqui foi que os desenvolvedores tentaram levar em consideração apenas uma variável de cada vez. Suponha que tenhamos um jogo e, para que funcione, os eventos devem ocorrer entre as medidas 20 e 120. Não sabemos a medida exata, então escolha 70, exatamente no meio.

Posteriormente, obtemos um relatório de bug em outro jogo e determinamos que, para esse jogo funcionar , o valor da medida deve estar entre 10 e 60. Agora, alteramos para 40, o que funciona para os dois jogos. Parece lógico!

Mas então o terceiro jogo aparece , no qual o evento deve funcionar entre as medidas 80 e 160! Agora não podemos fazer com que os três jogos funcionem ao mesmo tempo com o mesmo valor.

Isso forçou os desenvolvedores de emuladores a criar hacks para jogos específicos. Os codificadores não desejam lançar um emulador no qual você não pode executar Mario , Zelda ou Metroid . Portanto, para o caso geral, o ciclo do relógio 40 é usado, mas ao carregar o Metroid, forçamos o valor de tempo para 100.

Como isso é possível, por que dois jogos precisam de valores diferentes? Isso acontece porque não apenas uma variável está envolvida aqui. O tempo que você usou anteriormente para acionar outro evento pode afetar o valor de tempo necessário para o próximo evento.

Imagine isso na forma de uma simples expressão algébrica:

2x + y = 120

Você pode resolvê-lo usando x = 10, y = 100. Ou x = 20, y = 80. Ou x = 30, y = 60. Se pensarmos apenas no valor de x, que permite executar simultaneamente um conjunto de jogos, perderemos o fato de que, de fato, o problema pode estar no y errado!

As primeiras versões dos emuladores para aumentar a compatibilidade simplesmente redefiniram o valor de x, dependendo do jogo em execução. Esses hacks de jogos individuais persistiram, mesmo que o valor único e correto de x fosse descoberto posteriormente. Portanto, o problema y nunca seria resolvido!

No entanto, no caso do SNES, nenhuma ou duas variáveis ​​estão envolvidas simultaneamente. Somente a PPU do console SNES possui 52 registros externos, que são aproximadamente 130 parâmetros. No processo de renderização de uma única linha de varredura, todos os 130 desses parâmetros e um número desconhecido de registros e travas internos estão envolvidos. Isso é muita informação para alguém de fora ser capaz de perceber o estado completo da PPU em um determinado momento.

Esse aspecto da emulação não é óbvio para os não iniciados, mas é muito justo: precisão não é igual à compatibilidade. Podemos criar um emulador com 99% de precisão, capaz de executar 10% dos jogos. E você pode escrever um emulador de 80% de precisão que executa 98% dos jogos. Às vezes, uma implementação correta no curto prazo quebra os jogos populares. Este é um sacrifício necessário se você estiver tentando alcançar 100% de precisão e 100% de compatibilidade.

Resolva o problema


Chegamos ao estágio atual de emulação de PPU, graças ao raciocínio dedutivo e aos resultados no mundo real.

Sabemos que duas PPUs têm acesso a dois chips VRAM. Sabemos que eles podem ler de cada chip um número conhecido de bytes de dados por linha raster. Conhecemos os detalhes aproximados de como cada um dos modos de vídeo do SNES funciona. E com base nisso, podemos esboçar um padrão generalizado de como a arquitetura pode parecer. Por exemplo, aqui está um breve exemplo de como os três primeiros modos de vídeo SNES podem funcionar:

if (io.bgMode == 0) {

bg4.fetchNameTable ();

bg3.fetchNameTable ();

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg4.fetchCharacter (0);

bg3.fetchCharacter (0);

bg2.fetchCharacter (0);

bg1.fetchCharacter (0);

}

if (io.bgMode == 1) {

bg3.fetchNameTable ();

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg3.fetchCharacter (0);

bg2.fetchCharacter (0);

bg2.fetchCharacter (1);

bg1.fetchCharacter (0);

bg1.fetchCharacter (1);

}

if (io.bgMode == 2) {

bg2.fetchNameTable ();

bg1.fetchNameTable ();

bg3.fetchOffset(0);

bg3.fetchOffset(8);

bg2.fetchCharacter(0);

bg2.fetchCharacter(1);

bg1.fetchCharacter(0);

bg1.fetchCharacter(1);

}


A PPU revela a um observador de terceiros apenas uma pequena parte de seu estado: sinalizadores horizontais e verticais para trás (apagamento horizontal / vertical), contagens de pixels horizontais e verticais e sinalizadores de sobreposição de ladrilhos no intervalo para sprites. Isso não é tanto, mas repito - todo pequeno elemento do estado acessível ao observador nos ajuda.

A VRAM (RAM de vídeo, memória de vídeo) do chip PPU durante a renderização é fechada para as CPUs SNES, mesmo para leitura. Mas, como se viu, OAM (memória de sprite) e CGRAM (memória de paleta) estão abertos. O truque é que, neste momento, a PPU controla o barramento de endereços. Portanto, lendo o OAM e o CGRAM durante a renderização da tela, posso observar o que a PPU obtém desses dois blocos de memória em um momento tão crítico.

Essas não são todas as peças do quebra-cabeça, mas são suficientes para eu poder implementar os padrões praticamente corretos para obter sprites.

Usando padrões de acesso para OAM e CGRAM abertos, sinalizadores de PPU, observações gerais (ou seja, suposições) de relatórios de erros para diferentes jogos e raciocínio dedutivo, fomos capazes de criar renderizadores de PPU baseados em relógio que poderiam quase perfeitamente iniciar todos os jogos lançados.

Mas a situação ainda é precária: se alguém começar a criar jogos de homebrew usando o tempo preciso de ticks e efeitos raster, todos os nossos emuladores modernos não serão capazes de lidar com isso. Incluindo implementações de software e hardware baseadas em FPGA.

Devo dizer claramente: hoje tudoeles conhecem apenas a ordem interna das operações e o comportamento do snap nos chips PPU do console SNES. Ninguém sabe como imitá-los perfeitamente. Pelo menos por enquanto.

Soluções possíveis


O que devemos fazer com isso? Como determinar a ordem exata das operações em uma PPU se, do ponto de vista da CPU do SNES, é uma "caixa preta"?

Vejo quatro opções possíveis: analisadores lógicos, saída de vídeo digital em modo de teste, risers e remoção de tampas dos chips.

Analisadores lógicos


Se você observar as varreduras de cristais de PPU mostradas acima, notará áreas pretas nas bordas do chip. Estas são as plataformas que se conectam aos contatos dos chips.

Esses pinos armazenam o estado dos chips PPU durante cada ciclo de clock. Aqui você encontra o endereço atual para o qual os chips acessam o chip de memória de vídeo, os valores dos dados transferidos de uma PPU para a segunda e muito mais.

Esta informação não está disponível para código em execução na CPU SNES, mas fornece observações valiosas sobre a ordem interna das operações de PPU.


Conectar as PPUs do console Super NES a um analisador lógico semelhante pode ser a chave para a caixa preta.

O problema crítico dos analisadores lógicos é que eles não são muito convenientes de gerenciar: se você tentar amostrar dados ao vivo de um sistema em funcionamento, obteremos um fluxo de resultados bastante difícil de decifrar. Você encontrará o mesmo problema se tentar analisar a saída RGB analógica do sistema: para capturar esses dados, você deverá executar manualmente cada um dos testes. Esse sistema não é muito bom para criar testes de regressão automatizados reproduzíveis.

Saída de vídeo digital no modo de teste


Recentemente, através de uma varredura de fatias de cristal com uma ampliação de 20x, um modo de teste secreto foi descoberto nos chips PPU do console do SNES. Se você fizer uma pequena modificação no hardware, a PPU começará a emitir um sinal RGB digital de 15 bits !

Isso é quase o que precisamos! No entanto, este modo apresenta problemas, porque o famoso modo 7 não pode exibir a imagem correta. Parece que esta função não foi totalmente concluída.

Além disso, para implementar esse método, a modificação manual dos consoles SNES e um mecanismo apropriado para capturar e analisar a saída no modo de teste ainda são necessários. No entanto, diferentemente de uma solução com a captura de um sinal RGB analógico, esse sinal digital pode ser testado automaticamente, o que nos permite concluir rapidamente uma grande quantidade de trabalho em engenharia reversa de PPU.

Risers


Como as PPUs são estáticas, podemos remover os chips PPU de um console SNES em funcionamento e conectá-los a uma placa de prototipagem ou a uma placa de circuito personalizada juntamente com dois chips VRAM. Depois disso, você pode colocar um microcontrolador entre a PPU e a interface USB e conectar a interface ao PC, o que permitirá ao codificador programar todos os registros de memória de vídeo externos e PPUs. Além disso, o codificador poderá controlar manualmente os ciclos do relógio da PPU e ler os sinais resultantes nos conectores de E / S, registradores e na memória da PPU em cada ciclo do relógio.

Ao modificar o emulador de software para gerar os mesmos valores internos dos conectores de E / S, podemos comparar diretamente o hardware real com a emulação, mesmo em tempo real. No entanto, isso será um trabalho muito difícil, porque ainda não podemos ver as operações internas da PPU.

Remoção da tampa


Por fim, a solução mais extrema é estudar mais o cristal removendo a tampa do chip. Já temos varreduras de cristal com uma ampliação de 20x, mas sua resolução não é suficiente para analisar e recriar circuitos lógicos individuais, como foi feito no projeto Visual 6502 . Se conseguirmos obter as varreduras de cristal das duas PPUs com uma ampliação de 100x, podemos começar o trabalho duro de compilar circuitos PPU e convertê-los em tabelas de conexão ou código VHDL. Em seguida, eles podem ser usados ​​diretamente no FPGA, bem como portados para C ++ ou outra linguagem de programação, aplicável para a criação de emuladores de software.

Um especialista que havia feito isso antes me deu uma estimativa aproximada: levaria cerca de 600 horas para mapear as duas PPUs. Essa tarefa é muito mais alta do que o nível de "vamos coletar dinheiro através da captação de recursos e pagar alguém" e, idealmente, se enquadra na categoria "esperamos que alguém muito talentoso, com um conjunto único de habilidades, queira nos ajudar voluntariamente".

Obviamente, isso não significa que eu não ficaria feliz em recompensar alguém financeiramente por sua ajuda; posso pagar pelos detalhes e pelo trabalho necessários.

Peça ajuda


Para resumir: fui o mais longe possível no meu projeto de emulador SNES e preciso de ajuda para concluir esta tarefa final. Se você leu até o fim, pode querer ajudar! Qualquer suporte, incluindo a participação no projeto bsnes no GitHub , ou qualquer documentação de pesquisa sobre a operação interna de chips PPU, será inestimável para nós!

Obrigado pela leitura e pelo seu apoio! Foi uma honra para mim, durante quinze anos, ser membro da comunidade de emulação do SNES.

All Articles