Gerência de Memória no MSX (I) Resumo O objetivo deste artigo é mostrar como a memória é distribuída no MSX, desde a ROM, passando pelos cartuchos e chegando a RAM. São 2 artigos, onde o primeiro fala sobre slots primários e o segundo sobre slots secundários, incluindo a Mapper e Megaram. 1- Introdução Sabemos que a memória do MSX tem 64 Kb. Mas a ROM tem 32 Kb, a RAM 64, fora os cartuchos. Não são mais de 64 Kb? A resposta é sim. Então, como o Z-80 pode acessar diretamente mais de 64 Kb? A resposta é ele não acessa! De fato, o Z-80 só enxerga 64 Kb. Para acessar todas estas memórias, será necessário comutar (trocar) a memória ativa para outra que se deseje acessar. Por exemplo, o Z-80 deixa de enxergar a ROM e passa a enxergar a RAM. Para facilitar este mecanismo, os projetistas do MSX dividiram a memória em 4 partes, chamadas de páginas e cada fatia desta pode ser comutada independentemente das outras. A figura 1.1 mostra a memória principal, que é enxergada pelo Z-80. A vantagem desta divisão será comprovada mais adieante. Fig.1.1 - Memória principal As páginas são numeradas de 0 a 3, têm 16 Kb cada uma e seus limites estão expressos na figura 1.1. Mas, quantas memórias de 64 Kb podem ser compartilhadas na memória principal? Podemos compartilhar 4 memórias. Cada memória é chamada de slot, e varia de 0 a 3. No MSX 1, Expert, a configuração de slots é dada, conforme a figura 1.2. Fig.1.2 - Configuração dos slots no Experts Esta configuração pode variar em outros modelos de MSX, mas a ROM está sempre no slot 0. 2- Chaveamento de memória Cada página da memória principal pode enxergar somente a região de memória de qualquer um dos 4 slots correspondentes ao seus limites. Por exemplo, a página 0 enxerga de 0000 a 3FFF de qualquer um dos 4 slots, a página 1 enxerga de 4000 a 7FFF de qualquer um dos 4 slots, etc. Fica claro aqui que não existe uma quinta memória de 64 Kb. A memória principal referida aqui é o que o Z-80 está enxergando, conforme o chaveamento da memória. As memórias são somente os 4 slots físicos. Foi dito na seção anterior, que podemos chavear as páginas independentemente umas das outras. De fato podemos ter a página 0 enxergando o slot 0, a página 1 enxergando o slot 3, a página 2 enxergando o slot 1 e a página 3 enxergando o slot 2. Afinal, como eu posso chavear a memória, para que cada página enxergue a fatia de memória do slot desejado? O chip PPI é o responsável por chavear a memória. A porta A8 controla o chaveamento conforme a figura 2.1. Fig.2.1 - Porta A8 A porta A8 é um número de 8 bits, onde os 2 primeiros indicam qual slot a página 3 enxerga, os 2 seguintes indicam qual slot a página 2 enxerga, etc. Se lermos esta porta e o resultado for 128 (&B10000000), temos que a página 3 está enxergando o slot 2 (&B10) e as demais estão enxergando o slot 0. Quando damos a partida no Expert, a memória é configurada conforme a figura 2.2. Os 32 Kb de ROM ficam nas páginas 0 e 1 e 32 Kb de RAM ficam nas páginas 2 e3. Portanto, a porta A8 tem o seguinte valor: 160 (&B10100000) Fig2.2 - Configuração inicial do Expert. Quando mudamos o slot de uma página, estamos mudando todo o acesso à memória nos limites correspondentes aquela página. Dizemos para o sistema: “esquece o slot X e use o slot Y”. Portanto, todo cuidado é pouco! Este assunto será abordado adiante. 3- O Perigo da Troca de Slots Nesta seção, será abordado como é percorrida a memória, no par busca-execução em assembly. Assim como no Basic, quando nosso programa roda, ele começa em uma dada posição e, caso não haja desvios (goto, then <linha>, return, etc), segue para a posição logo abaixo. No caso específico do assembly, a busca consome n bytes (referentes a instrução, onde normalmente são 1, 2 ou 3 bytes) e a próxima instrução será executada em p+n. Seja o programa abaixo: 1800 1802 1803 3E C8 80 92 LD A,C8 ADD A,B SUB D Se o programa começar em 1800, irá executar a instrução LD A,C8. A próxima instrução será em 1802 e depois em 1803. Se estivermos em 1800 e a página que for trocada for a mesma que o programa estiver rodando, neste caso a página 0, todo conteúdo de memória entre 0000 e 3FFF será trocado para o conteúdo de memória do novo slot. Com isso, a linha 1802 não será mais ADD A,B e sim o que estiver na página, no momento da troca. E esta será a próxima linha de execução. Como o programa rodará a partir da posição 1802 da nova página, buscará as instruções relativas ao que for encontrado lá, e dali em diante. Portanto, estaremos perdendo o controle de nosso programa, pois a página inicial não voltará sozinha! Para fazer leituras em páginas de outro slot, devemos estar fora da página a ser trocada. 4- Estratégia de jogos e cartuchos A BIOS contém instruções básicas, como escrever na tela, som, sprites, joystick etc, já prontas de fábrica para que o usuário não se preocupe em gastar tempo para fazê-las e nem espaço em seu programa com elas. A BIOS está na ROM, na página 0. Quando programamos em Basic, precisamos das páginas 0 e 1 da ROM, que contém este interpretador. Porém, quando rodamos em assembly, o máximo que iremos precisar é da BIOS, que está integralmente na página 0. Caso você queira, você poderá colocar seu programa na página 0, mas deverá fazer todos os acessos diretos aos chips de som, vídeo, PPI etc, sem utilizar a BIOS. Normalmente, os jogos em assembly utilizam a memória a partir da posição 4000H, ou seja, página 1. Isto, porque este programa não precisa do interpretador Basic. Os jogos utilizam uma estratégia simples para se carregar em tais posições. Quando chamados do Basic, são carregados na página 2 (RAM usada pelo Basic), são executados a partir de um trecho perto do final do bloco, trocando o slot 1 da página 1 para RAM e copiando o bloco de programa do jogo para o endereço 4000H. Caso a página já seja RAM, basta mover o bloco para a posição desejada. A figura 4.1 mostra como é um arquivo de um bloco de um jogo. A figura 4.2 mostra como o bloco é carregado na memória e copiado. Fig.4.2 - Arquivo de jogo Fig.4.2 Esquerda: Carregamento pelo Basic. Trecho verde roda, trocando slot da página 1 de 0 para 2. Direita: trecho verde copia bloco amarelo para página 1. O bloco em amarelo tem exatos 16 Kb (quando não é o último). Se o segundo bloco for carregado na página 2, basta que o trecho em verde da figura 4.2 apenas copie o bloco amarelo para a posição 8000H. Ao final, deverá pular para a posição 4000H para que o jogo se inicie. O jogo na verdade são os 32 Kb, desde a posição 4000H. Se o primeiro bloco do jogo for carregado no Basic sem a extensão “,r”, obviamente, o jogo não rodará. Para testar o que foi dito, vejamos o que os dois blocos do jogo Konami’s Soccer faz. Primeiramente, são apresentados os blocos, se lidos em um editor hexadecimal. Bloco 1: 000000 000010 000020 000030 000040 000050 000060 000070 000080 000090 0000A0 0000B0 0000C0 0000D0 0000E0 0000F0 000100 000110 000120 FE 00 E0 00 46 CD C3 5C 03 0C 9B ED E0 03 E0 CF E0 41 69 00 00 00 00 20 3E 47 40 4F B1 FD B0 CD E0 79 41 1F 10 48 90 00 00 C3 0D 01 00 5E 06 26 31 3E 3E 34 FE 08 D0 0C CD 60 00 00 C4 34 B7 85 23 00 80 80 01 01 3A 03 42 CD 21 59 D0 00 00 4B FB F3 6F 56 21 CD F3 32 FB 02 30 CD D8 04 46 00 00 00 CD CD FC D0 EB C1 24 21 05 18 E0 01 42 4B E0 CD D0 00 00 3E 87 16 24 E9 FC 00 00 E0 FE E6 E5 DD C0 35 27 41 41 00 01 48 4F C9 F3 09 3E E0 CD 00 40 CD 42 11 C0 4A 42 42 00 F3 CD FB 83 ED B6 C3 11 56 00 20 66 E8 2A CD CD 70 07 00 CD C8 C9 5F 56 4F 32 01 48 00 03 40 43 49 A1 B6 40 32 00 16 40 00 D0 CD 23 9A E0 CD 00 21 F5 2A CD 4C 4B 00 00 05 4F 3E 00 14 38 23 FD 01 33 00 55 40 45 A0 AF C3 00 67 00 21 00 00 C9 01 23 21 7F 5A C3 45 27 10 47 C3 CA 00 00 00 05 32 00 E1 0F 23 2E 13 AF 4C ED 41 13 AF F3 41 00 E0 00 E0 05 00 87 0F 7E 40 36 32 4E 4B A2 3A C3 41 CD 00 02 00 CB E0 00 CD E6 E6 22 00 05 21 00 41 03 C7 CD 94 ···`···ABp@····· ·······AB·2·g··· ················ ····K·>····O!··· F ·4···H··@>·2·· ·>·····O········ ·G··o·$··_······ \@^#V····V·8···· ·O··!····O####~· ··&··$·>·2··!.@" ··1··!······•·6· ··>·2···VH·3Z·2· ··>··········LN! ··4:···@ ·!UE·K· ·y··0···f@·@'A·A ·A·B·B·B·C*E··:· ·····K··*I··G··· A··!··5···L···A· iH·YF·'J··K··A·· FE FF 09 35 F8 F8 F8 F8 FB FB FF F7 17 0D E5 F8 F8 F8 F8 00 EF 82 80 00 00 00 00 FB FB FC 00 FE 00 80 00 00 00 00 90 EB 5D 75 5C 5C 5C 5C D4 D4 7C 78 94 A4 75 6C 6C 6C 6C 57 F7 82 80 02 02 02 02 03 03 03 02 04 03 80 02 02 02 02 D0 F6 B1 85 F8 F8 F8 F8 08 08 07 FF FD 18 85 F8 F8 F8 F8 00 EA 82 80 00 00 00 00 00 00 00 FC FC 00 80 00 00 00 00 D0 21 05 95 60 60 60 60 CC CC 84 7C 98 A0 95 74 74 74 74 E3 80 83 80 03 03 03 03 04 04 04 03 03 04 80 03 03 03 03 01 21 35 A9 08 08 08 08 0D 0D 10 07 F7 E5 A9 08 08 08 08 F5 80 80 80 FE 00 FC FF 00 00 FC FD 00 80 80 FE FA FB FA 01 D1 45 BD 64 80 88 90 D8 D8 80 90 78 F5 BD 64 AC A0 98 FF 80 80 80 03 03 03 03 03 03 03 04 02 80 80 03 03 03 03 FF 25 55 08 08 08 08 F8 F8 F7 17 0F 08 05 08 08 08 08 39 01 81 80 FE 00 FC 00 F8 F8 00 00 FD FF 81 FE FB F9 FC 81 01 79 65 68 84 8C 94 D0 E0 78 88 8C 9C 15 68 A8 B0 BC 49 01 81 80 04 04 04 04 02 02 02 04 03 04 81 04 04 04 04 81 ···W············ ······!·!···%·y· ··]·····5·E·U·e· 5·u···········h· ··\···`···d····· ··\···`········· ··\···`········· ··\···`········· ················ ··············x· ··|············· ··x···|········· ··········x····· ················ ··u···········h· ··l···t···d····· ··l···t········· ··l···t········· ··l···t·····9·I· Bloco 2 006000 006010 006020 006030 006040 006050 006060 006070 006080 006090 0060A0 0060B0 0060C0 0060D0 0060E0 0060F0 006100 006110 006120 Depois de carregados, inspecionamos a memória do MSX, através do debug do BrMSX, para compararmos os resultados: +¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ ¦ +- BrMSX debugger -------------------------- SCC ------- Stack ---- VDP BASE() + ¦ ¦ ¦ 4000 BE 42 70 40 00 00 00 00 .Bp@.... ¦ F1 0000 ¦ SP+6 E077 Name 3800 ¦ 4008 00 00 00 00 00 00 00 00 ........ ¦ F2 0000 ¦ SP+4 5A76 Pattrn 2000 ¦ 4010 41 42 07 32 00 67 00 E0 AB.2.g.. ¦ F3 0000 ¦ SP+2 40BE Color 0000 ¦ 4018 02 E0 00 00 00 00 00 00 ........ ¦ F4 0000 ¦ SP A8D3 SpAttr 3B00 ¦ 4020 00 00 00 00 05 00 00 00 ........ ¦ F5 0000 ¦ SP-2 185E SpImag 1800 ¦ 4028 00 00 00 C3 C4 4B CD 3E .....K.> ¦ V1 00 0 ¦ SP-4 D303 ¦ 4030 01 F3 CD 16 4F 21 05 E0 ....O!.. ¦ V2 00 0 ¦ SP-6 73A8 Screen 2 ¦ 4038 CB 46 20 0D 34 FB CD 87 .F .4... ¦ V3 00 0 +- Advanced Info --------¦ 4040 48 CD C8 40 3E 00 32 05 H..@>.2. ¦ V4 00 0 ¦ ¦ 4048 E0 CD 3E 01 B7 F3 FC 16 ..>..... ¦ V5 00 0 ¦ Clocks Left 0E90B ¦ 4050 4F FB C9 00 00 00 00 00 O....... ¦ ¦ Interrupt Pending NO ¦ 4058 00 C3 47 00 85 6F D0 24 ..G..o.. +- Drive ---¦ VDP Waiting NO ¦ 4060 C9 83 5F D0 14 C9 E1 87 .._..... ¦ D0 D0 ¦ VDP Low Byte 6C ¦ 4068 CD 5C 40 5E 23 56 EB E9 .\@^#V.. ¦ D1 00 ¦ VDP Lookahead 20 ¦ 4070 F3 ED 56 CD 38 01 0F 0F ..V.8... ¦ D2 09 ¦ VDP Address 3974 ¦ 4078 E6 03 4F 06 00 21 C1 FC ..O..!.. ¦ D3 00 ¦ VDP Access Write ¦ ¦ D4 61 ¦ VDP Status Byte 04 ¦ BrMSX settings -------------------------------------------------------------¦ ¦ Resolution: 320x200 Frame skipping: 0001 Bar Graph: OFF Emulation: NORMAL ¦ ¦ Joy: - Sound: ON VSync: OFF Session: SINGLE COM: 1 Video Cache: ON ¦ ¦ Command: SCC: OFF ¦ +------------------------------------------------------------------------------+ +- BrMSX debugger -------------------------- SCC ------- Stack ---- VDP BASE() + ¦ ¦ ¦ ¦ ¦ 8000 E3 01 F5 01 FF FF 01 01 ........ ¦ F1 0000 ¦ SP+6 E077 Name 3800 ¦ ¦ 8008 01 FF EF EB F7 F6 EA 21 .......! ¦ F2 0000 ¦ SP+4 5A76 Pattrn 2000 ¦ ¦ 8010 80 21 80 D1 80 25 81 79 .!...%.y ¦ F3 0000 ¦ SP+2 40BE Color 0000 ¦ ¦ 8018 81 09 82 5D 82 B1 82 05 ...].... ¦ F4 0000 ¦ SP A8D3 SpAttr 3B00 ¦ ¦ 8020 83 35 80 45 80 55 80 65 .5.E.U.e ¦ F5 0000 ¦ SP-2 185E SpImag 1800 ¦ ¦ 8028 80 35 80 75 80 85 80 95 .5.u.... ¦ V1 00 0 ¦ SP-4 D303 ¦ ¦ 8030 80 A9 80 BD 80 08 FE 68 .......h ¦ V2 00 0 ¦ SP-6 73A8 Screen 2 ¦ ¦ 8038 04 F8 00 5C 02 F8 00 60 ...\...` ¦ V3 00 0 +- Advanced Info --------¦ ¦ 8040 03 08 FE 64 03 08 00 84 ...d.... ¦ V4 00 0 ¦ ¦ ¦ 8048 04 F8 00 5C 02 F8 00 60 ...\...` ¦ V5 00 0 ¦ Clocks Left 0E90B ¦ ¦ 8050 03 08 00 80 03 08 FC 8C ........ ¦ ¦ Interrupt Pending NO ¦ ¦ 8058 04 F8 00 5C 02 F8 00 60 ...\...` +- Drive ---¦ VDP Waiting NO ¦ ¦ 8060 03 08 FC 88 03 08 00 94 ........ ¦ D0 D0 ¦ VDP Low Byte 6C ¦ ¦ 8068 04 F8 00 5C 02 F8 00 60 ...\...` ¦ D1 00 ¦ VDP Lookahead 20 ¦ ¦ 8070 03 08 FF 90 03 F8 F8 D0 ........ ¦ D2 09 ¦ VDP Address 3974 ¦ ¦ 8078 02 FB FB D4 03 08 00 CC ........ ¦ D3 00 ¦ VDP Access Write ¦ ¦ ¦ D4 61 ¦ VDP Status Byte 04 ¦ +- BrMSX settings -------------------------------------------------------------¦ ¦ Resolution: 320x200 Frame skipping: 0001 Bar Graph: OFF Emulation: NORMAL ¦ ¦ Joy: - Sound: ON VSync: OFF Session: SINGLE COM: 1 Video Cache: ON ¦ ¦ Command: SCC: OFF ¦ +------------------------------------------------------------------------------+ 5- Conclusões O jogo apresentado, não utiliza nenhum acesso à ROM na página 1. Logo, o problema de troca de slots não existe aqui. Este problema nem mesmo existe no carregamento do bloco, pois a cópia se dá da página 2 para a 1. Se estivéssemos na página 1 e trocássemos o slot, aí sim o programa perderia o controle. Caso desejássemos usar a página 0 para nosso programa, poderíamos chamar a BIOS da página 3, por exemplo. Isto é, desviaríamos nosso programa para um endereço maior que C000H e de lá chamaríamos a BIOS, antes comutando a página 0. O retorno seria para a página 3 (que não foi comutada) e voltaríamos a página 0 para RAM. Em seguida, um RET retornaria para a posição da página 0, corretamente setada para RAM. Este truque pode ser feito em qualquer página diferente da 0. A sugestão da página 3 se deve ao fato de que, provavelmente, seu programa já terminou muito antes e que seja usada uma área livre de memória. 6- Créditos Este artigo foi escrito por Marcelo Teixeira Silveira, engenheiro de sistemas e computação formado pela UERJ, Universidade do Estado do Rio de Janeiro, Brasil. http://www.marmsx.cjb.net