Introducció

A l'article "Paginació de la memòria usant MSX-DOS 2" vam comentar com demanar pàgines de RAM al MSXDOS2 de 16k i posar codi en aquests blocs per ser llegits per al Z80. El problema que hi ha, és que els blocs que vas compilant en C han de tenir una estructura d'adreces que no és fàcil d'aconseguir, necessites molts càlculs per saber a on es va posant cada bloc i com definir-los perquè el sdcc els pugui trobar. Una altra solució passa per abandonar el MSXDOS2 i passar-se al format ROM, al de cartutxos del MSX, que en comptes de guardar la informació en el disquet, es guarda a la ROM del cartutx i té un accés molt més ràpid.

Com funcionen els cartuxtos ROM

Tal i com hem explicat en diferents articles, el Z80 pot veure com a màxim 64K que té dividits en 4 pàgines de 16K. Per evitar el límit de 64K, l'estàndard MSX va crear un sistema de slots i subslots de 16K a on pot anar canviant la memòria que veu el Z80 assignant diferents slots a les diferents pàgines. Normalment la pàgina 0 és ocupada per la BIOS, la 1 per la primera ranura de cartutxos, la 2 per la segona ranura de cartutxos i la 3 és per la RAM. Depenent de cada fabricant la posició de la BIOS i la RAM pot anar variant de slot i sub-slot.

Si tu tens un cartutxo de 16K i el poses a la ranura 1, el Z80 veurà aquests 16K a la pàgina 1. Evolucionant la tecnologia per tenir ROMs de més capacitat, es van crear els mappers que són ROMs de més de 16K que tenen diferents segments i una lògica que permet anar canviant els diferents segments que veuen les diferents pàgines que veu el Z80. Aquests mappers no estan estandaritzats i diferents fabricants van fer el seu propi, així tenim: ASCII-8, ASCII-16, Konami5, etc.

La llibreria MSXgl permet programar per diferents mappers. Nosaltres utilitzarem el ASCII-8 a on cada segment és de 8K i és la que també està explicada a la documentació de la MSXgl.

Configuració project_config.js

Per poder utilitzar les facilitats que ens dóna la llibreria MSXgl per crear la ROM, hem de tenir els següents paràmetres activats en el project_config.js

A RawFiles hi ha tots els fitxers binaris que no cal compilar, com ara imatges o sons i que poden anar directament a la ROM.

Funcions MSXgl

El MSXDOS2 tenia les funcions de paginació, crear pàgina i canviar pàgina, per gestionar la memòria. Ara la MSXgl ens permet gestionar els segments de memòria amb les funcions:

Cas pràctic de les funcions ROM de la MSXgl

Un exemple d'aquest ús el podem trobar al fitxer squirrel.c a on cridem una funció d'un dels segments:

189void obtenir_coordenades_rajola(char map_x, char map_y) {
190  u8 savedSeg = GET_BANK_SEGMENT(3);
191  SET_BANK_SEGMENT(3, 6); // El segment del mapa squirrel_s6_b3
192  char tipus = map1[map_x + (map_y * NOMBRE_RAJOLES_HOR)];
193  stamp_x = (tipus % NOMBRE_RAJOLES_HOR_ORIGEN_PATRONS) * 8;
194  stamp_y = OFFSET_COORDENADAY_PAGINA_ACTIVA_1 + (tipus / NOMBRE_RAJOLES_HOR_ORIGEN_PATRONS)*8;
195  SET_BANK_SEGMENT(3, savedSeg);
196}
            

Primer de tot guardem el segment que es troba a la part de memòria que volem ocupar amb el nou segment (potser no fa falta si sempre és el mateix en la nostra estructura de programa, però d'aquesta manera és més genèric). Després assignem al banc 3 de la memòria del MSX en el que nosaltres tenim al segment 6 (és el que en la nostra estructura de fitxers de l'aplicació hem guardat a squirrel_s6_b3 i que l'script que crea el projecte de la MSXgl ja s'ha encarregat de posar en el lloc indicat quan crea la ROM).

La variable map1 és una constant que es troba justament en el segment que hem activat, d'aquesta manera la línia 192 agafa el valor correcte. És molt important tenir ben definides les variables usant la directiva extern per indicar que no es troba definida en aquell fitxer c i que serà el linker que s'encarrega de posicionar correctament a la memòria.

Finalment a la línia 195 restaurem la memòria que hi havia abans que féssim el canvi per consultar map1.

En aquest exemple hem fet només un canvi de memòria per consultar una variable gran, però també es podria fer per cridar tota una funció de l'altre banc i després retornar aquí. Quant més autònoma (no necessita variables ni crida cap altra funció) més bona candidata és per a ser posada en un segment independent.

Una alra operació interessant és com es carreguen les imatges de la ROM al VDP. Abans ho fèiem llegint al disquet, però com es fa ara per accedir a aquella part de la ROM que la MSXgl ha posat en una adreça? El següent codi fa aquesta operació:

200void ROM_LoadSc5Image() {
201  u16 dst = 0x8000;
202  u16 file_size = 33792;  // PatCit.sc5 is 33KB
203  u16 bytes_loaded = 0;
204  u8 segment;
205  u8 savedSeg = GET_BANK_SEGMENT(3);
206
207  // Load PatCit.sc5 from segments 5-9 (spans 5 segments of 8KB each)
208  for (segment = 10; segment <= 14 && bytes_loaded < file_size; segment++) {
209    SET_BANK_SEGMENT(3, segment);
210    const u8 *src = (const u8 *)0xA000;
211
212    u16 size_to_copy;
213    if (bytes_loaded == 0) {
214      // First segment: skip 7 bytes header
215      src += 7;
216      size_to_copy = (file_size < 8185) ? file_size - 7 : 8185;
217      bytes_loaded = size_to_copy;
218    } else {
219      // Subsequent segments: copy full 8KB or remaining bytes
220      size_to_copy = (file_size - bytes_loaded < 8192) ? (file_size - bytes_loaded) : 8192;
221      bytes_loaded += size_to_copy;
222    }
223
224    VDP_WriteVRAM(src, dst, 0, size_to_copy);
225    dst += size_to_copy;
226  }
227
228  SET_BANK_SEGMENT(3, savedSeg);
229}
            

La variable dst és l'adreça de la memòria VRAM a on volem començar a escriure. file_size és el tamany del fitxer en bytes, fàcilment descobrible utilitzant un ls -l en Linux o les propietats del fitxer en altres entorns gràfics. bytes_loaded és un comptador que utilitzarem per saber la quantitat de bytes que hem llegit i l'usarem per acabar de llegir els bytes sobrants de l'últim segment. A la línia 205 fem com abans, guardem el segment actual per restaurar-lo al final de la càrrega. I comencem al bucle a la línia 208. Com sabem quins i quants segments hem de llegir? Això és senzill, al nostre fitxer project_config.js hem definit:

147//-- List of raw data files to be added to final binary (array). Each entry must be in the following format: { offset=0x0000, file="myfile.bin" }
148RawFiles = [
149	{ segment: 10, file: "./content/img/PatBosc.sc5" },
150	{ segment: 15, file: "./content/img/PatBosc.pl5" }
151];
            

amb el que hem indicat que el fitxer PatBosc.sc5 comença al segment 10. Si sabem que els bytes totals són 33792, calculem quants segments hi ha: 33792/(8*1024) = 4.125, són 4 segments i el sobrant. Per tant el bucle ha de ser fins al 14, a on d'aquest últim segment, que seria el cinquè, no està ple i només hi ha el sobrant.

L'if de la línia 213 només és per eliminar els primers 7 bytes que porten informació del fitxer i que no contenen dades de VRAM. A la línia 220 comprovem que els bytes que queden per carregar són de tot el segment (8x1024=8192) o del sobrant. Copiem les dades llegides de la RAM a VRAM amb la comanda VDP_WriteVRAM. I finalment, un cop ha acabat el bucle, tornem el segment original al banc 3.

Consells i trucs per programar en segments

En aquest capítol parlarem de diferents tècniques per poder programar en segments sense tenir errors de sobreescriptura, o poder-los detectar correctament.

Controlar les posicions de les variables i les funcions

Aquesta part és molt crítica i requereix un disseny inicial per tal de crear segments el més autònoms possibles, que no tinguin crides a d'altres segments ni a d'altres variables. En una aplicació complexa, això és molt difícil d'aconseguir, per tant, el millor és tenir les variables comunes a diferents segments al banc inicial que no canviarem. El mateix amb les funcions, les funcions comunes a diferents segments les tindrem al banc inicial. D'aquesta manera és més senzill de controlar que totes són visibles mentre s'estan executant.

En ASCII-8 només tenim 4 bancs per anar posant el nostre programa. Altres mappers com el NEO elimina la part de la Main Rom permetent treballar fins a 6 bancs.

Segment massa llarg

Si el segment és massa llarg, hi haurà una part del codi que no estarà ben creada. La MSXgl dóna la següent informació quan està compilant:

┌───────────────────────────────────────────────────────────────────────────┐
│ PACKAGE                                                                   │
└───────────────────────────────────────────────────────────────────────────┘
Packaging binary...
Execute: "/home/jepsuse/MSX/MSXgl/tools/MSXtk/bin/MSXhex" /home/jepsuse/MSX/MSXgl/projects/learning-msxgl/2p_squirrelHunt/out/squirrel.ihx -e rom -s 0x4000 -l 131072 -b 8192  -f /home/jepsuse/MSX/MSXgl/projects/learning-msxgl/2p_squirrelHunt/out/msxhex.txt
Log: Add file './content/img/PatBosc.sc5' at offset 00014000h
Log: Add file './content/img/PatBosc.pl5' at offset 0001E000h
MSXhex 1.0.0 - Convert an Intel HEX file to binary
Start=00004000h Size=00020000h Bank=00002000h Pad=FFh
Saving /home/jepsuse/MSX/MSXgl/projects/learning-msxgl/2p_squirrelHunt/out/squirrel.rom...
Seg[0000]: Lower=4000h Higher=8A20h Size=18977 (Holes=0)
Seg[0004]: Lower=A000h Higher=ABC5h Size=3014 (Holes=0)
Seg[0005]: Lower=A000h Higher=A04Ah Size=75 (Holes=0)
Seg[0006]: Lower=A000h Higher=BAFFh Size=6912 (Holes=0)
Total size: 28978
Exit code: 0
Success
            

OpenMSX debuggers

Aquí tenim una llista dels segments del nostre projecte. La nostra arquitectura és ASCII-8 amb el que els nostres segments són de 8192 bytes. La nostra aplicació és formada per un segment 0 que ocupa més de 2 segments amb el que els bancs 0,1 i 2 ja estan ocupats, és per això que en els exemples anteriors, hem posat les funcions en el banc 3. Els altres 3 segments, 4, 5 i 6, tots ocupen menys de 8K i per tant no tinc cap solapament que em faci corrompre l'aplicació.

Si vols comprovar que la ROM que veu l'openMSX sigui correcte, quan obres els debuggers, a la part add Hex editor surt el fitxer de la teva ROM i apareix el binari en el debugger. Si vols comprovar que la MSXgl hagi fet bé la ROM amb els rawfiles, pots mirar l'adreça d'aquest debugger i comparar-lo amb el binari del fitxer. Així per exemple, nosaltres carreguem PatBosc.sc5 al segment 10, per tant a la rom hauria d'estar a la posició 10*8*1024=0x14000. Si mirem el debugger i per exemple l'Okteta veiem que coincideix tal i com mostra la següent imatge:

Un altre debugger de l'openMSX que pot ser interessant és el de slots, a on ens indica per cada pàgina que pot veure el Z80, quin slot és el que està veient en aquests moments. Nosaltres hem utilitzat segments de 8K amb el que cada pàgina del Z80 estarà formada per 2 segments. L'openMSX mostra aquesta informació amb la notació RX/Y a on X és el primer segment i Y és el segon. Així en la nostra aplicació, tal i com mostra la imatge de més avall, a la pàgina 1 del Z80 hi ha els segments de la ROM 0 i 1, i a la pàgina 2 hi ha els segments 2 i 5. Aquest 4 segment, el 5, és el que nosaltres anem variant depenent de la part del programa que ho necessiti. Si deixem córrer l'apliació, veurem com aquest 5 va fent pampallugues i va canviant al 6 i a altres números.