
Este post é uma introdução ao meu projeto de consoles de vídeo em console "caseiros", feitos a partir do zero. Eu fui inspirado por consoles retrô e amostras modernas, mas consegui minha própria arquitetura. Meus amigos sempre me diziam que eu deveria falar sobre o meu projeto, e não fazer tudo exclusivamente "por mim", então aqui estou publicando este post.
Atenção, esta é uma tradução
Como tudo começou
Chamo-me Sergio Vieira, cresci em Portugal nas décadas de 80 e 90, tenho uma longa nostalgia pelo jogo retro, especialmente pelos consoles de terceira e quarta geração.
Há alguns anos, decidi entender melhor a eletrônica e tentar criar meu próprio prefixo.
Por profissão, sou programador e não tinha nenhuma experiência como engenheiro eletrônico, exceto por (e não deve ser considerado) atualizações independentes do meu destkop.
Embora eu não tivesse experiência, me disse "por que não?", Comprei vários livros, vários kits eletrônicos e comecei a estudar com base em meus sentimentos sobre o que valia a pena estudar.
Eu queria criar um prefixo semelhante àqueles que me causam sentimentos nostálgicos, queria algo entre NES e Super Nintendo , ou talvez entre o Sega Master System e o Mega Drive .
Esses consoles tinham uma CPU, um chip de vídeo original (eles não eram chamados de GPU na época) e um chip de áudio, às vezes embutido e às vezes externo.
Os jogos foram distribuídos em cartuchos, que em geral eram extensões de ferro, às vezes apenas chips de ROM e às vezes possuíam componentes adicionais.
O plano original era criar um prefixo com as seguintes características:
- Sem emulação, jogos e programas devem funcionar em hardware real, não necessariamente o mesmo daqueles tempos, mas rápido o suficiente para a tarefa e nada mais.
- Com uma CPU retro real.
- Com saída de TV analógica.
- Com som
- Com suporte para controlador duplo
- Becks de rolagem e sprites de animação
- Com recursos para suportar jogos de plataforma como Mario e, claro, todos os tipos de outros jogos.
- Com o download de jogos e programas de cartões SD.
Por que cartões SD, e não cartuchos, bem, basicamente é muito mais prático, você pode copiá-los do seu computador. E cartuchos significariam, em primeiro lugar, mais ferro no decodificador e, em segundo lugar, produzir ferro para cada programa.
Produção
Sinal de vídeo
A primeira coisa que fiz foi gerar um sinal de vídeo.
Qualquer console do período que tirei como amostra tinha vários chips gráficos proprietários, o que significa que todos tinham especificações diferentes.
Por esse motivo, não queria usar um chip gráfico pronto, queria que meu console tivesse especificações gráficas exclusivas. E como não conseguia fabricar meu próprio chip gráfico e, na época, ainda não conseguia usar o FPGA, decidi me limitar à geração de sinal gráfico gerada por software usando um microcontrolador de 8 bits e 20 megahertz.
Isso não é demais, e apenas uma solução poderosa o suficiente para gráficos do nível em que eu estava interessado.
Então comecei a usar o microcontrolador Atmega644 a uma pureza de 20 MHz para gerar um sinal de vídeo PAL para a TV. Eu tive que superar o protocolo PAL porque o próprio chip não sabe como fazê-lo.


O microcontrolador produz uma cor de 8 bits (RGB332, 3 bits vermelho, 3 bits verde e 2 azul) e o DAC passivo converte tudo em RGB. Felizmente em Portugal, quase todas as TVs estão equipadas com um conector SCART e suportam entrada RGB.
O subsistema gráfico correto
Como o microcontrolador é bastante poderoso, e eu decidi usá-lo exclusivamente para gerar um sinal de vídeo (eu o chamei de VPU - Video Processing Unit), então decidi organizar um buffer duplo ao mesmo tempo.
Aconteceu que o segundo microcontrolador (PPU, Picture Processing Unit, chip Atmega1284 também a 20 MHz) gerou uma imagem no chip de RAM 1 (eu o chamei de VRAM1), e o primeiro enviou o conteúdo do segundo chip (VRAM2) para a TV ao mesmo tempo.
Após um quadro e dois quadros no sistema PAL serem de 1/25 de segundo, o VPU alterna os VRAMs e eles são trocados, o PPU gera uma imagem em VRAM2 e o VPU despeja VRAM1 na saída da TV.
A placa de vídeo acabou sendo muito complicada, porque eu tive que usar hardware externo para que ambos os microcontroladores pudessem usar os dois módulos de memória e acelerar o acesso à RAM, porque ela também possui batidas de bits, então eu tive que adicionar 74 chips da série como contadores, seletores de linha, transceptores etc. .
O firmware para VPU e PPU também se mostrou complicado, porque eu tive que escrever muito código para obter a velocidade máxima dos gráficos. No início, tudo foi escrito em assembler, depois parte foi reescrita em C.


Como resultado, a PPU gera uma imagem de 224x192 pixels, que é então enviada à TV via VPU. Você pode encontrar uma resolução baixa, mas na verdade é quase o mesmo que os consoles da época tinham, geralmente 256x224. Uma resolução um pouco mais baixa, mas me permitiu adicionar mais recursos que o sistema consegue calcular em um quadro.
Como nos velhos tempos, a PPU possui sua própria mecânica rígida que você deve poder usar. O apoio (apoio) é renderizado a partir de caracteres de 8x8 pixels, também chamados de blocos. Acontece que o tamanho do plano de fundo é de 28x24 peças.
Para que o suporte possa rolar suavemente, pixel por pixel, eu fiz isso. Existem quatro telas virtuais, cada uma das peças 28x24 que ficam na memória sequencialmente e são enroladas uma na outra, na imagem fica mais clara.


No topo do plano de fundo, o PPU pode renderizar 64 sprites com 8 ou 16 pixels de altura ou largura, ou seja, 1, 2 ou 4 blocos e também podem ser invertidos horizontalmente e / ou verticalmente.
Na parte de trás, também é possível renderizar uma sobreposição com um buffer de 28x6 peças, destinado a renderizar HUDs, pontuações para não interferir nos sprites principais e na rolagem das costas.
Um recurso "avançado" é que o suporte pode ser rolado não inteiramente, mas cada linha separadamente, o que permite todos os tipos de efeitos interessantes, como telas divididas ou quase paralaxe .
Há também uma tabela de atributos que permite definir cada bloco com um valor de 0 a 3 e, em seguida, você pode especificar uma página de blocos para todos os blocos com um atributo ou incrementar seu valor simbólico. Isso é conveniente quando há partes do backup que precisam ser alteradas regularmente e a CPU não precisa calcular cada bloco individualmente, basta dizer algo como: "todos os blocos com atributo 1, aumentam o valor numérico do seu personagem em 2", tais coisas podem ser implementadas por diferentes técnicas observe, por exemplo, em blocos de blocos em Mario, onde o ponto de interrogação é animado ou em jogos onde há uma cachoeira na qual todos os blocos estão mudando constantemente, criando o efeito de queda de água.
CPU
Quando minha placa de vídeo funcionou, comecei a trabalhar com a CPU como Zilog 80 para meu decodificador.
Uma das razões pelas quais o Z80 foi escolhido, além de ser uma CPU retro legal, é a capacidade de endereçar dois espaços de 16 bits, um para memória e o segundo para portas de E / S, o menos lendário 6502 , por exemplo, não pode , ele pode endereçar apenas espaço de 16 bits e é necessário mapeá-lo na memória, além de vários dispositivos externos, vídeo, áudio, joysticks, gerador de números aleatórios de hardware etc. É mais conveniente ter dois espaços de endereço, um totalmente fornecido até 64 kilobytes de código e dados na memória e o segundo para acesso a dispositivos externos.
Primeiro, conectei a CPU à EEPROM na qual meu programa de teste estava localizado e também a conexão através do espaço de E / S ao microcontrolador que instalei para que eu pudesse me comunicar com o meu computador via RS232 e monitorar como a CPU e tudo o mais funcionava. Esse microcontrolador Atmega324, operando a 20 MHz, chamo de unidade de microcontrolador IO MCU - entrada / saída, é responsável por controlar o acesso a controladores de jogos (joysticks), leitor de cartões SD, teclado PS / 2 e comunicador via RS232.

A CPU se conecta a um chip de memória de 128 kilobytes, dos quais apenas 56 kilobytes estão disponíveis, é claro que isso não faz sentido, mas eu poderia obter apenas chips de 128 ou 32 kilobytes. Descobriu-se que a memória consiste em 8 kilobytes de ROM e 56 kilobytes de RAM.
Depois disso, atualizei o firmware do IO MCU usando esta biblioteca e obtive suporte para leitores de cartão SD.
Agora a CPU pode percorrer os diretórios, ver o que está neles, abrir e ler arquivos. Tudo isso é feito escrevendo e lendo para endereços específicos no espaço de E / S.
Conecte a CPU à PPU
A próxima coisa que fiz foi a conexão entre a CPU e a PPU. Para fazer isso, apliquei uma "solução simples" que era comprar RAM de porta dupla, esse é um chip de RAM que pode ser conectado diretamente a dois barramentos diferentes. Isso permite que ele se livre de chips adicionais, como seletores de linha, e, além disso, permite acesso quase simultâneo à memória de ambos os chips. Outra PPU pode acessar diretamente a CPU em cada quadro, ativando suas interrupções não mascaráveis . Acontece que a CPU recebe uma interrupção em cada quadro, o que é útil para várias tarefas de temporização e para entender quando é hora de fazer uma atualização gráfica.
Cada quadro de interação da CPU, PPU e VPU ocorre de acordo com o seguinte esquema:
- A PPU copia as informações da memória da PPU para a memória interna.
- A PPU envia um sinal de interrupção para a CPU.
- Ao mesmo tempo:
- A CPU salta para a função de interrupção e começa a atualizar a memória PPU com um novo estado gráfico. O programa deve retornar da interrupção até o próximo bloco.
- O PPU renderiza uma imagem com base nas informações previamente copiadas para um dos VRAM.
- O VPU envia uma imagem de outro VRAM para a saída da TV.
Na mesma época, comecei a dar suporte a controladores de jogos, no começo eu queria usar os controles da Nintendo, mas seus soquetes são proprietários e geralmente difíceis de encontrar, então decidi usar controladores de 6 botões compatíveis com Mega Drive / Genesis, pois eles têm soquetes DB-9 padrão que estão por toda parte.

Escrevendo o primeiro jogo real
Naquele momento, eu já tinha uma CPU capaz de controlar PPU, trabalhando com joysticks, lendo cartões SD ... era hora de escrever o primeiro jogo , é claro, no montador Z80, levava vários dias do tempo livre.
Adicionar gráficos dinâmicos
Tudo estava super, eu tinha meu próprio console de jogo, mas isso não foi suficiente para mim, porque no jogo eu tive que usar gráficos costurados na memória PPU e era impossível desenhar peças para um jogo específico, e isso só podia ser alterado com a atualização da ROM. Comecei a pensar em como adicionar mais memória para que a CPU carregasse caracteres para os blocos e, em seguida, a PPU poderia ler tudo a partir daí e como fazê-lo mais facilmente, porque o prefixo já era complicado e grande.
E eu vim com o seguinte: apenas a PPU terá acesso a essa nova memória e a CPU carregará dados lá através da PPU e, enquanto esse processo de carregamento estiver em andamento, essa memória não poderá ser usada para desenhar, mas será possível desenhar a partir da ROM nesse momento.
Após o término do carregamento, a CPU mudará a memória ROM interna para essa nova memória, que chamei de RAM de caracteres (CHR-RAM) e, nesse modo, a PPU começará a desenhar gráficos dinâmicos, provavelmente essa não é a melhor solução, mas funciona. Como resultado, uma nova memória foi instalada com 128 kilobytes e pode armazenar 1024 caracteres de 8x8 pixels cada para o plano de fundo e o mesmo número de caracteres para sprites.

E finalmente o som
As mãos alcançaram o som por último. No começo, eu queria um som como o que está no Uzebox , ou seja, que o microcontrolador gera 4 canais de som PWM.
No entanto, descobri que posso obter facilmente os chips antigos e pedi vários chips de síntese FM YM3438, esses caras são totalmente compatíveis com o YM2612 usado no Mega Drive / Genesis. Ao instalá-los, você pode obter Mega Drive de música de qualidade e efeitos sonoros produzidos pelo microcontrolador.
Eu instalei outro microcontrolador e chamei de SPU (Sound Processor Unit), ele controla o YM3438 e pode gerar sons sozinho. A CPU controla-o através de uma memória de porta dupla, desta vez com apenas 2 kilobytes.
Como na unidade gráfica, a unidade de som possui 128 kilobytes de memória para armazenar amostras de PCM e patches de som, a CPU carrega dados nessa memória acessando a SPU. Acontece que a CPU diz à SPU para executar comandos a partir dessa memória ou atualiza os comandos para a SPU a cada quadro.
A CPU controla quatro canais PWM através de quatro buffers circulares na memória da SPU. O SPU passa por esses buffers e executa os comandos gravados neles. Há também um desses buffer para o chip de síntese FM.
No total, como no gráfico, a interação entre a CPU e a SPU segue o esquema:
- A SPU copia dados da SPU para a memória interna.
- O SPU está aguardando uma interrupção do PPU (isto é para sincronização)
- Ao mesmo tempo
- A CPU atualiza os buffers de canal PWM e os buffers de sintetizador de FM.
- A SPU executa comandos em buffers de acordo com os dados na memória interna.
- Junto com tudo isso, o SPU atualiza os sons PWM com uma frequência de 16 kilohertz.

O que saiu no final
Depois que todos os blocos estavam prontos, alguns foram para a tábua de pão.
No bloco da CPU, consegui desenvolver e solicitar uma placa de circuito impresso personalizada, não sei se vale a pena para os outros módulos, acho que tive muita sorte por minha placa de circuito impresso funcionar imediatamente.
Na tábua de pão agora (até agora) há apenas som.
Veja como está hoje:

Arquitetura
O diagrama ilustra os componentes em cada bloco e como eles interagem entre si. A única coisa que não é mostrada é o sinal da PPU para a CPU em cada quadro como uma interrupção e o mesmo sinal que vai para a SPU.

- CPU: Zilog Z80 a 10 MHz
- CPU-ROM: 8KB EEPROM, contém código do carregador de inicialização
- CPU-RAM: 128KB RAM (56KB disponível), código e dados para programas / jogos
- IO MCU: Atmega324, é a interface entre a CPU e o RS232, o teclado PS / 2, joysticks e o sistema de arquivos do cartão SD
- PPU-RAM: 4 kilobytes de memória de porta dupla, memória intermediária entre CPU e PPU
- CHR-RAM: 128KB de RAM, armazena blocos dinâmicos para suporte (substrato) e sprites (em caracteres de 8x8 pixels).
- VRAM1, VRAM2: 128KB RAM (43008 está realmente disponível), eles são usados para o buffer de quadros, escrevem PPU e lêem VPU a partir deles.
- PPU (Picture Processing Unit): Atmega1284, desenha um quadro no buffer de moldura.
- VPU (Unidade de processamento de vídeo): Atmega324, lê o buffer de quadros e gera sinal e sincronização RGB e PAL.
- SPU-RAM: RAM de 2 portas de 2 KB, serve como interface entre a CPU e a SPU.
- SNDRAM: 128KB RAM, armazena patches PWM, amostras PCM e blocos de instruções para o sintetizador de FM.
- YM3438: YM3438, chip de síntese FM.
- SPU (Sound Processing Unit): Atmega644, gera sons usando o princípio de modulação por largura de pulso (PWM) e controla o YM3438.
Especificações finais
CPU:
- CPU de 8 bits Zilog Z80 a uma frequência de 10Mhz.
- ROM de 8 KB para o carregador de inicialização.
- 56KB de RAM.
IO:
- Lendo dados do leitor de cartão SD FAT16 / FAT32.
- Leitura / gravação na porta RS232.
- 2 controladores de jogo compatíveis com MegaDrive / Genesis.
- Teclado PS2.
Vídeo:
- Resolução 224x192 pixels.
- 25 quadros por segundo (meio FPS do PAL).
- 256 cores (RGB332).
- Plano de fundo virtual 2x2 (448x384 pixels), com rolagem bidirecional baseada em pixels, com base em quatro páginas em tela cheia.
- 64 sprites com largura e altura de 8 ou 16 pixels, com a possibilidade de virar vertical e horizontal.
- O plano de fundo e os sprites consistem em caracteres de 8x8 pixels cada.
- Memória de vídeo simbólica de 1024 caracteres para o fundo e 1024 para sprites.
- 64 rolagem horizontal independente ao longo de linhas definidas
- 8 rolagem vertical independente ao longo de linhas definidas
- Sobreposição de 224x48 pixels com transparência opcional das teclas de cores.
- Tabela de atributos em segundo plano.
- RGB e PAL composto via conector SCART.
Som:
- PWM para 8 bits e 4 canais, com formas de onda incorporadas: quadrada, senoidal, serra, ruído, etc.
- Amostras de 8 bits e 8 kHz em um dos canais PWM.
- O chip de síntese FM YM3438 é carregado com instruções na frequência de 50 hertz.
Desenvolvimento para o console
Para o console, um carregador de inicialização foi gravado. O carregador de inicialização é colocado na CPU da ROM e pode levar até 8 kilobytes. Ele usa os primeiros 256 bytes de RAM. O carregador é a primeira coisa que a CPU executa. É necessário mostrar os programas localizados no cartão SD.
Esses programas estão em arquivos que contêm código compilado e também podem conter gráficos e som.
Depois de selecionar um programa, ele é carregado na memória da CPU, memória CHR e memória SPU. Após o qual o código do programa é executado. O tamanho máximo do código carregado no console é de 56 kilobytes, além dos primeiros 256 bytes, e é claro que você precisa levar em consideração o espaço para a pilha e os dados.
E esse carregador de inicialização e outros programas criados para esse console foram criados da mesma maneira descrita abaixo.
Mapeamento de memória / E / S
O que é importante ao desenvolver esse prefixo é levar em consideração como a CPU acessa os vários blocos e aloca corretamente o espaço de endereço para entrada de entrada e espaço de endereço de memória.
A CPU acessa a memória de acesso aleatório do carregador de inicialização através do espaço de endereço da memória.
Espaço de endereço de memória

E para PPU-RAM, SPU-RAM e IO MCU através do espaço de endereço de E / S.
Espaço de endereço de E / S

Como você pode ver na tabela, os endereços para todos os dispositivos, IO MCU, PPU e SPU são alocados dentro do espaço de endereço de E / S.
Gerenciamento de PPU
A partir das informações da tabela, é possível observar que, para o controle PPU, é necessário gravar na memória PPU disponível nos endereços 1000h-1FFFh no espaço de endereço de E / S.
Alocação de espaço de endereço PPU

O status da PPU pode assumir os seguintes valores:
- Modo de gráficos incorporados
- Modo de gráficos dinâmicos (CHR-RAM)
- Modo de gravação na memória CHR
- A gravação está concluída, aguardando a confirmação do modo da CPU
Aqui, por exemplo, como você pode trabalhar com sprites:
O prefixo pode desenhar 64 sprites por vez. CPU - 1004h-1143h (320 ), 5 (5 * 64 = 320):
- , : Active, Flipped_X, Flipped_Y, PageBit0, PageBit1, AboveOverlay, Width16, Height16.
- , ( ).
- ( — )
- X
- Y
, , Active 1, X Y , 32/32 , .
.
Por exemplo, se precisarmos mostrar o sprite número 10, o endereço será 4145 (1004h + (5 x 9)), escreva o valor 1 para ativação e as coordenadas, por exemplo, x = 100 e y = 120, escreva o valor 100 no endereço 4148 e endereço 4149 valor 120.
Usando assembler
Um dos métodos de programação para o console é o assembler.
Aqui está um exemplo de como mostrar um sprite e animá-lo para que ele se mova e afaste as bordas da tela.
ORG 2100h PPU_SPRITES: EQU $1004 SPRITE_CHR: EQU 72 SPRITE_COLORKEY: EQU $1F SPRITE_INIT_POS_X: EQU 140 SPRITE_INIT_POS_Y: EQU 124 jp main DS $2166-$ nmi: ; (NMI) ld bc, PPU_SPRITES + 3 ld a, (sprite_dir) and a, 1 jr z, subX in a, (c) ; X inc a out (c), a cp 248 jr nz, updateY ld a, (sprite_dir) xor a, 1 ld (sprite_dir), a jp updateY subX: in a, (c) ; X dec a out (c), a cp 32 jr nz, updateY ld a, (sprite_dir) xor a, 1 ld (sprite_dir), a updateY: inc bc ld a, (sprite_dir) and a, 2 jr z, subY in a, (c) ; Y inc a out (c), a cp 216 jr nz, moveEnd ld a, (sprite_dir) xor a, 2 ld (sprite_dir), a jp moveEnd subY: in a, (c) ; Y dec a out (c), a cp 32 jr nz, moveEnd ld a, (sprite_dir) xor a, 2 ld (sprite_dir), a moveEnd: ret main: ld bc, PPU_SPRITES ld a, 1 out (c), a ; 0 inc bc ld a, SPRITE_CHR out (c), a ; 0 inc bc ld a, SPRITE_COLORKEY out (c), a ; 0 inc bc ld a, SPRITE_INIT_POS_X out (c), a ; 0 inc bc ld a, SPRITE_INIT_POS_Y out (c), a ; Y 0 mainLoop: jp mainLoop sprite_dir: DB 0
Usando a linguagem C
Você também pode usar a linguagem C, para isso precisamos do compilador SDCC e de alguns utilitários adicionais.
O código C pode ser mais lento, mas a escrita é mais rápida e fácil.
Aqui está um exemplo de código que faz o mesmo que o código do assembler acima, ele usa uma biblioteca que ajuda a fazer chamadas para PPU:
#include <console.h> #define SPRITE_CHR 72 #define SPRITE_COLORKEY 0x1F #define SPRITE_INIT_POS_X 140 #define SPRITE_INIT_POS_Y 124 struct s_sprite sprite = { 1, SPRITE_CHR, SPRITE_COLORKEY, SPRITE_INIT_POS_X, SPRITE_INIT_POS_Y }; uint8_t sprite_dir = 0; void nmi() { if (sprite_dir & 1) { sprite.x++; if (sprite.x == 248) { sprite_dir ^= 1; } } else { sprite.x--; if (sprite.x == 32) { sprite_dir ^= 1; } } if (sprite_dir & 2) { sprite.y++; if (sprite.y == 216) { sprite_dir ^= 2; } } else { sprite.y--; if (sprite.x == 32) { sprite_dir ^= 2; } } set_sprite(0, sprite); } void main() { while(1) { } }
Gráficos dinâmicos
(Nos gráficos personalizados originais. Aprox. Por.)
No prefixo ROM, uma página de blocos para backup e outra página de sprites prontos são costuradas); por padrão, você só pode usar esses gráficos fixos, mas pode mudar para dinâmico.
Meu objetivo era que todos os gráficos necessários em formato binário fossem imediatamente carregados na RAM do CHR, e o código no gerenciador de inicialização da ROM pode fazer isso. Para fazer isso, fiz várias fotos do tamanho correto com diferentes símbolos úteis:

Como a memória de gráficos dinâmicos consiste em 4 páginas com 256 caracteres de 8x8 pixels cada e 4 páginas dos mesmos caracteres para sprites, converti as imagens para o formato PNG, excluindo as duplicadas:

E então ele usou uma ferramenta auto-escrita para traduzir tudo para o formato binário RGB332 com blocos 8x8.

Como resultado, temos arquivos com caracteres, em que todos os caracteres passam sequencialmente um após o outro e cada um ocupa 64 bytes.
Som
Amostras RAW de onda convertidas em amostras PCM de 8 bits e 8 quilohertz.
Patches para efeitos sonoros no PWM e na música são escritos com instruções especiais.
Quanto ao chip de síntese Yamaha YM3438 FM, encontrei um programa chamado DefleMask que produz música sincronizada com PAL para o chip Genesis YM2612, compatível com o YM3438.
O DefleMask exporta músicas no formato VGM e eu as converto com outro utilitário proprietário em meu próprio formato binário.
Todos os binários dos três tipos de som são combinados em um arquivo binário, que meu carregador de inicialização pode ler e carregar na memória de som SDN RAM.

Link para o arquivo final
Código executável binário, gráficos e som são combinados em um arquivo PRG. O arquivo PRG possui um cabeçalho no qual tudo é descrito, se há dados de áudio e gráficos, quanto eles ocupam e os próprios dados.
Esse arquivo pode ser gravado no cartão SD e o carregador de inicialização do console o considera e baixa tudo para os locais apropriados e inicia o código executável do programa.

Emulador
Eu escrevi um emulador do meu console em C ++ usando wxWidgets para facilitar o desenvolvimento para ele.
A CPU é emulada pela biblioteca libz80 .
Recursos foram adicionados ao emulador para depuração, eu posso pará-lo a qualquer momento e fazer a depuração do assembly passo a passo, há um mapeamento para o código-fonte em C. se esse idioma foi usado para o jogo.
De acordo com o gráfico, posso olhar para a memória de vídeo, nas tabelas de símbolos e na própria memória CHR.
Aqui está um exemplo de um programa em execução em um emulador com as ferramentas de depuração ativadas.

Demonstração de programação
Esses vídeos foram filmados com uma câmera de smartphone voltada para a tela CRT da TV, peço desculpas pela qualidade imperfeita da imagem.
O intérprete BASIC programável a partir do teclado PS / 2, após o primeiro programa, mostro como gravar diretamente na memória PPU através do espaço de endereço de E / S, ativando e movendo o sprite:
Uma demonstração dos gráficos, neste vídeo, programaticamente, faça o download de 64 sprites de 16x16, no contexto de um plano de fundo com rolagem dinâmica e uma sobreposição que se move abaixo e acima dos sprites:
A demonstração de som mostra os recursos do som YM3438 e PWM, os dados sonoros dessa demonstração, a música FM e os sons PWM juntos ocupam quase todos os 128 kilobytes disponíveis de memória sonora.
Tetris, quase exclusivamente os recursos de fundo, música no YM3438, efeitos sonoros nos patches PWM foram usados para gráficos.
Conclusão
Este projeto é realmente um sonho tornado realidade, eu trabalho nele há vários anos, com interrupções, olhando para o meu tempo livre, nunca pensei que fosse tão longe na criação do meu próprio console de videogame retro. Naturalmente, não é perfeito, eu certamente não sou um especialista em eletrônica, obviamente havia muitos elementos no decodificador e, sem dúvida, alguém poderia fazer melhor e provavelmente alguns dos leitores estão pensando nisso.
Mas ainda assim, no processo de trabalhar neste projeto, aprendi muito sobre eletrônicos, consoles de jogos e design de computadores, linguagem assembly e outras coisas interessantes e, mais importante, recebi uma grande satisfação jogando jogos que eu mesmo escrevi em hardware que eu mesmo desenvolvi e coletado.
Eu tenho planos para fazer consoles / computadores e muito mais. Na verdade, já estou fazendo um novo decodificador, ele está quase pronto e é um decodificador retro simplificado baseado em uma placa FPGA e em vários componentes adicionais (em uma quantidade muito menor do que neste projeto, certamente), a ideia é ser muito mais barata e repetível.
Embora eu tenha escrito muito sobre esse projeto aqui, sem dúvida muito mais pode ser discutido, eu apenas mencionei como o mecanismo de som funciona, como a CPU interage com ele e muito mais pode ser feito sobre o sistema gráfico e outras entradas / saídas e todo o console seria contar.
Observando a reação dos leitores, posso escrever mais artigos com foco em atualizações, detalhes sobre blocos de prefixos individuais ou outros projetos.
Projetos, sites, canais do Youtube que me inspiraram e me ajudaram com o conhecimento técnico:
Esses sites / canais não apenas inspiraram, mas também me ajudaram a encontrar soluções para problemas complexos que surgiram durante o trabalho neste projeto.
Obrigado por ler até aqui. :)
Se você tiver perguntas ou comentários, escreva nos comentários abaixo (Artigo original em inglês no Github. Aprox. Por.)