Triceraproghttps://www.triceraprog.fr/2024-02-23T00:00:00+01:00La programmation depuis le CrétacéNotes sur le Motorola 68092024-02-23T00:00:00+01:002024-02-23T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2024-02-23:/notes-sur-le-motorola-6809.html<p>Je place ici quelques notes sur le Motorola 6809, pour me remettre rapidement dans le bain lorsque je change de processeur.</p>
<h1>Registres</h1>
<ul>
<li>2 accumulateurs 8 bits A et B, qui peuvent se joindre en un seul accumulateur 16 bits D</li>
<li>1 registre d'index X 16 bits</li>
<li>1 registre SP (Stack …</li></ul><p>Je place ici quelques notes sur le Motorola 6809, pour me remettre rapidement dans le bain lorsque je change de processeur.</p>
<h1>Registres</h1>
<ul>
<li>2 accumulateurs 8 bits A et B, qui peuvent se joindre en un seul accumulateur 16 bits D</li>
<li>1 registre d'index X 16 bits</li>
<li>1 registre SP (Stack Pointer)</li>
<li>1 registre PC (Program Counter)</li>
<li>1 registre d'état : 11HINZVC<ul>
<li>5, H : half carry</li>
<li>4, I : interrupt</li>
<li>3, N : negative</li>
<li>2, Z : zero</li>
<li>1, V : overflow</li>
<li>0, C : carry</li>
</ul>
</li>
</ul>
<h1>Interruptions</h1>
<ul>
<li>NMI (Non Maskable Interrupt)</li>
<li>IRQ1 (Interrupt Request) : pour les périphériques externes</li>
<li>IRQ2 (Interrupt Request) : pour le timer et l'interface série</li>
</ul>
<p>IRQ1 a la priorité sur IRQ2.</p>
<p>Vecteurs d’interruptions :</p>
<table>
<thead>
<tr>
<th>MSB</th>
<th>LSB</th>
<th>Interruption</th>
</tr>
</thead>
<tbody>
<tr>
<td>FFFE</td>
<td>FFFF</td>
<td>Reset</td>
</tr>
<tr>
<td>FFFC</td>
<td>FFFD</td>
<td>NMI</td>
</tr>
<tr>
<td>FFFA</td>
<td>FFFB</td>
<td>Software interrupt (SWI)</td>
</tr>
<tr>
<td>FFF8</td>
<td>FFF9</td>
<td>IRQ1</td>
</tr>
<tr>
<td>FFF6</td>
<td>FFF7</td>
<td>ICF (Input Capture)</td>
</tr>
<tr>
<td>FFF4</td>
<td>FFF5</td>
<td>OCF (Output Compare)</td>
</tr>
<tr>
<td>FFF2</td>
<td>FFF3</td>
<td>TOF (Timer Overflow)</td>
</tr>
<tr>
<td>FFF0</td>
<td>FFF1</td>
<td>SCI (RDRF + ORFE + TDRE)</td>
</tr>
</tbody>
</table>
<h1>Ensemble des Instructions</h1>
<h2>Mode d'adressage</h2>
<ul>
<li>Implied/Inherent : aucune donnée n'est nécessaire</li>
<li>Immediate : la donnée est dans l'instruction (1 ou 2 octets)</li>
<li>Direct : la donnée est dans l'octet suivant (1 octet, le poids fort est fixé à $00)</li>
<li>Extended : la donnée est dans les deux octets suivants (adresse 16 bits)</li>
<li>Indexed : la donnée est dans la mémoire, à l'adresse donnée par le registre d'index (opérande de 8 bits ajoutée à X)</li>
<li>Relative : la donnée est dans l'octet suivant, signée (pour les branchements)</li>
</ul>
<h2>Instructions</h2>
<table>
<thead>
<tr>
<th>Opérations à pointeur</th>
<th>Mnemonic</th>
<th>Imm</th>
<th>Dir</th>
<th>Ind</th>
<th>Ext</th>
<th>Inh</th>
<th>Operation</th>
<th>HINZVC</th>
</tr>
</thead>
<tbody>
<tr>
<td>Compare Index Reg</td>
<td>CPX</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>X - M : M + 1</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Decrement Index Reg</td>
<td>DEX</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>X - 1-> X</td>
<td>...!..</td>
</tr>
<tr>
<td>Decrement SP</td>
<td>DES</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>SP - 1-> SP</td>
<td>......</td>
</tr>
<tr>
<td>Increment Index Reg</td>
<td>INX</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>X + 1-> X</td>
<td>...!..</td>
</tr>
<tr>
<td>Increment SP</td>
<td>INS</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>SP + 1-> SP</td>
<td>......</td>
</tr>
<tr>
<td>Load Index Reg</td>
<td>LDX</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>M -> Xh, M+1 -> Xl</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Load SP</td>
<td>LDS</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>M -> SPh, M+1 -> SPl</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Store Index Reg</td>
<td>STX</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>Xh -> M, Xl -> M+1</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Store SP</td>
<td>STS</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>SPh -> M, SPl -> M+1</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Index to Stack</td>
<td>TXS</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>X - 1 -> SP</td>
<td>......</td>
</tr>
<tr>
<td>Stack to Index</td>
<td>TSX</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>SP + 1 -> X</td>
<td>......</td>
</tr>
<tr>
<td>Add B with X</td>
<td>ABX</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B + X -> B</td>
<td>......</td>
</tr>
<tr>
<td>Push X</td>
<td>PSHX</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>Xl->(SP),SP-1>SP,Xh...</td>
<td>......</td>
</tr>
<tr>
<td>Pull X</td>
<td>PULX</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>(SP)+1->SP,Xh->Xh,...</td>
<td>......</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Operations Acc. & Mémoire</th>
<th>Mnemonic</th>
<th>Imm</th>
<th>Dir</th>
<th>Ind</th>
<th>Ext</th>
<th>Inh</th>
<th>Operation</th>
<th>HINZVC</th>
</tr>
</thead>
<tbody>
<tr>
<td>Add Accumulators</td>
<td>ABA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>A + B -> A</td>
<td>!.!!!!</td>
</tr>
<tr>
<td>Add B to X</td>
<td>ABX</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>00:B + X -> X</td>
<td>......</td>
</tr>
<tr>
<td>Add A with Carry</td>
<td>ADCA</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>A + M + C -> A</td>
<td>!.!!!!</td>
</tr>
<tr>
<td>Add B with Carry</td>
<td>ADCB</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>B + M + C -> B</td>
<td>!.!!!!</td>
</tr>
<tr>
<td>Add with A</td>
<td>ADDA</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>A + M -> A</td>
<td>!.!!!!</td>
</tr>
<tr>
<td>Add with B</td>
<td>ADDB</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>B + M -> B</td>
<td>!.!!!!</td>
</tr>
<tr>
<td>Add Double</td>
<td>ADDD</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>D + M:M+1 -> D</td>
<td>..!!!!</td>
</tr>
<tr>
<td>And with A</td>
<td>ANDA</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>A & M -> A</td>
<td>..!!0.</td>
</tr>
<tr>
<td>And with B</td>
<td>ANDB</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>B & M -> B</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Shift Left Arithmetic M</td>
<td>ASL</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>B7 dans C, C dans B0</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Shift Left Arithmetic A</td>
<td>ASLA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B7 dans C, C dans B0</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Shift Left Arithmetic B</td>
<td>ASLB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B7 dans C, C dans B0</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Shift Left Arithmetic D</td>
<td>ASLD</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B15 dans C, C dans B0 ?</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Shift Right Arithmetic M</td>
<td>ASR</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>B0 dans C, B7 dans B7</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Shift Right Arithmetic A</td>
<td>ASRA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B0 dans C, B7 dans B7</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Shift Right Arithmetic B</td>
<td>ASRB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B0 dans C, B7 dans B7</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Bit Test A</td>
<td>BITA</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>A & M, A non modifié</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Bit Test B</td>
<td>BITB</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>B & M, B non modifié</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Compare A/B with M</td>
<td>CBA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>A & B, A/B non modifié</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Clear Memory</td>
<td>CLR</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>00 -> M</td>
<td>..0100</td>
</tr>
<tr>
<td>Clear A</td>
<td>CLRA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>0 -> A</td>
<td>..0100</td>
</tr>
<tr>
<td>Clear B</td>
<td>CLRB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>0 -> B</td>
<td>..0100</td>
</tr>
<tr>
<td>Compare with A</td>
<td>CMPA</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>A - M</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Compare with B</td>
<td>CMPB</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>B - M</td>
<td>..!!!!</td>
</tr>
<tr>
<td>1's Complement M</td>
<td>COM</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>~M -> M</td>
<td>..!!01</td>
</tr>
<tr>
<td>1's Complement A</td>
<td>COMA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>~A -> A</td>
<td>..!!01</td>
</tr>
<tr>
<td>1's Complement B</td>
<td>COMB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>~B -> B</td>
<td>..!!01</td>
</tr>
<tr>
<td>Decimal Adj. A</td>
<td>DAA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>Ajustement BCD</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Decrement M</td>
<td>DEC</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>M - 1 -> M</td>
<td>..!!!.</td>
</tr>
<tr>
<td>Decrement A</td>
<td>DECA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>A - 1 -> A</td>
<td>..!!!.</td>
</tr>
<tr>
<td>Decrement B</td>
<td>DECB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B - 1 -> B</td>
<td>..!!!.</td>
</tr>
<tr>
<td>Exclusive OR with A</td>
<td>EORA</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>A ^ M -> A</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Exclusive OR with B</td>
<td>EORB</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>B ^ M -> B</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Increment M</td>
<td>INC</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>M + 1 -> M</td>
<td>..!!!.</td>
</tr>
<tr>
<td>Increment A</td>
<td>INCA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>A + 1 -> A</td>
<td>..!!!.</td>
</tr>
<tr>
<td>Increment B</td>
<td>INCB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B + 1 -> B</td>
<td>..!!!.</td>
</tr>
<tr>
<td>Load to A</td>
<td>LDAA</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>M -> A</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Load to B</td>
<td>LDAB</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>M -> B</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Load to D</td>
<td>LDD</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>M:M+1 -> D</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Logical Shift Left M</td>
<td>LSL</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>B7 dans C, 0 dans B0</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Logical Shift Left A</td>
<td>LSLA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B7 dans C, 0 dans B0</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Logical Shift Left B</td>
<td>LSLB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B7 dans C, 0 dans B0</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Logical Shift Left D</td>
<td>LSLD</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B15 dans C, 0 dans B0 ?</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Shift Right Logical M</td>
<td>LSR</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>B0 dans C, 0 dans B7</td>
<td>..0!!!</td>
</tr>
<tr>
<td>Shift Right Logical A</td>
<td>LSRA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B0 dans C, 0 dans B7</td>
<td>..0!!!</td>
</tr>
<tr>
<td>Shift Right Logical B</td>
<td>LSRB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B0 dans C, 0 dans B7</td>
<td>..0!!!</td>
</tr>
<tr>
<td>Shift Right Logical D</td>
<td>LSRD</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B0 dans C, 0 dans B15 ?</td>
<td>..0!!!</td>
</tr>
<tr>
<td>Multiply</td>
<td>MUL</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>A * B -> D</td>
<td>.....!</td>
</tr>
<tr>
<td>2's Complement M</td>
<td>NEG</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>-M -> M</td>
<td>..!!!!</td>
</tr>
<tr>
<td>2's Complement A</td>
<td>NEGA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>-A -> A</td>
<td>..!!!!</td>
</tr>
<tr>
<td>2's Complement B</td>
<td>NEGB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>-B -> B</td>
<td>..!!!!</td>
</tr>
<tr>
<td>No Operation</td>
<td>NOP</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td></td>
<td>......</td>
</tr>
<tr>
<td>Logical OR with A</td>
<td>ORAA</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>A</td>
<td>M -> A</td>
</tr>
<tr>
<td>Logical OR with B</td>
<td>ORAB</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>B</td>
<td>M -> B</td>
</tr>
<tr>
<td>Push A</td>
<td>PSHA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>A -> (SP), SP - 1 -> SP</td>
<td>......</td>
</tr>
<tr>
<td>Push B</td>
<td>PSHB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B -> (SP), SP - 1 -> SP</td>
<td>......</td>
</tr>
<tr>
<td>Pull A</td>
<td>PULA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>SP + 1 -> SP, (SP) -> A</td>
<td>......</td>
</tr>
<tr>
<td>Pull B</td>
<td>PULB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>SP + 1 -> SP, (SP) -> B</td>
<td>......</td>
</tr>
<tr>
<td>Rotate Left M</td>
<td>ROL</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>C dans B0, B7 dans C</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Rotate Left A</td>
<td>ROLA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>C dans B0, B7 dans C</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Rotate Left B</td>
<td>ROLB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>C dans B0, B7 dans C</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Rotate Right M</td>
<td>ROR</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>C dans B7, B0 dans C</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Rotate Right A</td>
<td>RORA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>C dans B7, B0 dans C</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Rotate Right B</td>
<td>RORB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>C dans B7, B0 dans C</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Subtract A with B</td>
<td>SBA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>A - B -> A</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Subtract with Carry with A</td>
<td>SBCA</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>A - M - C -> A</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Subtract with Carry with B</td>
<td>SBCB</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>B - M - C -> B</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Store A</td>
<td>STAA</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>A -> M</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Store B</td>
<td>STAB</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>B -> M</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Store D</td>
<td>STD</td>
<td></td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>D -> M:M+1</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Subtract from A</td>
<td>SUBA</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>A - M -> A</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Subtract from B</td>
<td>SUBB</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>B - M -> B</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Subtract Double</td>
<td>SUBD</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td>X</td>
<td></td>
<td>D - M:M+1 -> D</td>
<td>..!!!!</td>
</tr>
<tr>
<td>Transfer A to B</td>
<td>TAB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>A -> B</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Transfer B to A</td>
<td>TBA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B -> A</td>
<td>..!!0.</td>
</tr>
<tr>
<td>Test Zero or Minus M</td>
<td>TST</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td>M, M non modifié</td>
<td>..!!00</td>
</tr>
<tr>
<td>Test Zero or Minus A</td>
<td>TSTA</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>A, A non modifié</td>
<td>..!!00</td>
</tr>
<tr>
<td>Test Zero or Minus B</td>
<td>TSTB</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td>B, B non modifié</td>
<td>..!!00</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Sauts et Branches</th>
<th>Mnemonic</th>
<th>Dir</th>
<th>Rel</th>
<th>Ind</th>
<th>Ext</th>
<th>Inh</th>
<th>Test</th>
<th>HINZVC</th>
</tr>
</thead>
<tbody>
<tr>
<td>Branch Always</td>
<td>BRA</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>......</td>
</tr>
<tr>
<td>Branch Never</td>
<td>BRN</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>......</td>
</tr>
<tr>
<td>Branch on Carry Clear</td>
<td>BCC</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>C = 0</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Carry Set</td>
<td>BCS</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>C = 1</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Equal</td>
<td>BEQ</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>Z = 1</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Greater or Equal</td>
<td>BGE</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>N ^ V = 0</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Greater</td>
<td>BGT</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>(N ^ V)</td>
<td>Z = 0</td>
</tr>
<tr>
<td>Branch on Higher</td>
<td>BHI</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>C + Z = 0</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Higher or Same</td>
<td>BHS</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>C = 0</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Less or Equal</td>
<td>BLE</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>(N ^ V)</td>
<td>Z = 1</td>
</tr>
<tr>
<td>Branch on Less than 0</td>
<td>BLT</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>N ^ V = 1</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Minus</td>
<td>BMI</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>N = 1</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Not Equal</td>
<td>BNE</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>Z = 0</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Overflow Clear</td>
<td>BVC</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>V = 0</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Overflow Set</td>
<td>BVS</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>V = 1</td>
<td>......</td>
</tr>
<tr>
<td>Branch on Plus</td>
<td>BPL</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td>N = 0</td>
<td>......</td>
</tr>
<tr>
<td>Branch to Subroutine</td>
<td>BSR</td>
<td></td>
<td>X</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>......</td>
</tr>
<tr>
<td>Jump</td>
<td>JMP</td>
<td></td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td></td>
<td>......</td>
</tr>
<tr>
<td>Jump to Subroutine</td>
<td>JSR</td>
<td>X</td>
<td></td>
<td>X</td>
<td>X</td>
<td></td>
<td></td>
<td>......</td>
</tr>
<tr>
<td>Return from Interrupt</td>
<td>RTI</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td></td>
<td>!!!!!!</td>
</tr>
<tr>
<td>Return from Subroutine</td>
<td>RTS</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td></td>
<td>......</td>
</tr>
<tr>
<td>Software Interrupt</td>
<td>SWI</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td></td>
<td>!1!!!!</td>
</tr>
<tr>
<td>Wait for Interrupt</td>
<td>WAI</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td>X</td>
<td></td>
<td>......</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Manipulation des drapeaux</th>
<th>Mnemonic</th>
<th>Inh</th>
<th>Operation</th>
<th>HINZVC</th>
</tr>
</thead>
<tbody>
<tr>
<td>Clear Carry</td>
<td>CLC</td>
<td>X</td>
<td>0 -> C</td>
<td>.....0</td>
</tr>
<tr>
<td>Clear Interrupt</td>
<td>CLI</td>
<td>X</td>
<td>0 -> I</td>
<td>.0....</td>
</tr>
<tr>
<td>Clear Overflow</td>
<td>CLV</td>
<td>X</td>
<td>0 -> V</td>
<td>....0.</td>
</tr>
<tr>
<td>Set Carry</td>
<td>SEC</td>
<td>X</td>
<td>1 -> C</td>
<td>.....1</td>
</tr>
<tr>
<td>Set Interrupt</td>
<td>SEI</td>
<td>X</td>
<td>1 -> I</td>
<td>.1....</td>
</tr>
<tr>
<td>Set Overflow</td>
<td>SEV</td>
<td>X</td>
<td>1 -> V</td>
<td>....1.</td>
</tr>
<tr>
<td>Accumulator to CCR</td>
<td>TAP</td>
<td>X</td>
<td>A -> CCR</td>
<td>!!!!!!</td>
</tr>
<tr>
<td>CCR to Accumulator</td>
<td>TPA</td>
<td>X</td>
<td>CCR -> A</td>
<td>......</td>
</tr>
</tbody>
</table>
<h1>Ports</h1>
<h2>Port 1</h2>
<p>P10-P17 : entrées/sorties, 8 bits. Toutes les lignes sont configurables en entrée ou en sortie indépendamment.</p>
<h2>Port 2</h2>
<p>P20-P24 : entrées/sorties, 5 bits.</p>
<ul>
<li>P20 et P21 déterminent le mode de fonctionnement du processeur (lors du reset)</li>
<li>Si P21 est configuré en sortie, il est lié à la fonction de comparison du timer</li>
<li>Peut être utilisé comme port série</li>
<li>Les lignes sont configurables en entrée ou en sortie indépendamment.</li>
</ul>
<h2>Port 3</h2>
<p>P30-P37 : entrées/sorties, 8 bits. Adresses et données multiplexées. A0 à A7 et D0 à D7.</p>
<h2>Port 4</h2>
<p>P40-P47 : adressage de la mémoire externe, A8 à A15.</p>
<h1>Mémoire interne</h1>
<ul>
<li>128 octets de RAM interne</li>
</ul>
<h1>Timer programmable</h1>
<ul>
<li>Compteur ($09:$0A) : lecture seule. Une écriture dans $09 reset le compteur à $FFF8, à éviter.</li>
<li>Comparateur ($0B:$0C) : 16 bits, lecture/écriture. Lorsqu'il est égal au compteur, OCF est mis à 1.</li>
<li>Capture d'entrée ($0D:$0E) : lecture seule. Enregistre la valeur du compteur lorsqu'une transition est détectée, tel que définie par IEG.</li>
<li>Contrôle du Timer ($08) : lecture, écriture sur bits 0-4. Les bits de poids fort représentent l'état.</li>
</ul>Sous le capot de l'Alice 32/90, un EF93452024-01-04T00:00:00+01:002024-01-04T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2024-01-04:/sous-le-capot-de-lalice-3290-un-ef9345.html<p>La machine de la nouvelle session du « Retro Programmers United for Obscure Systems » est connue. C'est le Matra Alice. Et plus exactement les version 32 et 90, afin de s'échapper de la trop grande proximité entre le Alice premier du nom et le Tandy MC-10 (une telle proximité que ce …</p><p>La machine de la nouvelle session du « Retro Programmers United for Obscure Systems » est connue. C'est le Matra Alice. Et plus exactement les version 32 et 90, afin de s'échapper de la trop grande proximité entre le Alice premier du nom et le Tandy MC-10 (une telle proximité que ce sont les mêmes machines).</p>
<p>Les versions 32 et 90 étant très similaires niveau hardware, je les nommerais conjointement Alice 32/90.</p>
<p>Pour cet article, ce qui va m'intéresser est que l'Alice 32/90 utilise un EF9345, comme le VG5000. Et l'EF9345 étant aussi utilisé dans le VG5000µ, c'est une puce que je connais bien. Cependant, l'Alice 32/90 l'utilise différemment. Je dirais même qu'il l'utilise mieux.</p>
<p>L'autre axe de cet article est que je veux utiliser l'EF9345 depuis le BASIC. La machine tournant avec un 6803, les I/O sont mappées en mémoire. Et il est donc possible d'accéder aux registres de l'EF9345 depuis le BASIC avec des PEEKs et POKEs. Pas besoin de passer par
l'assembleur. Par contre, pas de mystère, c'est lent !</p>
<h2>Le mode graphique en BASIC sur l'Alice</h2>
<p>Première chose amusante, l'Alice 32/90 démarre dans un mode 32 colonnes qui n'existe pas pour l'EF9345. Je pense que ce mode par défaut, qui utilise en fait le mode 40 colonnes de l'EF9345, est là pour être compatible avec le Alice 4k au niveau BASIC. Pour assurer cette compatibilité, des caractères utilisateurs sont chargés dans la RAM vidéo au démarrage pour représenter les différentes combinaison d'un bloc de 2x2 « gros pixels » pour chaque caractère.</p>
<p>L'Alice 32/90 peut cependant utiliser l'EF9345 en vrai mode 40 colonnes, et même en mode 80 colonnes. Pour cela passer d'un mode à l'autre, il faut utiliser les commandes CLS32, CLS40 et CLS80. Je vais me concentrer sur le mode 40 colonnes.</p>
<p>Celui-ci est initialisé dans le mode « 40 CHAR LONG ». C'est un mode qui prend trois pages de 1Ko en mémoire vidéo. La première page contient les caractères, la deuxième les attributs et la troisième les couleurs. Ça fait beaucoup sur les 8ko de RAM vidéo de la machine, et le VG5000µ avait fait le choix d'utilise le mode « 40 CHAR SHORT » qui ne prend que 2ko. Cependant, on y gagner en simplicité et flexibilité au niveau des attributs.</p>
<p>Autre choix de l'Alice : le système ne conserve pas de miroir en RAM de l'affichage. Là encore, bon choix pour l'utilisation de la RAM principale. En contrepartie cela signifie que les opérations non prévues par le BASIC devront se faire en s'adressant directement à l'EF9345.</p>
<p>Plus techniquement, le mode est initialisé avec les registres suivants :</p>
<div class="highlight"><pre><span></span><code><span class="o">-</span><span class="w"> </span><span class="n">TGS</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x10</span><span class="w"> </span><span class="p">(</span><span class="n">binaire</span><span class="o">:</span><span class="w"> </span><span class="mo">00010000</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="mi">312</span><span class="w"> </span><span class="n">lignes</span><span class="w"> </span><span class="n">non</span><span class="w"> </span><span class="n">entrelacées</span><span class="p">,</span><span class="w"> </span><span class="n">synchro</span><span class="w"> </span><span class="n">composite</span><span class="p">,</span><span class="w"> </span><span class="n">pas</span><span class="w"> </span><span class="n">de</span><span class="w"> </span><span class="n">synchro</span><span class="w"> </span><span class="n">entrante</span><span class="p">,</span><span class="w"> </span><span class="kr">service</span><span class="w"> </span><span class="nf">row</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="mi">40</span><span class="w"> </span><span class="n">Char</span><span class="w"> </span><span class="n">Long</span><span class="w"></span>
<span class="o">-</span><span class="w"> </span><span class="n">MAT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x28</span><span class="w"> </span><span class="p">(</span><span class="n">binaire</span><span class="o">:</span><span class="w"> </span><span class="mo">00101000</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">couleur</span><span class="w"> </span><span class="n">de</span><span class="w"> </span><span class="n">marge</span><span class="w"> </span><span class="n">noire</span><span class="p">,</span><span class="w"> </span><span class="n">curseur</span><span class="w"> </span><span class="n">complet</span><span class="w"> </span><span class="n">clignotant</span><span class="p">,</span><span class="w"> </span><span class="n">curseur</span><span class="w"> </span><span class="n">désactivé</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="p">),</span><span class="w"> </span><span class="n">simple</span><span class="w"> </span><span class="n">hauteur</span><span class="w"></span>
<span class="o">-</span><span class="w"> </span><span class="n">PAT</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x67</span><span class="w"> </span><span class="p">(</span><span class="n">binaire</span><span class="o">:</span><span class="w"> </span><span class="mo">01100111</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="kr">service</span><span class="w"> </span><span class="nf">row</span><span class="w"> </span><span class="n">visible</span><span class="p">,</span><span class="w"> </span><span class="n">bulk</span><span class="w"> </span><span class="n">haut</span><span class="w"> </span><span class="n">et</span><span class="w"> </span><span class="n">bas</span><span class="w"> </span><span class="n">visible</span><span class="p">,</span><span class="w"> </span><span class="n">conceal</span><span class="w"> </span><span class="n">désactivé</span><span class="p">,</span><span class="w"> </span><span class="n">flash</span><span class="w"> </span><span class="n">activé</span><span class="w"></span>
<span class="o">-</span><span class="w"> </span><span class="n">DOR</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x13</span><span class="w"> </span><span class="p">(</span><span class="n">binaire</span><span class="o">:</span><span class="w"> </span><span class="mo">00010011</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">page</span><span class="w"> </span><span class="n">G</span><span class="s">'0 située en Z = 3, page G'</span><span class="mi">10</span><span class="w"> </span><span class="n">située</span><span class="w"> </span><span class="n">en</span><span class="w"> </span><span class="n">Z</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="n">page</span><span class="w"> </span><span class="n">Q</span><span class="w"> </span><span class="n">située</span><span class="w"> </span><span class="n">en</span><span class="w"> </span><span class="n">Z</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="o">-</span><span class="w"> </span><span class="n">ROR</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x08</span><span class="w"> </span><span class="p">(</span><span class="n">binaire</span><span class="o">:</span><span class="w"> </span><span class="mo">00001000</span><span class="p">)</span><span class="w"> </span><span class="o">-></span><span class="w"> </span><span class="n">première</span><span class="w"> </span><span class="n">ligne</span><span class="w"> </span><span class="n">bulk</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="n">page</span><span class="w"> </span><span class="n">d</span><span class="s">'origine Z = 0</span>
</code></pre></div>
<h2>Les registres de l'EF9345</h2>
<p>Depuis le processeur, l'EF9345 est accessible via 8 registres. Ces registres sont accessibles en lecture et écriture, et sont tous des registres de 8 bits. Ils sont accessibles via les adresses suivantes :</p>
<div class="highlight"><pre><span></span><code>- R0 = $BF20 ; 48928 (commande)
- R1 = $BF21 ; 48929 (paramètre principal)
- R2 = $BF22 ; 48930 (paramètre)
- R3 = $BF23 ; 48931 (paramètre)
- R4 = $BF24 ; 48932 (pointeur auxiliaire haut)
- R5 = $BF25 ; 48933 (pointeur auxiliaire bas)
- R6 = $BF26 ; 48934 (pointeur principal haut)
- R7 = $BF27 ; 48935 (pointeur principal bas)
</code></pre></div>
<p>Une adresse supplémentaire est située en $BF28 (48936). Il s'agit en fait de R0 mais avec le flag d'exécution (bit 7 du numéro de registre) mis à 1. Cela permet de demander à l'EF9345 d'exécuter la commande indiquée dans R0.</p>
<p>Techniquement, n'importe quel registre peut être utilisé pour exécuter une commande en mettant le bit 7 à 1. Cela permet d'ailleurs quelques optimisations. Je n'ai pas vérifié si les adresses au delà de $BF28 étaient mappées. Si ce n'est pas le cas, on n'utilisera pas, voire jamais, R0 directement en écriture directement, mais toujours via le « faux registre » R8.</p>
<p>Note additionnel : le livre "les astuces d'Alice 32 et 90" indique qu'il est en effet possible d'utiliser tous les registres en mode « exécution ».</p>
<h2>Un premier affichage</h2>
<p>Voici un programme qui place tous les registres directs correctement pour afficher un « A » à l'écran sur la ligne 0 (ligne de service), colonne 10. Notez que cela fonctionnera parfaitement en mode CLS32. Vous pouvez vous amuser à écrire où vous voulez dans l'espace de 40 colonnes par 25 lignes pour constater que le mode 32 colonnes est en fait le mode 40.</p>
<p>Attention, dans cette configuration de l'EF9345, la deuxième ligne affichée est la ligne... numéro 8.</p>
<div class="highlight"><pre><span></span><code><span class="nl">100</span><span class="w"> </span><span class="c1">REM AFFICHE UN CARACTERE</span>
<span class="nl">110</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48928</span><span class="p">,</span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">R0</span><span class="w"> </span><span class="p">(</span><span class="vg">COMMANDE</span><span class="p">)</span><span class="w"> </span><span class="vg">KRF</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="vg">INUTILE</span>
<span class="nl">120</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48929</span><span class="p">,</span><span class="w"> </span><span class="il">65</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">R1</span><span class="w"> </span><span class="p">(</span><span class="vg">C</span><span class="p">)</span>
<span class="nl">130</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48930</span><span class="p">,</span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">R2</span><span class="w"> </span><span class="p">(</span><span class="vg">B</span><span class="p">)</span>
<span class="nl">140</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48931</span><span class="p">,</span><span class="w"> </span><span class="il">20</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">R3</span><span class="w"> </span><span class="p">(</span><span class="vg">A</span><span class="p">)</span>
<span class="nl">150</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48932</span><span class="p">,</span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">R4</span><span class="w"> </span><span class="p">(</span><span class="vg">NA</span><span class="w"> </span><span class="vg">pour</span><span class="w"> </span><span class="vg">KRF</span><span class="p">)</span>
<span class="nl">160</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48933</span><span class="p">,</span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">R5</span><span class="w"> </span><span class="p">(</span><span class="vg">NA</span><span class="w"> </span><span class="vg">pour</span><span class="w"> </span><span class="vg">KRF</span><span class="p">)</span>
<span class="nl">170</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48934</span><span class="p">,</span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">R6</span><span class="w"> </span><span class="p">(</span><span class="vg">MP</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="vg">LIGNE</span><span class="p">)</span>
<span class="nl">180</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48935</span><span class="p">,</span><span class="w"> </span><span class="il">10</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">R7</span><span class="w"> </span><span class="p">(</span><span class="vg">MP</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="vg">COL</span><span class="p">)</span>
<span class="nl">190</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="w"> </span><span class="il">1</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">R0</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">EXEC</span><span class="w"> </span><span class="p">(</span><span class="vg">KRF</span><span class="p">)</span>
</code></pre></div>
<h2>Une démonstration plus complète</h2>
<p>Voici un programme de démonstration plus complet, mais pas entièrement, puisqu'il y a plein de choses à faire avec l'EF9345 qui ne sont pas démontrées ici.</p>
<p><strong>Attention</strong> : je n'ai pas du tout testé sur vrai matériel. L'EF9345 étant sensible aux synchronisations (on ne peut pas lui parler n'importe quand), je ne sais pas à quel point le BASIC est capable de gérer cela. Il est possible que ce programme ne fonctionne pas bien sur vrai matériel. Je suis preneur de retours (et j'essaierai moi même un de ces jours).</p>
<p>Voici quelques commentaires :</p>
<ul>
<li>En ligne 100, on initialise les registres nécessaires pour afficher une chaîne de caractère utilisant les mêmes attributs.</li>
<li>En ligne 200, on initialise les registres restants pour afficher un caractère et passer à la colonne suivante (attention, il n'y a pas de détection de fin de ligne).</li>
<li>En ligne 300, on initialise tous les registres pour afficher un caractère avec des attributs différents. Dans les commentaires <code>KRF</code> est le nom de la commande d'affichage d'un caractère en mode « 40 CHAR LONG ».</li>
<li>En ligne 400, une routine se charge de monter en mémoire vidéo les données pour un caractère utilisateur.</li>
<li>En ligne 10000, c'est le début du test et j'affichage "BONJOUR". C'est un PRINT en bien plus compliqué et bien plus lent...</li>
<li>En ligne 10100, j'affiche les 128 caractères alphanumériques en ROM de l'EF9345.</li>
<li>En ligne 10200, j'affiche une autre plage de 32 caractères, graphiques, en ROM de l'EF9345.</li>
<li>En ligne 10300, j'affiche les 128 caractères semi-graphiques en ROM de l'EF9345. Dans ces trois affichages, c'est la variable B qui indique le type de caractères à utiliser.</li>
<li>En ligne 10400, j'affiche un caractère graphique plein pour montrer les 16 teintes disponibles pour les caractères.</li>
<li>En ligne 11000, je tente de lire les registres indirects de l'EF9345. Cela ne fonctionne pas tout à fait bien. Possible que ce soit un problème de synchro (d'où la petite boucle, même si 1/ ce n'est pas comme cela qu'on synchronise l'EF9345 2/ la lenteur du BASIC me fait penser que quoique l'on fasse, il y a toujours un risque de tomber au mauvais moment).</li>
<li>En ligne 12300, je modifie le registre indirect ROR afin de faire un scrolling vertical. Comme la mémoire « sous » l'écran n'est pas préparée, cela affiche un peu n'importe quoi. C'est surtout une démonstration de l'utilisation de registre indirect en écriture.</li>
<li>En ligne 13000, je charge un caractère utilisateur (le bonhomme du manuel du VG5000µ) puis j'affichage un des « sets » de caractères redéfinis. Particularité : c'est un ensemble de 100 caractères allant de 0 à 3, puis de 32 à 127... L'adresse de destination <code>A=192</code> est aussi assez compliquée à calculer (en tout cas, ça ne s'invente pas, et le datasheet demande une grande attention, je ferai peut-être un article séparé là-dessus). Dans les caractères affichés, vous verrez les blocs redéfinis pour la compatibilité avec l'Alice 4k.</li>
</ul>
<p>Pour la manière dont fonctionne la mémoire de l'EF9345, en attendant un article, vous pouvez tentez de comprendre avec le code du fichier <code>ef9345_memory.py</code> dans mon <a href="https://github.com/Triceraprog/vg5000_tools">dépôt GitHub</a> dédié aux outils d'étude pour le VG5000µ.</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="kr">CLS</span><span class="o">:</span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">10000</span>
<span class="nl">100</span><span class="w"> </span><span class="c1">REM PREPARATION AFFICHAGE CARACTERE</span>
<span class="nl">110</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48930</span><span class="p">,</span><span class="vg">B</span>
<span class="nl">120</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48931</span><span class="p">,</span><span class="vg">A</span>
<span class="nl">130</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48934</span><span class="p">,</span><span class="vg">Y</span>
<span class="nl">140</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48935</span><span class="p">,</span><span class="vg">X</span>
<span class="nl">150</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">200</span><span class="w"> </span><span class="c1">REM AFFICHE UN CARACTERE AVEC INCREMENTATION</span>
<span class="nl">210</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48929</span><span class="p">,</span><span class="vg">C</span>
<span class="nl">220</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="il">1</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">KRF</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">INCR</span>
<span class="nl">230</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">300</span><span class="w"> </span><span class="c1">REM AFFICHE UN SEUL CARACTERE COMPLET</span>
<span class="nl">310</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48929</span><span class="p">,</span><span class="vg">C</span>
<span class="nl">320</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48930</span><span class="p">,</span><span class="vg">B</span>
<span class="nl">330</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48931</span><span class="p">,</span><span class="vg">A</span>
<span class="nl">340</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48934</span><span class="p">,</span><span class="vg">Y</span>
<span class="nl">350</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48935</span><span class="p">,</span><span class="vg">X</span>
<span class="nl">360</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="il">0</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">KRF</span>
<span class="nl">370</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">400</span><span class="w"> </span><span class="c1">REM CHARGEMENT D'UN CARACTERE REDÉFINI</span>
<span class="nl">410</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="il">1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">10</span>
<span class="nl">420</span><span class="w"> </span><span class="kr">READ</span><span class="w"> </span><span class="vg">C</span>
<span class="nl">430</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48932</span><span class="p">,</span><span class="kr">INT</span><span class="p">(</span><span class="vg">A</span><span class="o">/</span><span class="il">256</span><span class="p">)</span>
<span class="nl">440</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48933</span><span class="p">,</span><span class="vg">A</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">255</span>
<span class="nl">450</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48929</span><span class="p">,</span><span class="vg">C</span>
<span class="nl">460</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="il">48</span><span class="o">+</span><span class="il">4</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">OCT</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">AUX_PTR</span><span class="w"> </span><span class="p">(</span><span class="vg">EXEC</span><span class="p">)</span>
<span class="nl">470</span><span class="w"> </span><span class="vg">A</span><span class="o">=</span><span class="vg">A</span><span class="o">+</span><span class="il">4</span>
<span class="nl">480</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span>
<span class="nl">490</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">10000</span><span class="w"> </span><span class="c1">REM PROGRAMME DE TEST</span>
<span class="nl">10010</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">5</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">COORDONNEE</span>
<span class="nl">10020</span><span class="w"> </span><span class="vg">B</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">A</span><span class="o">=</span><span class="il">20</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">ATTRIBUTS</span>
<span class="nl">10030</span><span class="w"> </span><span class="vg">A$</span><span class="o">=</span><span class="s2">"BONJOUR"</span>
<span class="nl">10040</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">10050</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="il">1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="kr">LEN</span><span class="p">(</span><span class="vg">A$</span><span class="p">)</span>
<span class="nl">10060</span><span class="w"> </span><span class="vg">C</span><span class="o">=</span><span class="kr">ASC</span><span class="p">(</span><span class="kr">MID$</span><span class="p">(</span><span class="vg">A$</span><span class="p">,</span><span class="vg">I</span><span class="p">,</span><span class="il">1</span><span class="p">))</span>
<span class="nl">10070</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">200</span>
<span class="nl">10080</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span>
<span class="nl">10100</span><span class="w"> </span><span class="c1">REM AFFICHAGE DE CARACTERES</span>
<span class="nl">10110</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="il">8</span><span class="o">:</span><span class="vg">X</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">B</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">A</span><span class="o">=</span><span class="il">112</span>
<span class="nl">10120</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">C</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">127</span>
<span class="nl">10130</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">X</span><span class="o">></span><span class="il">39</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">Y</span><span class="o">+</span><span class="il">1</span>
<span class="nl">10140</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">X</span><span class="o">+</span><span class="il">1</span>
<span class="nl">10150</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300</span>
<span class="nl">10160</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">C</span>
<span class="nl">10200</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="il">12</span><span class="o">:</span><span class="vg">X</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">B</span><span class="o">=</span><span class="il">48</span><span class="o">:</span><span class="vg">A</span><span class="o">=</span><span class="il">112</span>
<span class="nl">10210</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">C</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">31</span>
<span class="nl">10220</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300</span>
<span class="nl">10230</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">X</span><span class="o">+</span><span class="il">1</span>
<span class="nl">10240</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">C</span>
<span class="nl">10300</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="il">13</span><span class="o">:</span><span class="vg">X</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">B</span><span class="o">=</span><span class="il">32</span><span class="o">:</span><span class="vg">A</span><span class="o">=</span><span class="il">112</span>
<span class="nl">10310</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">C</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">127</span>
<span class="nl">10320</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">X</span><span class="o">></span><span class="il">39</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">Y</span><span class="o">+</span><span class="il">1</span>
<span class="nl">10330</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300</span>
<span class="nl">10340</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">X</span><span class="o">+</span><span class="il">1</span>
<span class="nl">10350</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">C</span>
<span class="nl">10400</span><span class="w"> </span><span class="c1">REM COULEURS</span>
<span class="nl">10410</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="il">17</span><span class="o">:</span><span class="vg">X</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">B</span><span class="o">=</span><span class="il">32</span><span class="o">:</span><span class="vg">C</span><span class="o">=</span><span class="il">127</span>
<span class="nl">10420</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">A</span><span class="o">=</span><span class="il">16</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">112</span><span class="w"> </span><span class="k">STEP</span><span class="w"> </span><span class="il">16</span>
<span class="nl">10430</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300</span>
<span class="nl">10440</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">X</span><span class="o">+</span><span class="il">1</span>
<span class="nl">10450</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">A</span>
<span class="nl">10460</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="il">18</span><span class="o">:</span><span class="vg">X</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">B</span><span class="o">=</span><span class="il">33</span>
<span class="nl">10470</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">A</span><span class="o">=</span><span class="il">16</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">112</span><span class="w"> </span><span class="k">STEP</span><span class="w"> </span><span class="il">16</span>
<span class="nl">10480</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300</span>
<span class="nl">10490</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">X</span><span class="o">+</span><span class="il">1</span>
<span class="nl">10500</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">A</span>
<span class="nl">11000</span><span class="w"> </span><span class="c1">REM LECTURE DES REGISTRES INDIRECTS</span>
<span class="nl">11010</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="il">128</span><span class="o">+</span><span class="il">8</span><span class="o">+</span><span class="il">1</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">IND</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">LECTURE</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">TGS</span><span class="w"> </span><span class="p">(</span><span class="vg">EXEC</span><span class="p">)</span>
<span class="nl">11020</span><span class="w"> </span><span class="vg">T</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="il">48929</span><span class="p">)</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">LECTURE</span><span class="w"> </span><span class="vg">R1</span>
<span class="nl">11030</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"TGS="</span><span class="p">;</span><span class="nl">T:</span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">20</span><span class="o">:</span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span>
<span class="nl">11040</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="il">128</span><span class="o">+</span><span class="il">8</span><span class="o">+</span><span class="il">2</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">IND</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">LECTURE</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">MAT</span><span class="w"> </span><span class="p">(</span><span class="vg">EXEC</span><span class="p">)</span>
<span class="nl">11050</span><span class="w"> </span><span class="vg">M</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="il">48929</span><span class="p">)</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">LECTURE</span><span class="w"> </span><span class="vg">R1</span>
<span class="nl">11060</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"MAT="</span><span class="p">;</span><span class="nl">M:</span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">20</span><span class="o">:</span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span>
<span class="nl">11070</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="il">128</span><span class="o">+</span><span class="il">8</span><span class="o">+</span><span class="il">3</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">IND</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">LECTURE</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">PAT</span><span class="w"> </span><span class="p">(</span><span class="vg">EXEC</span><span class="p">)</span>
<span class="nl">11080</span><span class="w"> </span><span class="vg">P</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="il">48929</span><span class="p">)</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">LECTURE</span><span class="w"> </span><span class="vg">R1</span>
<span class="nl">11090</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"PAT="</span><span class="p">;</span><span class="nl">P:</span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">20</span><span class="o">:</span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span>
<span class="nl">11110</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="il">128</span><span class="o">+</span><span class="il">8</span><span class="o">+</span><span class="il">5</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">IND</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">LECTURE</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">DOR</span><span class="w"> </span><span class="p">(</span><span class="vg">EXEC</span><span class="p">)</span>
<span class="nl">11120</span><span class="w"> </span><span class="vg">D</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="il">48929</span><span class="p">)</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">LECTURE</span><span class="w"> </span><span class="vg">R1</span>
<span class="nl">11130</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"DOR="</span><span class="p">;</span><span class="nl">D:</span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">20</span><span class="o">:</span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span>
<span class="nl">11140</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="il">128</span><span class="o">+</span><span class="il">8</span><span class="o">+</span><span class="il">7</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">IND</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">LECTURE</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">ROR</span><span class="w"> </span><span class="p">(</span><span class="vg">EXEC</span><span class="p">)</span>
<span class="nl">11150</span><span class="w"> </span><span class="vg">R</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="il">48929</span><span class="p">)</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">LECTURE</span><span class="w"> </span><span class="vg">R1</span>
<span class="nl">11160</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"ROR="</span><span class="p">;</span><span class="vg">R</span>
<span class="nl">12300</span><span class="w"> </span><span class="c1">REM CHANGE ROR POUR SCROLLING</span>
<span class="nl">12310</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="il">8</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">31</span>
<span class="nl">12320</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48929</span><span class="p">,</span><span class="nl">I:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">ECRITURE</span><span class="w"> </span><span class="vg">R1</span>
<span class="nl">12330</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="il">128</span><span class="o">+</span><span class="il">0</span><span class="o">+</span><span class="il">7</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">IND</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">ECRITURE</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">ROR</span><span class="w"> </span><span class="p">(</span><span class="vg">EXEC</span><span class="p">)</span>
<span class="nl">12340</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">J</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">20</span><span class="o">:</span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">J</span>
<span class="nl">12350</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span>
<span class="nl">12360</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48929</span><span class="p">,</span><span class="il">8</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">ECRITURE</span><span class="w"> </span><span class="vg">R1</span>
<span class="nl">12370</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="il">48936</span><span class="p">,</span><span class="il">128</span><span class="o">+</span><span class="il">0</span><span class="o">+</span><span class="il">7</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">IND</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">ECRITURE</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="vg">ROR</span><span class="w"> </span><span class="p">(</span><span class="vg">EXEC</span><span class="p">)</span>
<span class="nl">13000</span><span class="w"> </span><span class="c1">REM CARACTERES REDÉFINIS</span>
<span class="nl">13010</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">28</span><span class="p">,</span><span class="il">58</span><span class="p">,</span><span class="il">28</span><span class="p">,</span><span class="il">8</span><span class="p">,</span><span class="il">127</span><span class="p">,</span><span class="il">93</span><span class="p">,</span><span class="il">93</span><span class="p">,</span><span class="il">28</span><span class="p">,</span><span class="il">54</span><span class="p">,</span><span class="il">99</span>
<span class="nl">13020</span><span class="w"> </span><span class="vg">A</span><span class="o">=</span><span class="il">192</span>
<span class="nl">13030</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">400</span>
<span class="nl">13100</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="il">20</span><span class="o">:</span><span class="vg">X</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">B</span><span class="o">=</span><span class="il">128</span><span class="o">+</span><span class="il">32</span><span class="o">+</span><span class="il">16</span><span class="o">:</span><span class="vg">A</span><span class="o">=</span><span class="il">112</span><span class="o">:</span><span class="kr">REM</span><span class="w"> </span><span class="vg">GP11</span>
<span class="nl">13110</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">C</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">127</span>
<span class="nl">13120</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">X</span><span class="o">></span><span class="il">39</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">Y</span><span class="o">+</span><span class="il">1</span>
<span class="nl">13125</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">C</span><span class="o">=</span><span class="il">4</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">C</span><span class="o">=</span><span class="il">32</span>
<span class="nl">13130</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300</span>
<span class="nl">13140</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">X</span><span class="o">+</span><span class="il">1</span>
<span class="nl">13150</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">C</span>
<span class="nl">14000</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">14000</span>
</code></pre></div>Dans la prison hantée sur AgonLight2023-12-21T00:00:00+01:002023-12-21T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-12-21:/dans-la-prison-hantee-sur-agonlight.html<p>Comme présenté dans un article précédent, j'ai participé à la game jam <a href="https://itch.io/jam/rpuos-camputers-lynx/entries">Retro Programmers United for Obscure Systems</a>, organisée par <a href="https://www.youtube.com/@Olipix">Olipix</a>. Le principe est de développer un jeu pour une machine qui n'a pas eu une grande ludothèque (moins de 100 titres).</p>
<p>Après avoir terminé ma contribution sur le Lynx …</p><p>Comme présenté dans un article précédent, j'ai participé à la game jam <a href="https://itch.io/jam/rpuos-camputers-lynx/entries">Retro Programmers United for Obscure Systems</a>, organisée par <a href="https://www.youtube.com/@Olipix">Olipix</a>. Le principe est de développer un jeu pour une machine qui n'a pas eu une grande ludothèque (moins de 100 titres).</p>
<p>Après avoir terminé ma contribution sur le Lynx, je me suis dit qu'un portage pour l'AgonLight serait intéressant et plutôt facile. Les capacités graphiques sont bien supérieures, et le processeur est un Z80, supporté par la même toolchain que j’avais utilisée pour le Lynx (z88dk).</p>
<p>Un petit mois plus tard, c'est chose faite. Le jeu est <a href="https://mokona78.itch.io/dans-la-prison-hante">disponible sur itch.io</a>.</p>
<p>J'en ai profité pour ajouter une version en anglais et une version en esperanto. Puisque tout est développé depuis les mêmes sources, ces versions sont aussi disponibles sur le Lynx. </p>
<p>Au passage, la taille de l'exécutable a été un tout petit peu réduite. Pas assez pour entrer sur la version Lynx 48k malheureusement. Et en projetant le gain potentiel en travaillant la compression des données, j'ai assez peu d'espoir d'y arriver. Cela pourrait probablement être possible en reprogrammant le jeu en assembleur, mais j'ai déjà passé assez de temps sur ce projet et j'ai envie de passer à autre chose. De plus, je perdrai la possibilité d'un portage facile sur une machine avec un autre processeur.</p>
<p>J'ai aussi publié <a href="https://github.com/Mokona/in-the-haunted-prison">les sources du jeu sur GitHub</a>.</p>
<p>À bientôt pour de nouvelles aventures !</p>
<p><img alt="Dans la prison hantée sur AgonLight2" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202312/20231221-RunningOnAgonLight2-750.jpg"></p>VG5000µ, Schémas de principe mis à jour en v1.52023-11-21T00:00:00+01:002023-11-21T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-11-21:/vg5000m-schemas-de-principe-mis-a-jour-en-v15.html<p>Et voici une nouvelle mise à jour du schéma de principe.</p>
<p>Il y a une seule modification par rapport à la version 1.4, qui est l'ajout du mode international lorsque la diode <code>6602</code> relie le signal <code>NMI/</code> au <code>Y3/</code> de <code>7807</code>. Avec cette diode présente, le VG5000µ passe en …</p><p>Et voici une nouvelle mise à jour du schéma de principe.</p>
<p>Il y a une seule modification par rapport à la version 1.4, qui est l'ajout du mode international lorsque la diode <code>6602</code> relie le signal <code>NMI/</code> au <code>Y3/</code> de <code>7807</code>. Avec cette diode présente, le VG5000µ passe en anglais.</p>
<p>Merci à <em>Etno</em> pour cette information.</p>
<p>Ce qui donne, mis à jour.</p>
<h4>La platine principale</h4>
<p>Image cliquable pour une version en haute définition. (mise à jour 21 novembre 2023)</p>
<p><a href="https://www.triceraprog.fr/images/202311/VG5000-Schema-v1.5.png"><img alt="Platine principale" src="https://www.triceraprog.fr/images/202311/VG5000-Schema-v1.5-750.png"></a></p>
<h4>La platine K7/Son</h4>
<p>Image cliquable pour une version en haute définition. (mise à jour 9 sept. 2018)</p>
<p><a href="https://www.triceraprog.fr/images/201809/VG5000-Schema-k7-v1.2.png"><img alt="Platine K7/Son" src="https://www.triceraprog.fr/images/201809/VG5000-Schema-k7-v1.2-750.png"></a></p>
<h4>Rappel des versions précédentes</h4>
<ul>
<li><a href="https://www.triceraprog.fr/vg5000m-schemas-de-principe.html">première version</a></li>
<li><a href="https://www.triceraprog.fr/vg5000m-schemas-de-principe-mis-a-jour.html">version 1.3</a></li>
<li><a href="https://www.triceraprog.fr/vg5000m-schemas-de-principe-mis-a-jour-en-v14.html">version 1.4</a></li>
</ul>Un an de Retro Programmers United for Obscure Systems2023-11-20T00:00:00+01:002023-11-20T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-11-20:/un-an-de-retro-programmers-united-for-obscure-systems.html<p>Un peu plus d'un an en fait, puisque <a href="https://www.youtube.com/@Olipix">Olipix</a> lance ce groupe en juin 2022. L'idée, je le rappelle, est d'offrir à des machines qui en leur temps n'ont pas eu une grande ludothèque quelques titres supplémentaires, dans un format game jam de trois mois (souvent étendus à quatre).</p>
<p>Dans …</p><p>Un peu plus d'un an en fait, puisque <a href="https://www.youtube.com/@Olipix">Olipix</a> lance ce groupe en juin 2022. L'idée, je le rappelle, est d'offrir à des machines qui en leur temps n'ont pas eu une grande ludothèque quelques titres supplémentaires, dans un format game jam de trois mois (souvent étendus à quatre).</p>
<p>Dans cet article, je vais revenir rapidement sur les 4 jeux que j'ai développés à cette occasion, avec quelques commentaires.</p>
<h2>VG5000µ : La Maison dans la colline</h2>
<p>La première machine choisie a été le VG5000µ, une machine que, vous le savez si vous suivez ce blog, j'étudie depuis un moment. Pour un premier développement réel (autre que des tests), je voulais un affichage rapide, mais sans aller dans un jeu rapide. L'idée du jeu d'aventure graphique avec support de texte est arrivée rapidement.</p>
<p>J'ai commencé ici une <a href="/Projets/20221222-MDLC-Partie-1.html">série d'articles</a> sur le développement du jeu.</p>
<p>Le jeu est développé avec <a href="https://z88dk.org/">z88dk</a>, en C avec un peu d'assembleur pour l'affichage. J'ai aussi réalisé tous les graphismes (et ça se voit ?) avec <a href="https://orama-interactive.itch.io/pixelorama">Pixelorama</a>. Pour les outils de données, c'est du Python avec un fichier de description du jeu, qui sort les données binaires injectées dans l'exécutable.</p>
<p>Le jeu est disponible sur <a href="https://mokona78.itch.io/la-maison-dans-la-colline">itch.io</a>.</p>
<p>Olipix fait une revue du jeu <a href="https://www.youtube.com/watch?v=nFzPpzOrcEU&t=1347s">dans cette vidéo</a>.</p>
<p><img alt="La maison dans la colline" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202311/RPUfOS-Maison.png"></p>
<h2>EXL100 : Plouf... in space</h2>
<p>La deuxième machine choisie a été l'EXL100, une machine que je ne connaissais pas du tout. Et une machine plutôt exotique. Un processeur que je ne connais pas, l'essentiel de la mémoire utilisable non adressable par le processeur... et un synthétiseur vocal plutôt difficile à utiliser.</p>
<p>J'avais quelques idées pour utiliser la machine, mais j'ai rapidement compris que le temps de me familiariser avec et de faire des tests, je n'aurais pas le temps de faire un jeu. J'ai donc changé d'avis et suis partie sur un jeu programmé en BASIC, qui permet facilement d'utiliser toute la mémoire. Et pour le type de jeu, un touché-coulé, mais avec un twist : les bateaux sont en mouvement. Je n'étais pas très certain que le gameplay donne quelque chose, mais au final et après quelques ajustements, ça fonctionne plutôt bien.</p>
<p>Sur la fin, les calculs en BASIC commençaient à être un peu lents, et j'ai donc ajouté un peu d'assembleur dans le peu de mémoire adressable par le processeur. J'ai utilisé l'assembleur <a href="http://john.ccac.rwth-aachen.de:8000/as/">ASL</a> qui est un des rares supportant le TMS7020. J'y associe un petit script en Python pour générer les DATA pour le BASIC. C'était assez manuel, je n'aime pas trop ça, mais comme souvent : manque de temps pour des choix assez tardifs.</p>
<p>Puis les bateaux sont devenus des vaisseaux parce que... parce que.</p>
<p>Je voulais aussi redéfinir quelques caractères pour un affichage plus sympa, mais je n'ai pas eu le temps. Peut-être un jour si je ressors l'idée ?</p>
<p>Le jeu est disponible sur <a href="https://mokona78.itch.io/plouf-in-space">itch.io</a>.</p>
<p>Olipix fait une revue du jeu <a href="https://www.youtube.com/watch?v=QxLnOV9Y8fA">dans cette vidéo</a>, dans laquelle nous avons aussi discuté du développement du jeu.</p>
<p><img alt="Plouf... in space" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202311/RPUfOS-Plouf.png"></p>
<h2>Aquarius : Le jardin des œufs</h2>
<p>La troisième machine a été l'Aquarius. Retour à du Z80 qui m'est familier. Et une machine simple. Dépouillée même. L'avantage pour le graphisme, c'est qu'il faut faire avec les caractères de la machine. Heureusement, un éditeur dédié à la machine est <a href="https://aquarius.mattpilz.com/draw/">disponible en ligne</a>, ce qui a bien simplifié les choses.</p>
<p>Comme la fin de la game jam était autour de Pâques, j'ai voulu faire thématique avec une chasse au œufs. Dans la lignée des anciens jeux avec des plateformes qui n'ont pas vraiment de sens, j'ai designé un jardin étrange, avec quelques éléments de game design classiques.</p>
<p>Dans tous ces jeux, je pars d'un principe : les joueurs de retro n'ont souvent pas vraiment de temps à passer sur ces machines, particulièrement celles qui ne sortent jamais. Je vise donc des jeux courts, quelque chose qui puisse se découvrir et se terminer en une vingtaine de minutes. Quitte à se qu'il se termine en moins de 5 minutes lorsque l'on connaît la solution.</p>
<p>Puisque le jeu se termine rapidement, j'ai ajouté un compteur de mouvements, avec d'y associer une sorte de time attack... mais sans temps.</p>
<p>Le jeu est entièrement en assembleur, utilisant <a href="https://z00m128.github.io/sjasmplus/documentation.html">sjasmplus</a>, un assembleur que j'avais essayé auparavant et que je voulais creuser un peu plus. C'était l'occasion. Un jeu tout en assembleur, ça prend un peu plus de temps à développer, par contre, question mémoire, ça permettait de faire petit. Et encore, il y a de la marge d'optimisation.</p>
<p>Le jeu est disponible sur <a href="https://mokona78.itch.io/le-jardin-des-oeufs">itch.io</a>.</p>
<p>Olipix fait une revue du jeu <a href="https://youtu.be/K7NlEP6SVcw?si=Rjaa_zgFN2Lx2v97&t=1800">dans cette vidéo</a>.</p>
<p><img alt="Le jardin des œufs" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202311/RPUfOS-Jardin.png"></p>
<h2>Camputers Lynx : Dans la prison hantée</h2>
<p>Une nouvelle session, et une nouvelle machine sortie de nulle part. J'avais connaissance de la machine, mais je ne m'étais jamais penché dessus et... quel étonnement. Cette machine a été conçue avec des choix originaux. C'est du Z80, bon point pour moi. La partie vidéo est aussi très intéressante avec des pixels indépendants en couleur les uns des autres. Une rareté pour l'époque. Bien entendu, je me dis qu'il faudra absolument utiliser cette particularité.</p>
<p>J'ai passé un bon moment sur la machine sur des tests variés. Les limitations d'accès à la RAM, avec un mapping vraiment pas simple, et des émulateurs pas très aboutis m'ont fait désespérer une paire de fois. J'ai souvent mis le projet sur le côté.</p>
<p>Pour le jeu, je voulais aller du côté de Temple of Apshaï, un jeu qui m'avait marqué étant petit. Comme d'habitude, dans une formule courte. Vu tout le temps passés à faire des tests et à procrastiner, j'ai aussi fait le choix d'utiliser un set de sprites désigné par <a href="https://kenney.nl/">Kenney</a>. Sur base d'un set noir et blanc, j'ai ajouté un peu de couleurs, puisque je voulais utiliser les capacités de la machine en la matière.</p>
<p>Initialement, j'étais parti sur la réutilisation du format et des scripts de la Maison dans la Colline. Cependant, j'avais un peu luté avec le côté semi-manuel de la méthode. J'ai donc décidé d'utiliser <a href="https://www.mapeditor.org/">Tiled</a> pour la conception des niveaux, et un script Python pour générer les données. Tiled est vraiment sympa à utiliser et je pense que je le réutiliserai dans le futur pour des projets similaires.</p>
<p>Pour le code, j'ai utilisé <a href="https://z88dk.org/">z88dk</a> avec un peu d'assembleur pour l'affichage, mais beaucoup moins que pour le VG5000µ. Je voulais réutiliser le principe d'affichage rapide, mais les émulateurs Lynx ne supportent pas (encore ?) la redirection du vecteur d'interruption pour la VSYNC. J'ai rapidement fait une croix dessus. N'ayant pas accès à cette machine, je devais absolument faire avec les émulateurs.</p>
<p>Aussi, rapidement, j'ai compris que le jeu n'entrerais pas en mémoire de la version 48k, mais uniquement sur la version 96k (cela semble beaucoup, mais de cette mémoire, 32ko sont réservés pour la vidéo).</p>
<p>Le jeu est disponible sur <a href="https://mokona78.itch.io/dans-la-prison-hante">itch.io</a>.</p>
<p>Retro VynZ a fait une partie filmée <a href="https://www.youtube.com/watch?v=WOuM0tr9eTg">sur sa chaîne YouTube</a>.</p>
<p>Olipix présente le jeu, suivi d'un petit échange <a href="https://youtu.be/QKNcR9iI_K0">sur sa chaîne YouTube</a>.</p>
<p><img alt="Dans la prison hantée" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202311/RPUfOS-Prison.png"></p>
<h2>Et la suite ?</h2>
<p>J'ai passé de bons moments sur ces projets. Découvrir ces machines est un plaisir (même si elles sont parfois un peu énervantes), cela profite un peu (un tout petit peu) à leur visibilité, à leur redécouverte. Et si ça n'amuse que nous, c'est déjà ça.</p>
<p>Je ne connais pas encore la prochaine machine, je ne sais donc pas vers où je vais aller, mais j'ai en tête deux défis : utiliser un peu plus de couleurs et, pour la première fois, ajouter du son !</p>
<p>À bientôt pour de nouvelles aventures !</p>La palette de couleur de l'Agon Light2023-06-10T00:00:00+02:002023-06-10T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2023-06-10:/la-palette-de-couleur-de-lagon-light.html<p>Ces derniers temps, je m'amuse avec un <a href="https://github.com/TheByteAttic/AgonLight">AgonLight</a> (ou plus exactement un AgonLight2, qui est la <a href="https://www.olimex.com/Products/Retro-Computers/AgonLight2/open-source-hardware">version Olimex</a>).</p>
<p>Cette machine est assez récente et possède une petite communauté. Sa documentation est par contre très éparse pour le moment. Du plus, la partie graphique de la machine se reposant sur <a href="http://www.fabglib.org/">FabGL …</a></p><p>Ces derniers temps, je m'amuse avec un <a href="https://github.com/TheByteAttic/AgonLight">AgonLight</a> (ou plus exactement un AgonLight2, qui est la <a href="https://www.olimex.com/Products/Retro-Computers/AgonLight2/open-source-hardware">version Olimex</a>).</p>
<p>Cette machine est assez récente et possède une petite communauté. Sa documentation est par contre très éparse pour le moment. Du plus, la partie graphique de la machine se reposant sur <a href="http://www.fabglib.org/">FabGL</a>, une partie des informations intéressantes sont en fait à déduire de cette bibliothèque. Mais d'autres se déduisent de l'implémentation pour la machine du BBC Basic.</p>
<p>Je vais me servir de ce blog pour prendre quelques notes. Cette semaine, j'ai tourné en rond autour de la gestion de la palette et des modes graphiques disponibles.</p>
<h2>Modes graphiques</h2>
<p>Les modes graphiques, à cette date (MOS 1.03), sont :</p>
<table>
<thead>
<tr>
<th>Mode</th>
<th>Résolution (Pixels)</th>
<th>Fréquence (Hz)</th>
<th>Nb. de couleurs</th>
<th>Palette?</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>1024x768</td>
<td>60</td>
<td>2</td>
<td>Oui</td>
</tr>
<tr>
<td>1</td>
<td>512x384</td>
<td>60</td>
<td>16</td>
<td>Oui</td>
</tr>
<tr>
<td>2</td>
<td>320x200</td>
<td>75</td>
<td>64</td>
<td>Non</td>
</tr>
<tr>
<td>3</td>
<td>640x480</td>
<td>60</td>
<td>16</td>
<td>Oui</td>
</tr>
</tbody>
</table>
<h2>Palette de couleurs</h2>
<p>Dans les modes en palette, les couleurs se choisissent parmi l'espace de couleur complet RGB222. Les 4 niveaux pour chaque composante sont 0x00, 0x55, 0xAA et 0xFF. Ce qui donne :</p>
<p><img alt="Palette RGB222 de l'AgonLight" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202306/AgonLight-Palette.png"></p>
<p><strong>Attention, la palette est réinitialisée lors d'un changement de mode</strong></p>
<h2>R,G,B ?</h2>
<p>Une chose étonnante, c'est qu'il est possible de spécifier les palettes, et il est obligatoire de spécifier les pixels des surfaces, en RGB888, alors que l'espace de couleur est RGB222. Le VDP (ou plutôt FabGL), va chercher à trouver les couleurs en fonction. Ce n'est pas hyper pratique au premier abord, et c'est un bon gâchis d'espace.</p>
<p>Mieux vaut ne spécifier que des couleurs faisant partie de la palette.</p>La Maison dans la colline, partie 72023-05-10T00:00:00+02:002023-05-10T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2023-05-10:/la-maison-dans-la-colline-partie-7.html<p>Dans ce septième article de la série sur le jeu « <a href="https://mokona78.itch.io/la-maison-dans-la-colline">La maison dans la colline</a> », il va être question de tests anti-regression.</p>
<h2>Regressions</h2>
<p>Mais qu'est-ce qu'une regression ? C'est un fonctionnement qui donnait toute satisfaction et qui, suite à un changement dans le système, se met à ne plus fonctionner comme …</p><p>Dans ce septième article de la série sur le jeu « <a href="https://mokona78.itch.io/la-maison-dans-la-colline">La maison dans la colline</a> », il va être question de tests anti-regression.</p>
<h2>Regressions</h2>
<p>Mais qu'est-ce qu'une regression ? C'est un fonctionnement qui donnait toute satisfaction et qui, suite à un changement dans le système, se met à ne plus fonctionner comme attendu. Autrement dit, une apparition de bug !</p>
<p>Les bugs n'arrivent jamais de nulle part, il y a toujours une raison. Mais plus un programme est grand, plus il se complexifie et plus le risque de programmer des morceaux qui entrent en conflit apparaît. C'est à peu près inéluctable et le développement d'un logiciel est, normalement, accompagné d'un certain nombre de règles pour éviter au mieux et surtout repérer au plus vite les défauts qui apparaissent.</p>
<p>La vitesse de détection est importante, car il une regression peut ne pas être immédiatement flagrante. Il est possible que quelque chose casse sur une partie « éloignée » de ce sur quoi on travaille sur le moment. Il est aussi possible que le défaut soit subtile ; présent, mais pas évident. Tout à l'air de bien fonctionner en apparence, mais pas dans les détails. Et si on continue à développer avec ce défaut présent, il se peut très bien que l'on amplifie le problème, ajoutant du bug à du bug.</p>
<p>La vitesse est aussi importante pour une question de contexte. Lorsque l'on a en tête une partie en train d'être travaillée, il est plus simple de corriger ce qui vient d'être modifié que lorsque l'on s'en rend compte plus tard, lorsqu'on est passé à autre chose.</p>
<p>Dans le contexte de « La maison dans la colline », je suis tout seul et, comme je l'ai déjà dit auparavant, j'essaie de maximiser mon temps libre passé sur le projet. Ainsi, une session à essayer de trouver et corriger la raison d'un bug introduit deux sessions avant ne m'enchante pas du tout.</p>
<p>J'ai donc mis en place deux systèmes pour m'aider à détecter rapidement les bugs</p>
<h2>Tests unitaires</h2>
<p>Le premier système est un système simpliste de tests unitaires. Le principe est de mettre le système dans un certain état, de faire une opération, puis de vérifier que le système est dans l'état attendu.</p>
<p>J'ai mis en place ce système après avoir débuté le projet et lorsque celui-ci commençait à devenir un peu complexe. Malheureusement ou heureusement, j'avais pris quelques raccourcis qui rendaient certains tests un compliqués. Cela m'a pris un peu de temps pour nettoyer ça ; au passage, j'en suis sorti avec quelques nettoyages bienvenue dans le code.</p>
<p>Je n'ai pas non plus beaucoup de tests. Majoritairement, je fais des tests sur la gestion de l'inventaire, qui m'a causé quelques soucis et qui a été ce qui m'a décidé à mettre des tests unitaires. Je fais aussi quelques tests sur mon micro-allocateur de mémoire dynamique (celui de z88dk ne me convenant pas).</p>
<p>Lorsque je lance le programme avec une option de compilation, les tests sont exécutés dans une page spéciale, au démarrage du jeu. Je peux ainsi m'assurer que les tests passent et que donc le fonctionnement de mon système d'inventaire et d'allocation sont toujours d'aplomb.</p>
<p>Voilà à quoi ressemble le test des objets, une fois que j'ai mis en place un environnement de tests avec des objets dans des pièces :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">CHECK</span><span class="p">(</span><span class="n">object_count_in_room</span><span class="p">(</span><span class="n">ROOM</span><span class="p">(</span><span class="mi">1</span><span class="p">))</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">2</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">CHECK</span><span class="p">(</span><span class="n">object_count_in_room</span><span class="p">(</span><span class="n">ROOM</span><span class="p">(</span><span class="mi">254</span><span class="p">))</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">CHECK</span><span class="p">(</span><span class="n">object_count_in_room</span><span class="p">(</span><span class="n">ROOM</span><span class="p">(</span><span class="mi">255</span><span class="p">))</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>Et voilà une capture d'écran lors d'une regression :</p>
<p><img alt="Liste terminée des fonctions du jeu." class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202305/20220820-MDLC-FailingTests.png"></p>
<h2>Mode automatique</h2>
<p>L'autre système que j'ai mis en place est un mode de jeu automatique. Au bout d'un moment, et alors que le nombre d'actions et de pièce commençait à grandir, il me fallait régulièrement jouer toute une première partie du jeu. Je vérifiais les mouvements, les portes et changements de pièce, la prise d'objet dans l'inventaire, l'utilisation d'un objet.</p>
<p>Ça a rapidement été long, pas très amusant et source d'erreurs : est-ce que je fais bien les mêmes étapes ? Est-ce que j'ai encore envie de le faire parce que ça m'ennuie ? Est-ce que quelque chose ne va pas casse pile la fois où j'aurai la flemme de conduire les tests ?</p>
<p>Solution : automatiser le test.</p>
<p>Lorsque je compile le jeu avec le mode automatique inclue, je déroule un petit script qui va exécuter les actions à ma place. C'est assez simpliste, je n'ai pas de retour d'erreur automatisé. Mais au moins, si je vois quelque chose d'étrange, ou bien si le personnage se retrouve bloqué sur une étape, je peux le voir rapidement. Et je peux alors conduire un test manuel pour comprendre dans les détails ce qu'il se passe.</p>
<p>Il n'y a pas beaucoup d'étapes dans ce test automatique, car c'est un peu fastidieux à maintenir. J'ai hésité un moment à rendre le système plus malin, avec une création de script depuis les données du jeu. Par exemple en trouvant le chemin pour aller d'un point A à un point B automatiquement. Mais je ne suis pas allé jusque là, j'ai trouvé un compromis avec un système qui « tente » d'aller vers une position, mais de manière simpliste, que je dois aider manuellement parfois.</p>
<p>Mais que de temps gagné au final !</p>
<p>Voici à quoi ressemble le début du script, qui est stocké dans un tableau de caractères dans le code source :</p>
<div class="highlight"><pre><span></span><code><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">script</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="sc">'W'</span><span class="p">,</span><span class="w"> </span><span class="sc">'P'</span><span class="p">,</span><span class="w"> </span><span class="mi">4</span><span class="p">,</span><span class="w"> </span><span class="c1">// Wait page 4 (test page)</span>
<span class="w"> </span><span class="sc">'W'</span><span class="p">,</span><span class="w"> </span><span class="sc">'T'</span><span class="p">,</span><span class="w"> </span><span class="mi">20</span><span class="p">,</span><span class="c1">// Wait frames</span>
<span class="w"> </span><span class="sc">'K'</span><span class="p">,</span><span class="w"> </span><span class="sc">' '</span><span class="p">,</span><span class="w"> </span><span class="c1">// Press ' '</span>
<span class="w"> </span><span class="sc">'W'</span><span class="p">,</span><span class="w"> </span><span class="sc">'P'</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="sc">'K'</span><span class="p">,</span><span class="w"> </span><span class="sc">' '</span><span class="p">,</span><span class="w"> </span><span class="c1">// Press ' '</span>
<span class="w"> </span><span class="sc">'W'</span><span class="p">,</span><span class="w"> </span><span class="sc">'P'</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">,</span><span class="w"> </span><span class="c1">//</span>
<span class="w"> </span><span class="sc">'K'</span><span class="p">,</span><span class="w"> </span><span class="sc">'A'</span><span class="p">,</span><span class="w"> </span><span class="c1">// Press 'A'</span>
<span class="w"> </span><span class="sc">'W'</span><span class="p">,</span><span class="w"> </span><span class="sc">'T'</span><span class="p">,</span><span class="w"> </span><span class="mi">5</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Go to Kitchen</span>
<span class="w"> </span><span class="sc">'K'</span><span class="p">,</span><span class="w"> </span><span class="n">KEY_UP</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="sc">'K'</span><span class="p">,</span><span class="w"> </span><span class="n">KEY_UP</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="sc">'K'</span><span class="p">,</span><span class="w"> </span><span class="n">KEY_UP</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="sc">'K'</span><span class="p">,</span><span class="w"> </span><span class="n">KEY_UP</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="sc">'G'</span><span class="p">,</span><span class="w"> </span><span class="sc">'R'</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="c1">// Go to Room 2</span>
</code></pre></div>
<h2>Conclusion</h2>
<p>J'ai déjà évoqué mon goût pour les outils et les automatisation dans les articles précédents. L'automatisation des tests est un outil de plus pour se simplifier la vie. Cela demande un peu d'effort en amont, mais force parfois à mieux réfléchir son code pour le rendre flexible, ce qui est bénéfique lors de la mise au point du jeu, et permet un gros gain de temps sur la durée du projet, pour peu que l'on trouve le bon équilibre entre le temps passé à créer les outils et le gain de temps espéré grâce à eux.</p>La Maison dans la colline, partie 62023-05-04T00:00:00+02:002023-05-04T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2023-05-04:/la-maison-dans-la-colline-partie-6.html<p>Dans ce sixième article de la série sur le jeu « <a href="https://mokona78.itch.io/la-maison-dans-la-colline">La maison dans la colline</a> », il va être question des structures du jeu, de portage et de « binarisation ».</p>
<h2>Les structures</h2>
<p>« <strong>La maison dans la colline</strong> » est un jeu programmé en grande partie en C. L'idée derrière est de pouvoir porter …</p><p>Dans ce sixième article de la série sur le jeu « <a href="https://mokona78.itch.io/la-maison-dans-la-colline">La maison dans la colline</a> », il va être question des structures du jeu, de portage et de « binarisation ».</p>
<h2>Les structures</h2>
<p>« <strong>La maison dans la colline</strong> » est un jeu programmé en grande partie en C. L'idée derrière est de pouvoir porter assez facilement sur une autre machine qui n'aurait potentiellement pas le même processeur, c'est aussi une manière de faciliter les itérations. Le jeu manipulant des objets, des pièces pour circuler, un personnage, il est intéressant de pouvoir se reposer sur des structures de données et de les manipuler, de les faire évoluer, sans avoir à adapter un code assembleur en parallèle (même s'il existe des assembleurs qui peuvent faciliter ces opérations).</p>
<h3>Les pièces</h3>
<p>La première structure que je présente est celle des <strong>pièces</strong> de la maison et des <strong>portes</strong> qui les relient. Ces données sont fixes et pourraient se situer en ROM si je jeu était sur ROM.</p>
<div class="highlight"><pre><span></span><code><span class="k">typedef</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">Door</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">position</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">destination_room</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">destination_position</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"> </span><span class="n">Door</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>Une porte a une position dans la pièce et amène vers une pièce de destination (identifiée par un octet) à une position donnée dans cette pièce de destination.</p>
<div class="highlight"><pre><span></span><code><span class="k">typedef</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">Room</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">id</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">short</span><span class="w"> </span><span class="n">shift_to_next</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">short</span><span class="w"> </span><span class="n">shift_to_doors</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">position</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">size</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">enter_text</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">door_count</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">Door</span><span class="o">*</span><span class="w"> </span><span class="n">doors</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">data</span><span class="p">[];</span><span class="w"></span>
<span class="p">}</span><span class="w"> </span><span class="n">Room</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>Une pièce est un peu plus complexe. Elle est identifiée par un octet (<code>id</code>) qui est suivi par deux nombres de 16 bits qui sont en fait des déplacements en mémoire. <code>shift_to_next</code> est un offset de chaînage vers la pièce suivante. Toutes les données des pièces sont contiguës en mémoire formant une liste chaînée unidirectionnelle. Ainsi, avec un pointeur vers une structure <code>Room</code>, si on avance de <code>shift_to_next</code> octets, on arrivera sur la pièce suivante dans les données.</p>
<p><code>shift_to_doors</code> est un peu plus complexe. C'est une indication qui permet de construire le pointeur <code>doors</code> un peu plus loin dans la structure.</p>
<p>Comme on peut le voir, la structure <code>Room</code> se termine par un tableau de taille non spécifiée d'octets. Dans ce tableau se trouvent les données graphiques de la pièce suivies par les données des portes présentes dans la pièce. Ces deux données sont de taille variable et s'il est facile de connaître l'emplacement des données graphiques (c'est <code>data</code>), il est plus compliqué de connaître le début des portes qui suivent. Surtout que les données graphiques sont compressées.</p>
<p>Il y aurait plusieurs autres manières de faire. J'aurais pu mettre les portes (dont les données ne sont pas compressées) en premier et calculer le déplacement à partir du nombre de portes qui est une donnée connue. Mais les portes ont connu différentes implémentations et se sont finalement retrouvées là. Puis la fin du projet est arrivée et elles y sont restées.</p>
<p>La <code>position</code> et la taille (<code>size</code>) de la pièce indiquent la façon dont elle doit-être affichée à l'écran. <code>enter_text</code> est un identifiant vers le texte qui apparaît à l'écran en entrant. Et <code>door_count</code> comme son nom l'indique, précise le nombre de portes présentes dans la pièce.</p>
<p>Les données graphiques d'une pièce sont compressées selon un schéma RLE. Lorsqu'on entre dans un pièce ces données sont décompressées dans une zone temporaire et envoyées à l'affichage.</p>
<h3>Les objets</h3>
<p>La structure qui décrit les objets est la suivante. Là encore, ce sont des données fixes.</p>
<div class="highlight"><pre><span></span><code><span class="k">typedef</span><span class="w"> </span><span class="k">struct</span><span class="w"> </span><span class="nc">Object</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">name_id</span><span class="p">;</span><span class="w"> </span><span class="c1">// the resource id for the text in the inventory. Used also to designate the object.</span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">char_mode</span><span class="p">;</span><span class="w"> </span><span class="c1">// what mode for the display</span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">character</span><span class="p">;</span><span class="w"> </span><span class="c1">// what char to display</span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">properties</span><span class="p">;</span><span class="w"> </span><span class="c1">// object properties</span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">room_id</span><span class="p">;</span><span class="w"> </span><span class="c1">// initial room</span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">position</span><span class="p">[</span><span class="mi">2</span><span class="p">];</span><span class="w"> </span><span class="c1">// initial position in the room</span>
<span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">action_text_id</span><span class="p">;</span><span class="c1">// text id when the action is done on the object</span>
<span class="p">}</span><span class="w"> </span><span class="n">Object</span><span class="p">;</span><span class="w"></span>
</code></pre></div>
<p>Magnifique, le code est commenté.</p>
<p>Un objet est donc identifié par un identifiant <code>name_id</code> qui est aussi l'identifiant du texte qui y est associé. C'est un choix que j'ai regretté, il aurait été bien plus pratique d'avoir un identifiant pour l'objet lui-même séparé du texte qui le décrit. Plus loin, on voit un autre identifiant du texte écrit lorsque l'on effectue une action. Là encore, c'est assez peu flexible et cela m'a obligé à ne considérer qu'une seule action par objet. Je m'en suis sorti et on dit que les contraintes amènent de la créativité.</p>
<p><code>char_mode</code> et <code>character</code> donnent les informations d'affichage. Il n'y a pas de couleur car les objets ont une couleur fixe dans ce jeu, pour indiquer que des actions peuvent être faites dessus.</p>
<p><code>properties</code> indique ce que l'on peut faire de l'objet : est-ce qu'on peut le prendre, est-ce qu'on peut le lire, est-ce que c'est un déclencheur d'évènement, est-ce que l'on peut marcher dessus, est-ce qu'il est transformable en un autre objet et enfin, est-ce que c'est un téléporteur.</p>
<p>Les téléporteurs sont en fait les portes. Initialement, j'avais un système spécifique de traitement des portes. Je l'ai plus tard unifié avec le traitement des objets de manière générale.</p>
<p>L'objet a aussi un pièce (<code>room_id</code>) et un emplacement (<code>position</code>) qui désignent l'endroit où se trouve l'objet en début de jeu. Cette information est immuable et servira lorsque l'on relance le jeu à tout remettre en place. Au début du jeu, un tableau des localisations réelles des objets est créé en mémoire et ce tableau qui sera modifié en fonction des actions.</p>
<p>Il existe deux pièces spéciales dans le jeu. Un pièce « nulle part » dans laquelle sont déplacés les objets qui ne sont plus valides (par exemple, une clé après avoir été utilisée). La seconde pièce est « l'inventaire ». Cela permet de s'assurer qu'un objet est toujours dans une pièce. Prendre un objet, c'est changer sa pièce courante pour celle de l'inventaire. En échangeant ses informations avec l'objet qu'il remplace dans l'inventaire, ce dernier est naturellement posé dans la pièce.</p>
<h3>La binarisation</h3>
<h4>Partie logique</h4>
<p>Si au début du développement il est possible d'indiquer directement dans le code les pièces (non compressées) et les objets, ça se révèle rapidement impraticable. Pour mettre au point le jeu, un éditeur est plus pratique. Cependant je n'avais non beaucoup de temps à consacrer au développement d'un éditeur de jeu. </p>
<p>Dans ces cas là, une manière classique de faire est de travailler sur des fichiers texte que l'on transpose dans le format binaire attendu par le jeu. D'où le terme « binarisation ». Un autre terme existe : « cooking »... et probablement d'autres.</p>
<p>Voici à quoi ressemble la première pièce du jeu :</p>
<div class="highlight"><pre><span></span><code><span class="nt">Room</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="nt">Entrée</span><span class="w"></span>
<span class="nt">Id</span><span class="o">:</span><span class="w"> </span><span class="nt">1</span><span class="w"></span>
<span class="nt">Position</span><span class="o">:</span><span class="w"> </span><span class="nt">4</span><span class="o">,</span><span class="nt">8</span><span class="w"></span>
<span class="nt">Size</span><span class="o">:</span><span class="w"> </span><span class="nt">9</span><span class="o">,</span><span class="nt">15</span><span class="w"></span>
<span class="nt">EnterText</span><span class="o">:</span><span class="w"> </span><span class="nt">42</span><span class="w"></span>
<span class="nt">Description</span><span class="w"></span>
<span class="err">#########</span><span class="w"></span>
<span class="err">###</span><span class="p">#</span><span class="nn">D</span><span class="err">####</span><span class="w"></span>
<span class="err">#</span><span class="w"> </span><span class="err">#</span><span class="w"></span>
<span class="err">#</span><span class="w"> </span><span class="err">#</span><span class="w"></span>
<span class="err">##</span><span class="w"> </span><span class="err">#</span><span class="w"></span>
<span class="err">#</span><span class="w"> </span><span class="err">#</span><span class="w"></span>
<span class="err">#</span><span class="w"> </span><span class="err">#</span><span class="w"></span>
<span class="nt">E</span><span class="w"> </span><span class="err">#</span><span class="w"></span>
<span class="nt">E</span><span class="w"> </span><span class="err">#</span><span class="w"></span>
<span class="err">##</span><span class="w"> </span><span class="err">##</span><span class="w"></span>
<span class="err">##</span><span class="w"> </span><span class="err">##</span><span class="w"></span>
<span class="err">#</span><span class="w"> </span><span class="err">#</span><span class="w"></span>
<span class="err">#</span><span class="w"> </span><span class="nt">i</span><span class="w"> </span><span class="err">#</span><span class="w"></span>
<span class="err">###</span><span class="p">#</span><span class="nn">F</span><span class="err">####</span><span class="w"></span>
<span class="err">#########</span><span class="w"></span>
<span class="nt">Doors</span><span class="w"></span>
<span class="nt">D</span><span class="p">:</span><span class="nd">3</span><span class="o">|</span><span class="nt">E</span><span class="o">^</span><span class="w"></span>
<span class="nt">E</span><span class="p">:</span><span class="nd">2</span><span class="o">|</span><span class="nt">E</span><span class="o"><</span><span class="w"></span>
<span class="nt">F</span><span class="p">:</span><span class="nd">255</span><span class="o">|</span><span class="nt">A</span><span class="o">></span><span class="w"></span>
<span class="nt">Objects</span><span class="w"></span>
<span class="nt">i</span><span class="p">:</span><span class="nd">G</span><span class="err">'</span><span class="nt">10</span><span class="o">,</span><span class="nt">68</span><span class="o">,</span><span class="nt">None</span><span class="o">,</span><span class="nt">9</span><span class="w"> </span><span class="o">;</span><span class="w"> </span><span class="nt">Apparition</span><span class="w"></span>
<span class="nt">Locks</span><span class="w"></span>
<span class="nt">F</span><span class="p">:</span><span class="nd">18</span><span class="o">,</span><span class="nt">56</span><span class="w"></span>
<span class="nt">EndRoom</span><span class="w"></span>
</code></pre></div>
<p>Tous les <code>#</code> sont des emplacements bloquants : des murs ou des objets de décors. Les autres caractères (souvent des lettres) sont des emplacements spéciaux décrits à la suite de la partie graphique. Ainsi, on voit trois portes, un objet et un verrou.</p>
<p>Les portes sont suivies d'un petit code qui indique la pièce d'arrivée et un emplacement sous la forme d'une lettre (que l'on trouvera dans cette pièce) ainsi qu'une direction naturelle pour le sprite du personnage.</p>
<p>Les objets sont suivis d'informations graphiques (G'10,68 signifie : caractère numéro 68 dans la palette G'10), des propriété et d'un identifiant de texte. Ce qui suit le point virgule est un commentaire, il n'est pas lu.</p>
<p>Toutes ces données sont traitées et envoyées dans un fichier de données qui sera inclus au jeu.</p>
<h4>Partie graphique</h4>
<p>La parte graphique est elle aussi binarisée. Pour cela, j'utilise <a href="https://orama-interactive.itch.io/pixelorama">Pixelorama</a> avec une palette d'objets graphiques et je dessine la pièce. La binarisation s'occupe de découper cela en morceaux de 10 pixels par 8 afin de construire la liste des caractères à redéfinir.</p>
<p>C'est à moi de m'assurer que les données logiques et graphiques sont cohérentes. Entre autre que les tailles de pièces soient identiques. Il y aurait de la marge pour aller plus loin avec un éditeur mais encore une fois, c'était dans un délai trop court pour cela. Peut-être plus tard ?</p>
<h2>La suite ?</h2>
<p>En effet, l'idée que j'avais en essayant de construire des structures réutilisables et flexibles étaient de pouvoir les... réutiliser. Et pourquoi pas étendre le jeu ou bien en faire un autre sur le même principe ? Avec cette fois un peu plus de temps à passer sur les outils.</p>
<p>Pourquoi pas. C'est une idée que je garde dans un coin de la tête.</p>La Maison dans la colline, partie 52023-02-23T00:00:00+01:002023-02-23T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-02-23:/la-maison-dans-la-colline-partie-5.html<p>Dans ce cinquième article, je vais aborder la méthodologie que j'ai appliquée pour le développement de « <a href="https://mokona78.itch.io/la-maison-dans-la-colline">La maison dans la colline</a> ».</p>
<h2>La planification</h2>
<p>Dans un premier temps, j'avais jeté sur papier (électronique) la liste des fonctionnalités que je voulais implémenter, en partant de l'idée générale du jeu et en descendant …</p><p>Dans ce cinquième article, je vais aborder la méthodologie que j'ai appliquée pour le développement de « <a href="https://mokona78.itch.io/la-maison-dans-la-colline">La maison dans la colline</a> ».</p>
<h2>La planification</h2>
<p>Dans un premier temps, j'avais jeté sur papier (électronique) la liste des fonctionnalités que je voulais implémenter, en partant de l'idée générale du jeu et en descendant successivement sur ce dont je pensais avoir besoin. Puis j'ai segmenté cette liste en thèmes, comme par exemple « mouvements du personnage » ou bien « gestion de l'inventaire ».</p>
<p>Ces fonctionnalités ont besoin les unes des autres, je suis descendu jusqu'aux briques de bases, comme « afficher quelque chose à l'écran » ou bien « lire une touche du clavier ». Entre toutes ces fonctionnalités, j'ai créé des dépendances : afficher un personnage nécessaire de savoir afficher quelque chose à l'écran. L'animer nécessite de savoir l'afficher. Et ainsi de suite.</p>
<p>Mes dépendances ne sont pas complètes. J'ai celles qui concernent les premières tâches à effectuer, mais c'est tout. Inutile de travailler sur celles qui viendront bien plus tard, et ce pour une raison très simple : il est très probable qu'elles changent au fur et à mesure que j'avance dans le développement. Voire pour certaines, qu'elles disparaissent.</p>
<p><img alt="Liste terminée des fonctions du jeu." class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202302/20230223-ListeFonctionsMDLC.png"></p>
<h3>La qualité progressive</h3>
<p>Dernière étape, pour chaque fonctionnalités, séparer des niveaux de qualité. Par exemple, je sais que je vais un personnage avec un affichage fin, et animé. Mais je ne sais pas encore trop ce que ça donne d'un point de vue jeu, est-ce qu'il me faut un sprite de 8 pixels de large ou de 16 pixels de large ? J'ai même joué avec l'idée à un moment qu'il fasse 8 de large de côté, mais 16 de large de face.</p>
<p>Je sais que dessiner un sprite va me prendre du temps, surtout que ça n'est pas mon domaine. Je ne veux pas avoir à le refaire trop de fois. Donc dans les premiers niveaux de qualité, je note que je vais afficher des caractères prédéfinis de l'ordinateur.</p>
<p><img alt="Version de développement de MDLC" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202302/20220813-vg5000-aventure-01.png"></p>
<p>Je note ensuite que je pourrais tenter avec un personnage fixe. Puis enfin un personnage animé. Cela me donne trois étapes de qualité pour cette fonctionnalité. Et celles-ci ne seront pas forcément exécutées consécutivement. C'est important car le temps de la jam est limité et je fais ça sur mon temps libre, qui est très variable. Si la fin arrive avant qu'un niveau de qualité soit atteint, ce n'est pas grave, j'aurais tout de même quelque chose de moins joli que prévu, mais quelque chose quand même.</p>
<p>Je fais de même avec les fonctionnalités : je les classe par importance, mêlée de difficulté. Un personnage qui se déplace, c'est essentiel. Parmi les interactions avec les objets, j'en avais initialement prévus beaucoup plus que ce qui est dans le jeu à la fin. Quant à l'audio... je n'ai pas eu le temps et j'ai laissé de côté.</p>
<p>De même j'avais prévu quelques scènes graphiques d'illustration en « haute résolution ». C'est passé à la trappe.</p>
<h3>Sur le long terme</h3>
<p>À chaque fois que je termine une fonctionnalité, je reviens sur le document et j'ajuste. En voyant le jeu progresser, je comprends qu'il y a des choses que je voulais faire qui ne vont pas avec le reste. Ou parfois, je vois qu'il me manque un morceau, que j'avais oublié quelque chose.</p>
<p>Ce document va souvent être modifié. Je garde l'idée générale du jeu, les grandes lignes. Mais je me laisse aussi porter par ce qu'il devient. Ça m'évite de me bloquer sur quelque chose trop longtemps alors que ça ne fonctionne pas, que ce soit techniquement ou en game design.</p>
<p>Il ne faut pas hésiter à couper par manque de temps, ou parce qu'on a vu trop gros pour la machine. À modifier parce que ça ne convient plus. Ou parce qu'une nouvelle idée arrive. Ce dernier cas est à évaluer avec prudence ceci dit : il faut l'intégrer correctement à l'existant, et à l'état actuel du projet. Les idées arrivent toujours plus nombreuses et plus rapidement que leur temps de développement nécessaire.</p>
<h2>La programmation</h2>
<p>Niveau programmation, j'applique là aussi une démarche itérative « par petits pas ». En reprenant l'affiche, par exemple, je commence par m'adresser directement à l'EF9345, à travers une <a href="https://www.triceraprog.fr/la-maison-dans-la-colline-partie-4.html">liste d'affichage</a>, avec une position et un caractère fixes. Puis j'extrais la position pour qu'elle devienne variable, mais toujours localement. Puis je la passe par paramètre de la liste d'affichage. Et enfin je l'injecte depuis le programme principal, avant de répéter la même démarche pour le caractère affiché.</p>
<p>L'idée ici est de vérifier que le code fonctionne sur un cas simple, s'assurer qu'on a bien compris le problème. Dans le cas de l'EF9345, être certain qu'on a bien compris son fonctionnement par exemple. Puis petit à petit, on généralise, si nécessaire, avant d'extraire les données variables.</p>
<p>Cela permet de valider chaque étape individuellement et d'éviter les bugs dus à un développement avec beaucoup de changements différents. Cela permet aussi de s'arrêter en chemin. Soit pour passer à autre chose que l'on développe en parallèle car en lien. Ou tout simplement parce que c'est l'heure de manger, et qu'il est toujours préférable de laisser le programme dans un état fonctionnel sur lequel on peut reprendre plus tard.</p>
<p>Et enfin, parce que parfois, on s'aperçoit qu'il n'est pas nécessaire de généraliser plus avant. Ou alors pas tout de suite. Et peut-être jamais. La fonctionnalité arrive à un point satisfaisant.</p>
<h2>Conclusion</h2>
<p>S'il faut retenir une chose de cette méthodologie, c'est le concept d'avancée graduelle. Faire des « petits pas », monter petit à petit en qualité et savoir s'arrêter.</p>La Maison dans la colline, partie 42023-02-20T00:00:00+01:002023-02-20T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-02-20:/la-maison-dans-la-colline-partie-4.html<p>Dans ce quatrième article concernant le développement de « La maison dans la colline », je vais aborder quelques points de programmation. Deux points en particulier : la structure générale du programme, puis les listes d'affichage.</p>
<h3>Structure générale</h3>
<p>Un jeu vidéo, c'est un programme qui ne s'arrête pas. Enfin si... quand on a …</p><p>Dans ce quatrième article concernant le développement de « La maison dans la colline », je vais aborder quelques points de programmation. Deux points en particulier : la structure générale du programme, puis les listes d'affichage.</p>
<h3>Structure générale</h3>
<p>Un jeu vidéo, c'est un programme qui ne s'arrête pas. Enfin si... quand on a fini de jouer. Mais il s'oppose aux programmes en « batch » qui doivent résoudre une fonction à partir de données en entrée. Un jeu vidéo se situe donc dans la classe des applications qui font évoluer un état en fonction des entrées de l'utilisateur.</p>
<p>Ainsi, un tel programme peut se résumer à cette structure :</p>
<div class="highlight"><pre><span></span><code><span class="kt">int</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">while</span><span class="p">(</span><span class="n">running</span><span class="p">())</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">read_input</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">update_state</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="n">display_state</span><span class="p">();</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Autrement dit : tant que le logiciel tourne, on lit les entrées, on met à jour les états du jeu, on affiche l'état du jeu (on peut aussi diffuser le son, mais ce jeu n'en n'a pas) et on recommence.</p>
<p>Voilà la base.</p>
<p>Au deuxième étage, le jeu est constitué de « pages ». Il y a la page d'accueil, avec le titre, la page d'introduction, qui donne le texte de début, le jeu en lui-même, et le texte de fin. Comme le jeu est à tout moment dans une seule de ces pages, j'utilise pour les représenter une paire de fonctions. L'une est appelée lorsque l'on entre dans la page, et l'autre à chaque mise à jour, en boucle, tant que cette page est active.</p>
<h3>Fonction d'entrée</h3>
<p>La fonction d'entrée permet de changer le contexte du jeu, de mettre l'écran dans les bonnes conditions. Par exemple, voici la fonction d'entrée de la page de titre :</p>
<div class="highlight"><pre><span></span><code><span class="kt">void</span><span class="w"> </span><span class="nf">page_title_enter</span><span class="p">(</span><span class="n">PageContext</span><span class="o">*</span><span class="w"> </span><span class="n">context</span><span class="p">)</span><span class="w"> </span><span class="n">__z88dk_fastcall</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">initialize_40_long</span><span class="p">();</span><span class="w"> </span><span class="c1">// Initialisation du mode 40 colonne format long de l'EF9345</span>
<span class="w"> </span><span class="n">clear_40_long</span><span class="p">();</span><span class="w"> </span><span class="c1">// Effacement de l'écran dans ce mode</span>
<span class="w"> </span><span class="c1">// Affichage du titre</span>
<span class="w"> </span><span class="n">set_print_attributes</span><span class="p">(</span><span class="n">M40LONG_COLOR_FG_WHITE</span><span class="p">,</span><span class="w"> </span><span class="n">M40LONG_DOUBLE_HEIGHT</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="o">*</span><span class="w"> </span><span class="n">title</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">extract_string_from_id</span><span class="p">(</span><span class="n">TEXT</span><span class="p">(</span><span class="mi">0</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="n">print_at</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="w"> </span><span class="n">title</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="n">print_at</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">13</span><span class="p">,</span><span class="w"> </span><span class="n">title</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Affichage de l'auteur</span>
<span class="w"> </span><span class="n">set_print_attributes</span><span class="p">(</span><span class="n">M40LONG_COLOR_FG_WHITE</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="o">*</span><span class="w"> </span><span class="n">author</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">extract_string_from_id</span><span class="p">(</span><span class="n">TEXT</span><span class="p">(</span><span class="mi">71</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="n">print_at</span><span class="p">(</span><span class="mi">20</span><span class="p">,</span><span class="w"> </span><span class="mi">17</span><span class="p">,</span><span class="w"> </span><span class="n">author</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="c1">// Affichage du PRESS START</span>
<span class="w"> </span><span class="n">set_print_attributes</span><span class="p">(</span><span class="n">M40LONG_COLOR_FG_WHITE</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">M40LONG_FLASH</span><span class="p">,</span><span class="w"> </span><span class="mi">0</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">char</span><span class="o">*</span><span class="w"> </span><span class="n">press</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">extract_string_from_id</span><span class="p">(</span><span class="n">TEXT</span><span class="p">(</span><span class="mi">1</span><span class="p">));</span><span class="w"></span>
<span class="w"> </span><span class="n">print_at</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="mi">22</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">8</span><span class="p">,</span><span class="w"> </span><span class="n">press</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Quelques commentaires :</p>
<ul>
<li>le paramètre <code>context</code> n'est pas utilisé ici. Nous verrons dans la fonction suivante son utilité. À vrai dire, je ne l'ai jamais utilisé dans les fonctions d'entrée et il devrait probablement être enlevé.</li>
<li><code>__z88dk_fastcall</code> est une annotation pour la suite <code>z88dk</code> qui indique au compilateur que le pointeur passé en argument devra être passé dans <code>HL</code>, plutôt que par la pile. Nous verrons plus tard que c'est bien pratique lorsque l'on mélange C et assembleur.</li>
<li>les chaînes de caractères sont appelées via un système d'accès à des ressources. J'en parlerai probablement dans un autre article plus tard.</li>
</ul>
<h3>Fonction de mise à jour</h3>
<p>L'autre fonction qui décrit une page est celle de la mise à jour. Elle sera appelée en boucle tant que la page est active. Contrairement à ce que j'indiquais au tout début de l'article, il n'y a pas de séparation entre la mise à jour et l'affichage. Dans ce programme, la fonction de mise à jour s'occupe des deux étapes.</p>
<p>Voici par exemple la fonction de mise à jour pour l'écran de titre :</p>
<div class="highlight"><pre><span></span><code><span class="kt">void</span><span class="w"> </span><span class="nf">page_title_update</span><span class="p">(</span><span class="n">PageContext</span><span class="o">*</span><span class="w"> </span><span class="n">context</span><span class="p">)</span><span class="w"> </span><span class="n">__z88dk_fastcall</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">context</span><span class="o">-></span><span class="n">just_pressed_key</span><span class="w"> </span><span class="o">!=</span><span class="w"> </span><span class="mi">0</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">context</span><span class="o">-></span><span class="n">command</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">CHANGE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">context</span><span class="o">-></span><span class="n">command_param</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">INTRODUCTION_PAGE</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Commentaires :</p>
<ul>
<li>ici, le <code>context</code> est utilisé. On peut voir qu'il sert de communication avec l'état extérieur à la page.</li>
<li>en lecture, le <code>context</code> fourni une information sur une touche du clavier éventuellement appuyée.</li>
<li>en écriture, le <code>context</code> permet de donner une information à transmettre à l'extérieur. Ici, l'information est celle d'un changement de page.</li>
</ul>
<p>En effet, au-dessus des pages, il y a un petit superviseur, qui n'est en fait rien d'autre que la boucle principale du jeu. Celle-ci va s'occuper de peupler le <code>context</code> avec les informations systèmes, dont les touches du clavier, puis va vérifier si la page en cours a donné une commande. Il y a trois commandes possibles :</p>
<ul>
<li><code>CHANGE</code> : qui indique une page de destination, et qui provoquera donc un changement de page. Ici, lorsqu'une touche est appuyée, on passe à la page d'introduction du jeu.</li>
<li><code>RUN</code> : qui indique que la page actuelle doit continuer à être appelée, c'est la page active.</li>
<li><code>STOP</code> : qui demande l'arrêt du programme. C'est une commande que j'ai utilisée en début de développement pour certains tests, mais que j'ai arrêté d'utiliser. Le programme ne s'arrête pas.</li>
</ul>
<p>Voilà pour la structure générale du jeu. Passons maintenant aux listes d'affichage.</p>
<h2>Les listes d'affichage</h2>
<p>Comme je l'avais mentionné dans un article précédent, je voulais dans ce jeu avoir un affichage rapide avec lequel le scintillement de mise à jour était, au moins dans la majorité des cas, invisible. Et cela signifie une chose : il faut afficher vite, et au bon moment.</p>
<p>Si la mise à jour de l'écran se fait au fil de la mise à jour de l'état du jeu, il y a de bonnes chances qu'une mise à jour de l'affichage arrive au mauvais moment. De plus, l'EF9345 est pleinement efficace en début de traçage de l'écran, dans la zone de bord haute. Par la suite, il commence à être occupé avec la génération de l'image et a moins de temps à consacrer aux données qui arrivent depuis le Z80.</p>
<p>Il y a deux outils classiques pour cela :</p>
<ul>
<li>la synchronisation verticale, qui permet de savoir quand le processeur vidéo commence une nouvelle image,</li>
<li>les listes d'affichage (Display Lists en anglais), qui sont des listes de commandes préparées pour être exécutées le plus vite possible. Ou en tout cas, « assez vite »</li>
</ul>
<h3>Mise en place</h3>
<p>Par bonheur, le VG5000µ est conçu de manière à être mis au courant d'une synchronisation verticale. En effet, le signal de synchronisation, envoyé par l'EF9345, est branché sur l'interruption (IRQ) du Z80. Comme je l'avais abordé dans l'article sur les <a href="https://www.triceraprog.fr/vg5000m-les-hooks.html">hooks</a> de la ROM du VG5000µ, le système appel une emplacement en RAM avant d'effectuer son affichage. Il est possible remplacer l'instruction <code>RET</code> initialement positionné à cette adresse par un saut (<code>JMP xxxx</code>) vers l'adresse que l'on veut.</p>
<p>Et c'est une des premières opérations que fait le programme : une mise en place d'un routage vers l'exécution des listes d'affichage en cours. La routine dépile aussi l'adresse de retour sur la pile, afin de désactiver complètement l'affichage géré par la ROM.</p>
<p>On est donc en contrôle complet de l'affichage du VG5000µ. Une lourde responsabilité !</p>
<h3>Gestion de listes</h3>
<p>Il y a de nombreuses manières différentes d'implémenter des listes d'affichage, en fonction des besoins en vitesse balancés avec la place prise et probablement d'autres paramètres encore.</p>
<p>Dans ce programme, les listes d'affichage sont formées par une suite d'appels, au sens machine (<code>CALL</code>) vers de fonctions spécialisées. Ce sont des listes chaînées qui ont le format suivant :</p>
<ul>
<li>l'adresse du lien vers l'élément suivant, ou zéro pour la fin de liste,</li>
<li>l'adresse d'appel,</li>
<li>les paramètres de l'appel.</li>
</ul>
<p>Ajouter un élément à la liste d'affichage est donc ajouter à la liste une nouvelle adresse d'appel ainsi que des arguments, puis ajuste le lien de la chaîne. </p>
<p>Rejouer une liste consiste à parcourir la liste et appeler les fonctions spécialisées en fournissant les paramètres.</p>
<p>Tout cela est stocké dans l'espace de la RAM que la ROM utilise normalement pour traiter l'affichage. Il y a là plein de place inutilisée.</p>
<p>J'ai considéré un temps utiliser aussi l'espace des variables internes du BASIC, mais <code>z88dk</code> en utilise une partie, par exemple pour la lecture des touches, que je n'ai pas réécrite. Il est cependant possible d'utiliser cette partie avec un peu plus d'efforts.</p>
<h3>Un exemple</h3>
<p>Dans la fonction d'affichage de l'écran titre plus haut, il est cette ligne :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">set_print_attributes</span><span class="p">(</span><span class="n">M40LONG_COLOR_FG_WHITE</span><span class="p">,</span><span class="w"> </span><span class="n">M40LONG_DOUBLE_HEIGHT</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>Cette fonction <code>set_print_attributes</code> n'agit pas directement sur l'EF9345. Si on regarde son implémentation, on peut voir qu'elle se contente d'ajouter à la liste d'affichage un appel différé vers la routine spécialisée :</p>
<div class="highlight"><pre><span></span><code><span class="kt">void</span><span class="w"> </span><span class="nf">set_print_attributes</span><span class="p">(</span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">a</span><span class="p">,</span><span class="w"> </span><span class="kt">unsigned</span><span class="w"> </span><span class="kt">char</span><span class="w"> </span><span class="n">b</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">params</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">a</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">params</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">b</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="n">add_to_display_list</span><span class="p">(</span><span class="n">dl_set_attributes</span><span class="p">,</span><span class="w"> </span><span class="mi">2</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="kt">void</span><span class="o">*</span><span class="p">)</span><span class="w"> </span><span class="n">params</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Les trois paramètres indiquent :</p>
<ul>
<li>l'adresse de la routine spécialisée,</li>
<li>la taille des paramètres, en octets,</li>
<li>les paramètres, sous forme d'un buffer qui sera copié dans la liste d'affichage.</li>
</ul>
<p>Au prochain traçage de l'écran, la routine sera alors appelée. Dans ce cas précis, c'est une routine en assembleur.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">; Sets the A and B attributes to used for KRF</span>
<span class="w"> </span><span class="c1">; Parameters are A then B</span>
<span class="nl">_dl_set_attributes:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ef9345_addr</span><span class="w"> </span><span class="no">EF9345_SEL</span><span class="err">|</span><span class="no">REG_R3_CMDST</span><span class="w"> </span><span class="c1">; Loads A</span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="no">a</span><span class="p">,(</span><span class="no">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ef9345_data</span><span class="w"> </span><span class="no">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="no">wait_impl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ef9345_addr</span><span class="w"> </span><span class="no">EF9345_SEL</span><span class="err">|</span><span class="no">REG_R2_CMDST</span><span class="w"> </span><span class="c1">; Loads B</span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="no">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="no">a</span><span class="p">,(</span><span class="no">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ef9345_data</span><span class="w"> </span><span class="no">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="no">wait_impl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>Mais il est possible d'appeler une fonction C annotée avec l'attribut <code>__z88dk_fastcall</code> vu plus haut. Ainsi, la fonction C recevra le buffer de paramètre naturellement. Il n'existe qu'une seule fonction dans le programme qui utilise cette méthode, car j'ai progressivement réécrit les autres en assembleur. C'était cependant bien pratique pendant le développement.</p>
<p>Cette fonction s'occupe de l'affichage des pièces, et commence comme ceci :</p>
<div class="highlight"><pre><span></span><code><span class="kt">void</span><span class="w"> </span><span class="nf">dl_display_room</span><span class="p">(</span><span class="k">const</span><span class="w"> </span><span class="n">RoomNew</span><span class="o">**</span><span class="w"> </span><span class="n">p_room_param</span><span class="p">)</span><span class="w"> </span><span class="n">__z88dk_fastcall</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
</code></pre></div>
<h2>Conclusion</h2>
<p>Le système de page est simple et efficace dans le cadre de ce jeu. Et le système de liste d'affichage a bien joué son rôle : l'affichage du jeu est rapide et agréable. Il a fallu quelques ajustements sur matériel réel sur quelques synchronisation, car j'envoyais parfois les commandes trop vite, ce qui est accepté par les émulateurs, mais pas par un vrai VG5000µ.</p>
<p>Dans le prochain article, je compte parler de la méthodologie de développement que j'ai utilisé sur le jeu. À la prochaine !</p>Récréation 3D, Micral N2023-02-15T00:00:00+01:002023-02-15T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-02-15:/recreation-3d-micral-n.html<p>Je l'avais brièvement mentionné il y a <a href="https://www.triceraprog.fr/baisse-de-regime-en-apparence.html">presque un an</a> : j'ai eu la chance de pouvoir travailler avec l'association <a href="https://www.mo5.com">M05.COM</a> à l'analyse et restauration d'un exemplaire de « <strong>Micral N</strong> ».</p>
<p>Entre deux analyses, j'ai reproduis cet exemplaire en <strong>modèle 3D</strong>, que je présente ici dans un rendu assez simple.</p>
<p>J'y …</p><p>Je l'avais brièvement mentionné il y a <a href="https://www.triceraprog.fr/baisse-de-regime-en-apparence.html">presque un an</a> : j'ai eu la chance de pouvoir travailler avec l'association <a href="https://www.mo5.com">M05.COM</a> à l'analyse et restauration d'un exemplaire de « <strong>Micral N</strong> ».</p>
<p>Entre deux analyses, j'ai reproduis cet exemplaire en <strong>modèle 3D</strong>, que je présente ici dans un rendu assez simple.</p>
<p>J'y voyais <strong>deux intérêts</strong>. Le premier est que c'est pour moi une façon d'étudier l'<strong>aspect extérieur</strong> d'une machine. Dans le cas de celle-ci, est-ce que j'ai bien vu tous les détails ? Est-ce que j'ai bien vu toutes les LEDs et tous les interrupteurs. L'exercice de modélisation force à se pencher sur les détails.</p>
<p>En comparant les photos, on peut aussi voir les différences entre différents exemplaires. Par exemple, l'exemplaire de l'association a un <strong>interrupteur</strong> ajouté sur la droite du panneau de contrôle par rapport aux autres exemplaires dont les photos sont disponibles.</p>
<p>Le deuxième intérêt est que cette modélisation pourra servir pour l'<strong>émulateur</strong> « grand public » (toute proportion gardée), afin de manipuler le clone virtuel de cette machine.</p>
<p><img alt="Reproduction en image de synthèse de l'exemplaire Micral N de l'association MO5" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202302/remake-micral-n-v4-750.jpeg"></p>
<p>L'exemplaire original, lui, peut-être vu dans <a href="https://mo5.com/site/premier-apercu-du-micral-n-de-lassociation-mo5/">cet article de blog</a> sur le site de l'association.</p>La Maison dans la colline, partie 32023-01-26T00:00:00+01:002023-01-26T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-01-26:/la-maison-dans-la-colline-partie-3.html<p>Suite de la série sur le développement du jeu <a href="https://mokona78.itch.io/la-maison-dans-la-colline">La maison dans la colline</a> sur VG5000µ. Après, un exposé du contexte dans la <a href="https://www.triceraprog.fr/la-maison-dans-la-colline-partie-1.html">première partie</a>, puis un aperçu des outils utilisés dans la <a href="https://www.triceraprog.fr/la-maison-dans-la-colline-partie-1.html">seconde partie</a>, cet article aborde l'idée du jeu et de son évolution au fur et à mesure …</p><p>Suite de la série sur le développement du jeu <a href="https://mokona78.itch.io/la-maison-dans-la-colline">La maison dans la colline</a> sur VG5000µ. Après, un exposé du contexte dans la <a href="https://www.triceraprog.fr/la-maison-dans-la-colline-partie-1.html">première partie</a>, puis un aperçu des outils utilisés dans la <a href="https://www.triceraprog.fr/la-maison-dans-la-colline-partie-1.html">seconde partie</a>, cet article aborde l'idée du jeu et de son évolution au fur et à mesure du développement.</p>
<h3>La première idée</h3>
<p>LE VG5000µ est une machine que je commence à bien connaître, et, comme expliqué dans le premier article, j'étais en train d'approfondir les capacités du VDP lorsque ce petit défi a commencé. Je voulais me servir des mes nouvelles connaissances et je suis parti sur l'idée de faire un <strong>affichage « fin »</strong> et sans clignotement.</p>
<p>L'idée de faire un jeu d'<strong>aventure graphique</strong> me trottait dans la tête depuis quelques temps, et c'est un genre qui collait bien à mes objectifs. Peu de mouvements à l'écran, donc l'affichage pourrait être maîtrisé.</p>
<p>Initialement, le jeu se passe dans un manoir, c'est-à-dire un lieu avec de nombreuses pièces. Et puis, c'est le classique des jeux graphiques ou textuels, repris aussi par des standards comme « Alone in the Dark » en jeu vidéo, ou « Mansions of Madness » en jeu de plateau. Les jeux où le but est de sortir d'une <strong>maison fermée</strong> ne manquent pas.</p>
<p>Rapidement, je fais quelques croquis pour me fixer une idée de ce à quoi le jeu ressemblerait. Un peu de place pour l'affichage de la pièce courante vue de dessus, de la place pour un texte et un inventaire.</p>
<p><img alt="Premières notes manuscrites du jeu." class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202301/MDLC-Notes-220602.png"></p>
<h3>Creuser les idées</h3>
<p>Dans ce genre de jeux, il s'agit de <strong>résoudre des énigmes</strong> qui prennent la forme de trouver les <strong>bons objets</strong> pour les amener aux <strong>bons endroits</strong>. Il peut y avoir quelques énigmes plus complexes à résoudre, mais je veux m'arrêter là. Le temps va passer vite, et il faut rester sur du faisable, et sachant que ça sera déjà probablement trop.</p>
<p>Je lance quelques idées d'objets. Une torche électrique. Une boite d'allumettes. Ah, donc autant remplacer la torche pas une lanterne à allumer. Des livres. Des clés. C'est vague, mais cela me permet de définir quels types d'interactions le personnage du jeu peut avoir. Ah, et des portes aussi.</p>
<p>Au tout début, les interactions sont nombreuses : prendre, lire, utiliser, combiner, poser, ouvrir... Mais je sens que cela va faire beaucoup. Je voudrais aussi essayer que le jeu soit jouable avec la manette, à un seul bouton (ce ne sera pas le cas au final). Cette contrainte me fait réfléchir à <strong>réduire</strong> le nombre d'<strong>interactions</strong>, ou en tout cas de faire en sorte qu'elles soient automatiques : si je fais une action sur un livre, c'est pour le lire. Si je fais une action devant une porte en ayant une clé, c'est pour l'ouvrir. Et ainsi de suite. Mais pour le moment, je ne creuse pas plus avant. Je laisse cette idée mûrir.</p>
<p>Je veux aussi me détacher des jeux d'époque dont une partie de la difficulté est de comprendre comment manipuler le jeu. De ce côté-ci, je veux apporter de la <strong>modernité</strong> (toute relative) : en jouant, les objets sur lesquels ont peut <strong>agir</strong> doivent être <strong>facilement identifiables</strong>, et les actions possibles indiquées en fonction du contexte. Ça va probablement raboter la durée du jeu, mais cela sera beaucoup plus agréable.</p>
<p>Concernant la durée d'ailleurs, je ne vise pas quelque chose de très long. Les quelques personnes susceptibles de jouer à ce jeu ne cherchent probablement pas une expérience de douze heures. Ni même d'une heure. Trente minutes pour quelqu'un qui ne connaît pas la solution, ça semble bien. Même si je cherche à ce que le jeu soit agréable à jouer et un bon moment à passer, c'est avant tout une preuve de concept d'affichage sur VG5000µ. Et c'est un hobby à mes heures perdues.</p>
<p>Ci-dessous, une <strong>maquette</strong> préliminaire du jeu avec des essais de rendu. On y voit un essaie de personnage plus large. Les portes sont différentes aussi.</p>
<p><img alt="Maquette préliminaire du jeu." class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202301/20220710-MDLC-Maquette-Initiale-EcranDeJeu.png"></p>
<h3>Évolutions</h3>
<p>Toutes ces idées, je les notes sur un <strong>carnet</strong> (électronique), et je n'hésite pas à les annoter, les <strong>modifier</strong>, les corriger, en fonction des développements, du temps qui passe, des choses plus faciles à programmer que d'autres, des simplifications,... Cette espèce de document de design est un <strong>document vivant</strong>, que j'adapte au fur et à mesure.</p>
<p>Je commence à coller un <strong>scénario</strong> sur mes éléments de gameplay, afin d'expliquer les contraintes. Pourquoi est-ce que le personnage ne peut pas sortir de la maison une fois qu'il y est entrée. Que s'est-il passé dans les différentes pièces.</p>
<p>En retour, le scénario influe sur le design. Comment est-ce que j'agence les pièces, quels objets vont dans quelles pièces, qu'est-ce que je fais faire aux gens qui jouent ?</p>
<p>C'est allers et retours entre conception du jeu, de niveaux, du scénario et du programme nourrissent peu à peu ces différentes parties et les font avancer de concert.</p>
<p>Le <strong>manoir</strong> initial se transforme en une <strong>maison</strong> à moitié <strong>troglodyte</strong>, car c'est un moyen assez « simple » d'avoir un environnement d'intérieur de maison et de grotte dans un espace assez restreint. C'est un peu tiré par les cheveux mais bon, on est dans du jeu vidéo rétro. Je me trouve même assez sobre par rapport à certaines productions.</p>
<p>Petit à petit se construit aussi un <strong>double objectif</strong> afin de pouvoir terminer le jeu. Sortir de la maison, mais aussi pouvoir reprendre la voiture qui nous a amené sur les lieux.</p>
<p>J'ajoute aussi un peu de « lore » avec quelques objets qui ne sont pas nécessaires, mais qui font transparaître une <strong>histoire assez simple</strong>, mais je pense suffisante pour l'ambition toute relative de ce jeu. J'ai donc écrit un petit background sur les évènements passés de cette maison. Qui sait ? Peut-être que je compléterai l'histoire grâce à d'autres jeux dans le futur.</p>
<h3>Léger</h3>
<p>La constante, c'est de <strong>rester léger</strong>. Comme je l'ai évoqué, c'est un petit défi de programmation hobbyiste. Il ne faut surtout pas hésiter à couper, et donc prévoir des choses de manières à ce qu'elles puissent facilement être enlevées en modifiées.</p>
<p>Je suis assez satisfait du résultat, même si j'y vois plein de petites erreurs, ou maladresses, en y jouant.</p>
<p><img alt="Diagramme montrant les étapes du jeu." class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202301/MDLC-flow.png"></p>Mattel Aquarius, scan des touches en assembleur2023-01-19T00:00:00+01:002023-01-19T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-01-19:/mattel-aquarius-scan-des-touches-en-assembleur.html<p>Le scan du clavier, sur <strong>Mattel Aquarius</strong>, à lieu dans la ROM à l'adresse <code>$1e80</code>. Cette fonction traite les minuscules, les majuscules mais aussi les raccourcis BASIC, en injectant au fur et à mesure les touches nécessaires comme si elles avaient été tapées au clavier.</p>
<p>C'est <strong>beaucoup trop</strong> pour un …</p><p>Le scan du clavier, sur <strong>Mattel Aquarius</strong>, à lieu dans la ROM à l'adresse <code>$1e80</code>. Cette fonction traite les minuscules, les majuscules mais aussi les raccourcis BASIC, en injectant au fur et à mesure les touches nécessaires comme si elles avaient été tapées au clavier.</p>
<p>C'est <strong>beaucoup trop</strong> pour un scan de clavier dans un jeu, et peut même poser quelques soucis. Mais c'est une <strong>bonne base</strong> pour écrire une routine de lecture de clavier, car la lecture des valeurs des touches n'est pas forcément très simples.</p>
<p>Ce que nous apprends la lecture de la routine en ROM est qu'il semble falloir <strong>attendre une stabilité</strong> dans les valeurs avant d'accepter la touche. En effet, la routine fait plusieurs lectures et ne considère la touche appuyée que si cette lecture est stable.</p>
<p>Voici une version de la routine, où j'ai enlever ce qui était traitement de raccourcis BASIC, ainsi que le traitement des touches de modifications. Ainsi, la traduction ne se fait qu'avec les touches minuscules.</p>
<p>Attention, je n'ai pas encore testé cette routine sur du matériel réel !</p>
<h2>La routine</h2>
<p>Dans cette version, le <strong>décodage</strong> de la touche est faite par rapport aux minuscules. En changeant vers la fin la valeur de HL de <code>lower_key_table-1</code> vers <code>upper_key_table-1</code>, il est possible d'obtenir un décodage avec des majuscules.</p>
<p>Il est aussi possible de ne traiter que des codes de touches, sans traductions. Dans ce cas là, il suffit de remplacer les trois instructions après <code>accept_key</code> par un simple <code>LD A, E</code>.</p>
<p>Les <strong>deux emplacements</strong> en RAM, qui commencent ce code source, doivent être en RAM et être <strong>adaptés</strong> à votre programme.</p>
<div class="highlight"><pre><span></span><code><span class="nl">latest_keycode_pressed:</span><span class="w"> </span><span class="nf">EQU</span><span class="w"> </span><span class="mi">0380</span><span class="no">eh</span><span class="w"></span>
<span class="nl">keyword_delay_value:</span><span class="w"> </span><span class="nf">EQU</span><span class="w"> </span><span class="mi">0380</span><span class="no">fh</span><span class="w"></span>
<span class="nl">lower_key_table:</span><span class="w"> </span><span class="nf">EQU</span><span class="w"> </span><span class="mi">01</span><span class="no">f38h</span><span class="w"></span>
<span class="nl">upper_key_table:</span><span class="w"> </span><span class="nf">EQU</span><span class="w"> </span><span class="mi">01</span><span class="no">f66h</span><span class="w"></span>
<span class="nl">check_key:</span><span class="w"></span>
<span class="w"> </span><span class="nf">EXX</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">BC</span><span class="p">,</span><span class="mi">0xff</span><span class="w"></span>
<span class="w"> </span><span class="nf">IN</span><span class="w"> </span><span class="no">A</span><span class="p">,(</span><span class="no">C</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">CPL</span><span class="w"></span>
<span class="w"> </span><span class="nf">AND</span><span class="w"> </span><span class="mi">0x3f</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">HL</span><span class="p">,</span><span class="no">latest_keycode_pressed</span><span class="w"></span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">Z</span><span class="p">,</span><span class="no">no_key_scanned</span><span class="w"> </span><span class="c1">; ?No row were selected, quick skip?</span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">B</span><span class="p">,</span><span class="mi">0x7f</span><span class="w"> </span><span class="c1">; Input row 7</span>
<span class="w"> </span><span class="nf">IN</span><span class="w"> </span><span class="no">A</span><span class="p">,(</span><span class="no">C</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">CPL</span><span class="w"></span>
<span class="w"> </span><span class="nf">AND</span><span class="w"> </span><span class="mi">0xf</span><span class="w"> </span><span class="c1">; Only 4 keys in lower bits</span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">NZ</span><span class="p">,</span><span class="no">decode_key_scan</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">B</span><span class="p">,</span><span class="mi">0xbf</span><span class="w"> </span><span class="c1">; Input rows 6 to 0</span>
<span class="nl">loop_input_rows:</span><span class="w"></span>
<span class="w"> </span><span class="nf">IN</span><span class="w"> </span><span class="no">A</span><span class="p">,(</span><span class="no">C</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">CPL</span><span class="w"></span>
<span class="w"> </span><span class="nf">AND</span><span class="w"> </span><span class="mi">0x3f</span><span class="w"> </span><span class="c1">; 5 keys in lower bits</span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">NZ</span><span class="p">,</span><span class="no">decode_key_scan</span><span class="w"></span>
<span class="w"> </span><span class="nf">RRC</span><span class="w"> </span><span class="no">B</span><span class="w"></span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">C</span><span class="p">,</span><span class="no">loop_input_rows</span><span class="w"></span>
<span class="nl">no_key_scanned:</span><span class="w"></span>
<span class="w"> </span><span class="nf">INC</span><span class="w"> </span><span class="no">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">A</span><span class="p">,</span><span class="mi">0x46</span><span class="w"></span>
<span class="w"> </span><span class="nf">CP</span><span class="w"> </span><span class="p">(</span><span class="no">HL</span><span class="p">)</span><span class="w"> </span><span class="c1">; Compare with delay</span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">C</span><span class="p">,</span><span class="no">return_no_key_value</span><span class="w"></span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">Z</span><span class="p">,</span><span class="no">reset_latest_key</span><span class="w"></span>
<span class="w"> </span><span class="nf">INC</span><span class="w"> </span><span class="p">(</span><span class="no">HL</span><span class="p">)</span><span class="w"> </span><span class="c1">; Increment delay</span>
<span class="nl">return_no_key_value:</span><span class="w"></span>
<span class="w"> </span><span class="nf">XOR</span><span class="w"> </span><span class="no">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">EXX</span><span class="w"></span>
<span class="w"> </span><span class="nf">RET</span><span class="w"></span>
<span class="nl">reset_latest_key:</span><span class="w"></span>
<span class="w"> </span><span class="nf">INC</span><span class="w"> </span><span class="p">(</span><span class="no">HL</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">DEC</span><span class="w"> </span><span class="no">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="p">(</span><span class="no">HL</span><span class="p">),</span><span class="mi">0x0</span><span class="w"></span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">return_no_key_value</span><span class="w"></span>
<span class="nl">decode_key_scan:</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">DE</span><span class="p">,</span><span class="mi">0x0</span><span class="w"></span>
<span class="nl">count_row:</span><span class="w"></span>
<span class="w"> </span><span class="nf">INC</span><span class="w"> </span><span class="no">E</span><span class="w"></span>
<span class="w"> </span><span class="nf">RRA</span><span class="w"></span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">NC</span><span class="p">,</span><span class="no">count_row</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">A</span><span class="p">,</span><span class="no">E</span><span class="w"> </span><span class="c1">; E is the active row</span>
<span class="nl">count_add_column:</span><span class="w"></span>
<span class="w"> </span><span class="nf">RR</span><span class="w"> </span><span class="no">B</span><span class="w"></span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">NC</span><span class="p">,</span><span class="no">keycode_found</span><span class="w"></span>
<span class="w"> </span><span class="nf">ADD</span><span class="w"> </span><span class="no">A</span><span class="p">,</span><span class="mi">0x6</span><span class="w"> </span><span class="c1">; Add 6 for each column</span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">count_add_column</span><span class="w"></span>
<span class="nl">keycode_found:</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">E</span><span class="p">,</span><span class="no">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">CP</span><span class="w"> </span><span class="p">(</span><span class="no">HL</span><span class="p">)</span><span class="w"> </span><span class="c1">; HL = 0380eh</span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="p">(</span><span class="no">HL</span><span class="p">),</span><span class="no">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">INC</span><span class="w"> </span><span class="no">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">NZ</span><span class="p">,</span><span class="no">new_key_delay</span><span class="w"> </span><span class="c1">; That's a new key, we need a bit of selay</span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">A</span><span class="p">,</span><span class="mi">0x4</span><span class="w"></span>
<span class="w"> </span><span class="nf">CP</span><span class="w"> </span><span class="p">(</span><span class="no">HL</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">C</span><span class="p">,</span><span class="no">commit_key_delay_continued</span><span class="w"></span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">Z</span><span class="p">,</span><span class="no">accept_key</span><span class="w"> </span><span class="c1">; Wait if over, accept key!</span>
<span class="w"> </span><span class="nf">INC</span><span class="w"> </span><span class="p">(</span><span class="no">HL</span><span class="p">)</span><span class="w"> </span><span class="c1">; Increment delay from 4 to 6 iteratively</span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">return_no_key_value</span><span class="w"></span>
<span class="nl">commit_key_delay_continued:</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="p">(</span><span class="no">HL</span><span class="p">),</span><span class="mi">0x6</span><span class="w"> </span><span class="c1">; Set the delay for the second pass</span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">return_no_key_value</span><span class="w"></span>
<span class="nl">new_key_delay:</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="p">(</span><span class="no">HL</span><span class="p">),</span><span class="mi">0x0</span><span class="w"></span>
<span class="w"> </span><span class="nf">JR</span><span class="w"> </span><span class="no">return_no_key_value</span><span class="w"></span>
<span class="nl">accept_key:</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">IX</span><span class="p">,</span><span class="no">lower_key_table-1</span><span class="w"></span>
<span class="w"> </span><span class="nf">ADD</span><span class="w"> </span><span class="no">IX</span><span class="p">,</span><span class="no">DE</span><span class="w"></span>
<span class="w"> </span><span class="nf">LD</span><span class="w"> </span><span class="no">A</span><span class="p">,(</span><span class="no">IX</span><span class="err">+</span><span class="mi">0x0</span><span class="p">)</span><span class="w"> </span><span class="c1">; Read decoded Key value</span>
<span class="nf">return_key_value</span><span class="w"></span>
<span class="w"> </span><span class="nf">EXX</span><span class="w"></span>
<span class="w"> </span><span class="nf">RET</span><span class="w"></span>
</code></pre></div>Mattel Aquarius, entrées des commandes BASIC2023-01-18T00:00:00+01:002023-01-18T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-01-18:/mattel-aquarius-entrees-des-commandes-basic.html<p>Commençant à étudier le <strong>Mattel Aquarius</strong> afin de participer à la nouvelle session de « Retro Programmers United for Obscure Systems », et devant le manque de documentation, j'ai regardé ce qu'il y avait dans la ROM. Et c'est un BASIC Microsoft qui y est implémenté, ce qui est bien pratique puisque …</p><p>Commençant à étudier le <strong>Mattel Aquarius</strong> afin de participer à la nouvelle session de « Retro Programmers United for Obscure Systems », et devant le manque de documentation, j'ai regardé ce qu'il y avait dans la ROM. Et c'est un BASIC Microsoft qui y est implémenté, ce qui est bien pratique puisque c'est un BASIC que j'ai bien étudié à travers le VG5000µ.</p>
<p>J'ai donc ressorti ma <strong>trousse à outils</strong> et voici la liste des <strong>points d'entrées</strong> des commandes et fonctions du BASIC.</p>
<p>La première colonne est le <strong>token</strong> BASIC, la seconde l'<strong>adresse</strong> du point d'entrée en ROM et la troisième <strong>le nom</strong>.</p>
<h2>Les commandes</h2>
<div class="highlight"><pre><span></span><code> 128 $0c21 end
129 $05bc for
130 $0d13 next
131 $071c data
132 $0893 input
133 $10cc dim
134 $08be read
135 $0731 let
136 $06dc goto
137 $06be run
138 $079c if
139 $0c05 restore
140 $06cb gosub
141 $06f8 return
142 $071e rem
143 $0c1f stop
144 $0780 on
145 $07b5 lprint
146 $1b15 copy
147 $0b3b def
148 $0b6d poke
149 $07bc print
150 $0c4b cont
151 $056c list
152 $0567 llist
153 $0ccd clear
154 $1c2c cload
155 $1c08 csave
156 $1a4f pset
157 $1a4c preset
158 $1ad6 sound
159 $0bbd new
</code></pre></div>
<h2>Les fonctions</h2>
<div class="highlight"><pre><span></span><code> 178 $14f5 sgn
179 $15b1 int
180 $1509 abs
181 $3803 usr
182 $10a8 fre
183 $0b2e lpos
184 $0b33 pos
185 $1775 sqr
186 $1866 rnd
187 $1385 log
188 $17cd exp
189 $18d7 cos
190 $18dd sin
191 $1970 tan
192 $1985 atn
193 $0b63 peek
194 $0ff3 len
195 $0e29 str
196 $1084 val
197 $1002 asc
198 $1013 chr
199 $1021 left
200 $1050 right
201 $1059 mid
202 $4ec5 point
</code></pre></div>
<p>Cela peut aider à donner des points d'entrée pour comprendre comment fonctionne la machine.</p>La Maison dans la colline, partie 22023-01-06T00:00:00+01:002023-01-06T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-01-06:/la-maison-dans-la-colline-partie-2.html<p>Suite de la série sur le développement du jeu <a href="https://mokona78.itch.io/la-maison-dans-la-colline">La maison dans la colline</a> sur VG5000µ. Après, la <a href="https://www.triceraprog.fr/la-maison-dans-la-colline-partie-1.html">première partie</a>, qui donnait le contexte de création, voici donc la deuxième partie, où j'aborde les outils de développement utilisés.</p>
<h3>Les bases</h3>
<p>Lorsque je développe un logiciel, je veux pouvoir me consacrer …</p><p>Suite de la série sur le développement du jeu <a href="https://mokona78.itch.io/la-maison-dans-la-colline">La maison dans la colline</a> sur VG5000µ. Après, la <a href="https://www.triceraprog.fr/la-maison-dans-la-colline-partie-1.html">première partie</a>, qui donnait le contexte de création, voici donc la deuxième partie, où j'aborde les outils de développement utilisés.</p>
<h3>Les bases</h3>
<p>Lorsque je développe un logiciel, je veux pouvoir me consacrer sur un temps contiguë maximum au cœur du projet. Même, et peut-être surtout, lorsque ce développement est un <strong>hobby</strong>, et que j'ai peu d'heures à y consacrer, ici et là en fonction de mon temps libre.</p>
<p>Ainsi, une fois le projet en développement, lorsque j'ai une petite demi-heure par-ci ou une grosse heure par là, je veux pouvoir m'installer et me consacrer à la valeur de ce projet. À son <strong>développement concret</strong>.</p>
<p>Pour moi, cela signifie que tout ce que je peux automatiser et qui me permettra de profiter de ces temps là au maximum doit l'être fait en amont. Si je dois cliquer sur des boutons, faire des manipulations, lancer ou configurer des logiciels à chaque fois que je fais un essai, voire même à chaque fois que je débute une session, c'est un <strong>temps précieux</strong> qui est inutilisé. Chaque manipulation est aussi une occasion de faire une <strong>erreur</strong>.</p>
<p>Dans le passé sur ce site, j'ai déjà présenté <a href="https://www.triceraprog.fr/injecter-un-programme-dans-un-emulateur-vg5000m-partie-ii.html">quelques outils</a> destinés à me faciliter la tâche. Et dès les essais sur l'EF9345 dont je parlais dans l'article précédent, j'ai remis en place un script qui permet de lancer l'émulateur automatiquement, configuré avec lancement du chargement.</p>
<p>Comme je me doutais que le programme allait gagner en taille, j'ai ajouté au script une manière de passer l'émulateur en vitesse accélérée le temps du chargement.</p>
<p>Ainsi, dès que je termine une compilation, je n'ai que <strong>quelques secondes</strong> à attendre avant de retrouver le jeu, prêt à être testé, dans l'émulateur.</p>
<h3>Le framework</h3>
<p>Afin de pouvoir itérer sur le jeu, particulièrement sur son gameplay, j'ai choisi de développer dans un mélange de <strong>C</strong> et d'<strong>assembleur</strong>. Le C permet des modifications simples de morceaux de code sans trop se poser de questions bas niveau. L'assembleur permet d’accélérer certaines parties une fois qu'elles sont fixes, ainsi qu'un accès rapide au matériel.</p>
<p>Car en effet, si j'ai choisi de faire un jeu d'aventure graphique, je tiens à deux choses au niveau de l'<strong>affichage</strong> :</p>
<ul>
<li>que l'affichage d'une pièce soit <strong>très rapide</strong> (si ce n'est immédiat),</li>
<li>que les mouvements à l'écran ne provoquent <strong>aucun clignotement</strong></li>
</ul>
<p>Difficile voire impossible à faire cela en BASIC. Plus facile à faire en C, mais c'est l'assembleur qui me permettra de vraiment réaliser ces objectifs.</p>
<p>De même, je voulais initialement que le jeu tienne sur une machine sans extension mémoire. Malheureusement, je ne réaliserai pas cet objectif. De peu...</p>
<h4>z88dk</h4>
<p>Quelques temps (années ?) auparavant, j'avais croisé le chemin de <a href="https://z88dk.org/site/">z88dk</a>, un framework de programmation en C dédié aux machines <strong>Z80</strong>. Cela me semblait être une bonne occasion pour l'essayer, avoir l'espoir qu'il vienne avec des fonctionnalités intéressantes qui me feraient gagner du temps.</p>
<p>Après tout, il y a des outils de création de programme assemblés dans une suite complète, une <strong>bibliothèque C</strong> pour Z80, un <strong>support VG5000µ</strong> et un fichier <code>crt.s</code> déjà configuré et configurable. Ça, se sont les avantages.</p>
<p>Après usage pendant toute la durée du projet, voici les inconvénients :</p>
<ul>
<li>le programme principal qui appelle les autres pour la compilation n'a <strong>pas une syntaxe d'option standard</strong>, et cela me posera des problèmes avec <code>cmake</code>,</li>
<li>le <strong>compilateur</strong> par défaut n'est <strong>pas le meilleur</strong>, je m'en rendrai compte beaucoup plus tard, vers la fin du projet. Il est possible d'utiliser un compilateur alternatif, qui optimise visiblement mieux. Malheureusement, les deux ne se comportent pas tout à fait de la même façon, et j'ai préférer ne pas changer en chemin. On verra pour un futur projet peut-être,</li>
<li>le support VG5000µ est axé sur une utilisation en <strong>mode texte</strong>, et passe par le système implémenté par la <strong>ROM</strong>, sans accès direct à l'EF9345. Ce n'est pas cohérent avec ce que je veux faire, et je n'utiliserai finalement aucune fonction spécifiquement dédiée au VG5000µ.</li>
</ul>
<p>Au final, je ne suis pas entièrement convaincu par z88dk, qui à l'air de prendre une orientation plutôt CP/M et machined un peu plus puissantes qu'un VG5000µ.</p>
<h3>Gestion de la construction</h3>
<p>Pour le gestionnaire de construction de version, je suis parti sur <code>cmake</code>. C'est très probablement <strong>surdimensionné</strong>, mais je connais son utilisation classique. Ici, je voulais pousser plus loin et aborder sa configuration de chaîne de compilation « exotique ».</p>
<p>J'ai beaucoup appris sur <code>cmake</code> au passage ; ma solution finale n'est pas entièrement solide, je confirme à nouveau que Stack Overflow n'est d'aucune aide dès que vous avez un soucis qui n'est pas basique, que la documentation de <code>cmake</code> est toujours peu aidante... et qu'un <strong>Makefile</strong> tout simple aurait été probablement tout aussi efficace. Mais je m'emmêle toujours les pinceaux avec un Makefile dès que je quitte les règles de bases.</p>
<p><code>cmake</code> est probablement trop automatique sur certains points, et il n'est pas toujours facile de lui faire comprendre que la buildchain utilisé ne se comporte pas tout à fait comme ce à quoi il s'attend.</p>
<p>Enfin maintenant c'est fait, et même si je dois corriger certaines petites choses au niveau des dépendances entre les <code>.c</code>, les <code>.h</code> et les <code>.asm</code>, ça marche globalement bien.</p>
<h3>Éditeur de code</h3>
<p><code>cmake</code> était surdimensionné ? Que dire de l'éditeur de code que j'ai utilisé. <code>CLion</code>... <code>vim</code> ou <code>visual studio code</code> auraient suffit. Et il m'est arrivé d'utiliser <code>vim</code> pendant le développement pour quelques modifs rapides. Mais là encore, je connais bien <code>CLion</code>, ses outils, sa configuration, et l'habitude, ça compte.</p>
<p>De toute façon, mes outils d'automatisation ne sont dépendants ni de <code>cmake</code>, ni de <code>clion</code>, ce sont donc des choix indépendants.</p>
<h3>Paré au départ !</h3>
<p>Ou presque. J'ai cité les outils de développement principaux. Mais je parlais en introduction d'automatiser la mise en place de l'environnement de développement. Pour cela, j'utilise le terminal <code>kitty</code> associé à un script. Lorsque je lance <strong>ce script</strong>, le terminal se met en place avec les bon terminaux ouverts dans les bons répertoire, et des outils textes déjà lancés (<code>ranger</code> pour la gestion de fichier, <code>lazygit</code> pour la gestion de version, <code>nb</code> pour la prise de notes, le script d'automatisation à l'écoute).</p>
<p>Ainsi, démarrer une session depuis un ordinateur fraîchement allumé me prend <strong>deux clics</strong> pour avoir l'intégralité de l'environnement en place, et j'ai tout ce qu'il me faut pour la session. Cela aurait été un seul si je n'avais pas choisi <code>Clion</code>.</p>
<p>Alors... clic, clic... et la suite au prochain épisde.</p>
<p><img alt="Capture montrant l'explorateur textuel Ranger dans le répertoire du jeu." class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202301/20230106-Ranger-MDLC.png"></p>Plouf ... in space2023-01-03T00:00:00+01:002023-01-03T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2023-01-03:/plouf-in-space.html<p>En attendant de revenir sur le déroulé du développement de « <a href="https://www.triceraprog.fr/la-maison-dans-la-colline-partie-1.html">La maison dans la colline</a> », voici un article beaucoup plus court pour présenter ma contribution à la session la plus récente de « <a href="https://itch.io/jam/retro-programmers-united-for-obscure-systems-exelvision">Retro Programmers United for Obscure Systems</a> », consacrée à <strong>Exelvision</strong>.</p>
<h3>La découverte de la machine</h3>
<p>Exelvision, une entreprise française …</p><p>En attendant de revenir sur le déroulé du développement de « <a href="https://www.triceraprog.fr/la-maison-dans-la-colline-partie-1.html">La maison dans la colline</a> », voici un article beaucoup plus court pour présenter ma contribution à la session la plus récente de « <a href="https://itch.io/jam/retro-programmers-united-for-obscure-systems-exelvision">Retro Programmers United for Obscure Systems</a> », consacrée à <strong>Exelvision</strong>.</p>
<h3>La découverte de la machine</h3>
<p>Exelvision, une entreprise française, a produit au milieu des années 80 une série de machines assez originales. Ce sont des machines de type familial, mais avec un look un peu pro, un choix audacieux mais probablement désastreux d'une connectique sans fil pour le clavier et les joysticks, de la synthèse vocale, et des capacités graphiques plutôt correctes.</p>
<p>Pour ce challenge, je me suis orienté vers la première de ces machines, l'<strong>EXL 100</strong>, dont vous pouvez trouver une présentation détaillée sur <a href="http://www.ti99.com/exelvision/website/index.php?page=exl100">ce site</a>.</p>
<p>C'est une machine que je ne connaissais pas vraiment. Ou tout du moins, je ne m'étais pas penché sur ces caractéristiques, son architecture ni même sur les périphériques disponibles. Et la <strong>surprise</strong> fut là.</p>
<p>Côté processeur principal, je savais qu'il faisait dans l'original, un <strong>TMS 7020</strong>. Côté RAM, tout est côté vidéo, associée à un TMS 3556, non accessible directement par le TMS 7020. Ça promet. Toute cette place mémoire, <strong>32 ko</strong> tout de même, hors de porté du bus principal. Et en RAM principale ? ... <strong>2 ko</strong>. Deux tout petits kilo octets, déjà très occupés par le système.</p>
<p>Je me tourne vers le <strong>BASIC</strong> pour découvrir la machine. Celui-ci est disponible sur cartouche. Et ses données sont bien en VRAM, le seul endroit où il y a de la place. Les performances sont en adéquation. C'est lent. Pas très lent, il y a pire, mais lent tout de même.</p>
<p>La machine peut bien supporter des extensions mémoires, mais l'architecture laisse peu de place au doute : c'est une machine qui est faite pour lancer ses programmes sur <strong>cartouches</strong> en priorité, à moins de lui adjoindre l'extension <strong>disquettes</strong>, qui fait entrer la machine dans une toute autre dimension.</p>
<p>Ces matériels ne sont pas très courant, et semblent complexifier la machine, je décide donc de cibler l'EXL100 de base, sans extension.</p>
<h3>Les deux premières pistes</h3>
<p>La première piste que j'explore est <strong>technique</strong> : celle de proposer le logiciel sur cartouche. L'avantage me semble être une rapidité d'exécution. L'inconvénient est que je n'ai aucune idée de comment fonctionne la ROM de la machine, de ce qu'il faut pour la faire reconnaître. Ça se trouve bien entendu, mais je ne connais pas non plus l'assembleur TMS 7020. Je laisse l'idée rapidement de côté, il y a trop d'inconnues pour moi sur cette machine.</p>
<p>La seconde piste est <strong>logicielle</strong> : écrire une machine virtuelle qui tiendrait dans la petite RAM de la machine pour exécuter un byte code présent dans la VRAM. L'idée me plaît bien et j'y réfléchi un temps tout en consultant les documentations de la machine. Mais encore un fois, je m'aperçois des particularités de la machine. Je crains le hors sujet.</p>
<p>Au final, faisons simple : un petit jeu en BASIC pour s'entraîner. Et si j'ai le temps, loger un peu d'assembleur pour accélérer certaines parties.</p>
<h3>Plouf</h3>
<p>Puisque j'ai besoin de creuser ma connaissance de la machine, je ne peux pas me lancer dans quelque chose de compliqué. Je vais donc chercher dans <strong>les classiques</strong>. Après quelques hésitations, je choisi la <strong>bataille navale</strong>. C'est un jeu qui peut se représenter avec des graphismes détaillé tout comme en caractères, c'est assez simple à programmer. Petit inconvénient, il me faudra une IA pour un jeu à une seule personne.</p>
<p>Je cherche un <strong>petit twist</strong> tout de même pour ne pas aller dans le trop classique et je me dis que faire bouger les bateaux entre deux tours pourrait être sympa sans trop complexifier le jeu. Là encore, c'est débrayable, si je n'ai pas le temps, je ferai sans.</p>
<p>Et donc c'est parti.</p>
<h3>Le BASIC</h3>
<p>La plupart des <strong>BASIC</strong>s de l'époque se ressemblent, souvent parce qu'ils sont soient des adaptations du BASIC Microsoft, soit parce qu'ils ont été créés avec une syntaxe similaire, ou en tout cas en suivant cette branche d'évolution du BASIC.</p>
<p>Sans être complètement différent, le BASIC Exelevision a des <strong>particularités</strong>. Entre autre, il a un concept de <strong>routines</strong> appelables sans tenir compte de son numéro de ligne. Pratique à première vue. De plus, dans ces routines, les <strong>variables sont locales</strong>. C'est plutôt attrayant.</p>
<p>Rapidement, les routines semblent être une fausse bonne idée. En premier lieu, même si le numéro de ligne n'a pas d'importance, elles doivent absolument se trouver à la fin du programme, sous peine d'erreur (et les erreurs sont sous forme de nombres seulement, mieux vaut avoir le manuel sous les yeux). Et puis la localité des variables est tout ou rien. Et on ne peut pas passer de tableaux à ces routines.</p>
<p>Au final, je ne garde qu'une paire de fonctionnalités sous forme de routines. Le reste sera fait avec ce bon vieux <strong>GOSUB</strong> et des numéros de ligne.</p>
<p>Le BASIC permet aussi un traitement d'exception, avec de la récupération d'erreurs. C'est plutôt pas mal... mais je ne penserai à m'en servir que tard dans le développement. Ce BASIC semble cependant un peu <strong>capricieux</strong>, avec des commandes qui parfois ne veulent pas être sur la même ligne et parfois oui... je n'ai pas vraiment compris les raisons.</p>
<p>Le démarrage d'un programme après un RUN est assez long, et de plus en plus long avec l'avancée du projet. J'acquière la certitude que RUN effectue une <strong>vérification</strong> du programme. Peut-être pas une compilation, quoi que je n'en sais rien, mais il est capable de détecter avant démarrage les erreurs de numéros de lignes dans les GOTO/GOSUB, et les mauvais appariement de boucles FOR/NEXT.</p>
<p>C'est pratique pour le premier cas, parfois un peu obscure pour le second, car la ligne en erreur n'est pas toujours celle où se trouve vraiment l'erreur.</p>
<p>Cependant, c'est une <strong>fonctionnalité notable</strong>, car peu présente, voire pas, sur d'autres BASIC de cette époque.</p>
<h3>Le jeu</h3>
<p>Je n'ai pas grand chose à dire sur le jeu lui-même. La principale difficulté est d'arriver à caser deux grilles à l'écran. L'algorithme de déplacement et collision des bateaux me pose aussi quelques soucis, et debugguer n'est pas des plus plaisants, même sur émulateur. Cette machine est peu utilisée, peu connue, et le manque d'outils se fait cruellement sentir.</p>
<p>Mais je crois bien que ça sera le cas à chaque fois pour ces Game Jams, étant donné que le but est d'aller sur des machines sur lesquels il y a peu de développement.</p>
<p>Mon principal <strong>problème</strong> est la <strong>vitesse</strong>. Le jeu est affreusement lent. Vraiment. Le fait que les bateaux bougent et collisionnent pour faire demi tour demande de longs calculs. Un temps, j'imagine couper les collisions et dire que après tout, ce sont des sous-marins et qu'ils peuvent passer l'un sur l'autre.</p>
<p>Cependant, cela complexifie la compréhension du jeu, ainsi que la gestion des tirs, qui peuvent toucher plusieurs sous-marins en même temps. Je décide donc qu'il est temps de s'essayer à un peu d'assembleur.</p>
<h3>Optimisations assembleur</h3>
<p>Le TMS 7020, de la série <strong>TMS 7000</strong>, est un processeur connu, même si pas aussi utilisé dans les ordinateurs personnes que le Z80 ou le 6809. Il est donc supporté par des assembleurs et en particulier par <a href="http://john.ccac.rwth-aachen.de:8000/as/">celui-ci</a>, que je connais déjà. Parfait.</p>
<p>J'avais déjà commencé à lire des ressources sur la programmation de cette machine et ses 128 registres. <strong>Quel luxe</strong>... ou presque, car c'est dans cette mémoire interne que se loge la pile aussi. Ça reste quand même bien spacieux.</p>
<p>Et au final, c'est un processeur plutôt agréable. Très simple à programmer. La plus grosse difficulté étant plutôt, à nouveau, de comprendre l'environnement de la machine, les registres à préférer et/ou à laisser tranquille. Comment discuter avec le processeur vidéo et accéder à la VRAM. Mais là encore, il y a de nombreux exemples dans de la documentation.</p>
<p>La machine, bien que peu connue et peu dynamique, a eu déjà eu des retro-développeurs dans le passé proche. La route est pavée. Un peu laissée à l'abandon, mais pavée quand même, et c'est agréable. <strong>Merci à eux</strong>.</p>
<p>Au final, transférer en assembleur mes routines les plus coûteuses se révèle <strong>extrêmement bénéfique</strong>. J'en aurais bien transféré plus, mais d'après la documentation, j'ai évalué à un peu moins de 500 octets la place disponible de manière « sûre ». On devrait pouvoir pousser un peu plus, mais je n'avais pas le temps de pousser les tests. Restons prudent.</p>
<h3>... in Space</h3>
<p>Pendant le développement, j'ai souvent hésité. Est-ce que ce sont des <strong>bateaux</strong> ? Est-ce que ce sont des <strong>vaisseaux spaciaux</strong> ? Initialement, je comptais redéfinir des caractères afin d'avoir un affichage un peu plus joli. Le temps manquant, je suis resté sur le jeu de caractères disponibles dans le BASIC, qui heureusement à quelques caractères permettant de tracer des lignes en mode basse résolution.</p>
<p>Pas non plus le temps d'enjoliver en utilisant un peu de mode haute résolution, qui peut se partager l'écran sur cette machine avec le mode texte. L'essentiel du développement se fait pendant mes vacances de fin d'années, et donc juste à la fin de la période de la Game Jam.</p>
<p>Donc bateau ou vaisseau... cela n'a pas vraiment d'importance en soi.</p>
<p>Oui mais, dans cette Game Jam, même si on n'en est qu'à la deuxième session, il est de « tradition » de pousser un peu la qualité en ayant une <strong>couverture</strong> de jeu, une <strong>illustration</strong>, un <strong>manuel</strong>, ou un peu de tout ça. Et les jeux de l'époque misaient beaucoup sur ce packaging pour faire travailler l'imagination.</p>
<p>Donc je dois choisir. Et je choisi l'espace... même si le fond de l'écran pendant le jeu reste bleu foncé. Les vaisseaux ne sont pas coulés après avoir été touchés, mais désactivés, ce qui me donne une justification au RADAR, qui réagi aux morceaux de vaisseaux touchés.</p>
<p>Il me reste une <strong>IA</strong> à <strong>bricoler</strong>. Je fais simple : si le RADAR ne donne pas d'information, le coup prochain se fera au hasard. S'il donne une information, le tir se fera d'autant de cases que la distance indiquée par le RADAR, réparties en horizontal et en vertical. Cela ne prend pas en compte le déplacement des vaisseaux, ni les touches précédentes, mais après quelques parties, je juge que c'est suffisant pour mettre une petite pression à l'humain.</p>
<h3>Conclusion</h3>
<p>Une <strong>machine originale</strong>, qui demande vraiment à y revenir. Un jeu qui ne vole pas très haut, mais qui a été intéressant à optimiser et comme toujours, à terminer et présenter.</p>
<p>Si vous aussi vous voulez jouer avec des torpilles photoniques, c'est <a href="https://mokona78.itch.io/plouf-in-space">par ici</a>.</p>
<p><img alt="Écran de jeu de Plouf... in Space" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202301/20230103-PloufInSpace.png"></p>Inifinite Turtles, un jeu avec des tortues jusqu'en bas.2022-12-22T00:00:00+01:002022-12-22T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2022-12-22:/inifinite-turtles-un-jeu-avec-des-tortues-jusquen-bas.html<p>Le jeu « <a href="https://store.steampowered.com/app/1952740/Infinite_Turtles/"><strong>Inifinite Turtles</strong></a> », de Charlie Brej, est un casse-tête de programmation. De manière classique dans ce type de jeu, vous êtes exposés à des problèmes de plus en plus complexe à résoudre, en ayant à votre disposition de nouvelles briques du système.</p>
<p>En ce sens, il se rapproche des …</p><p>Le jeu « <a href="https://store.steampowered.com/app/1952740/Infinite_Turtles/"><strong>Inifinite Turtles</strong></a> », de Charlie Brej, est un casse-tête de programmation. De manière classique dans ce type de jeu, vous êtes exposés à des problèmes de plus en plus complexe à résoudre, en ayant à votre disposition de nouvelles briques du système.</p>
<p>En ce sens, il se rapproche des jeux de <em>Zachtronics</em> comme <em>Shenzhen I/O</em>, de <em>Human Resource Machine</em> ou bien d'autres, à un niveau de réalisation moins poussé tout en étant tout à fait acceptable.</p>
<p>Dans « Infinite Turtles », votre terrain est une grille de positions avec quatre « portes » aux points cardinaux. Ces portes sont des entrées ou des sorties par lesquels vont transiter des jetons numérotés.</p>
<p>À votre charge d'acheminer les jetons grâce à des tapis roulants à travers la grille.</p>
<p>Chaque niveau vous indique l'objectif à atteindre et vous donne, pour les premiers niveaux, des indications sur le fonctionnement du jeu, ainsi qu'une nouvelle brique fonctionnelle.</p>
<p>Ces briques fonctionnelles occupent un espace sur la grille. Elles réalisent une fonction simple, comme par exemple un aiguillage, un clonage du jeton ou encore un filtre.</p>
<p>De plus, chaque niveau réalisé devient une nouvelle brique fonctionnelle que vous pouvez utiliser pour résoudre d'autres niveaux. Et bien entendu, le jeu est construit de manière à ce que les nouvelles fonctions servent dans les problèmes suivants.</p>
<p>Enfin, pour vous aider à la réalisation du niveau, un batterie de tests est disponible, que vous pouvez exécuter plus ou moins rapidement, afin d'aider à mettre au point la solution.</p>
<p>Et enfin, un score vous indique l'efficacité en temps et en briques utilisées et vous pouvez le comparer aux autres joueurs.</p>
<h2>Des tortues jusqu'en bas</h2>
<p>Le titre du jeu ne fait pas référence à une tortue de type de celle utilisée dans le langage Logo, mais à la cosmogonie qui dit que la Terre repose sur une tortue, elle même reposant sur une tortue,... et tout ceci <a href="https://fr.wikipedia.org/wiki/Des_tortues_jusqu%27en_bas">jusqu'en bas</a>.</p>
<p>Le thème est introduit dès le début dans les explications par les dialogues des personnages qui animent la narration, qui sont toujours par couple identique mais de tailles différentes.</p>
<p>Ainsi, le jeu introduit l'utilisation de briques fonctionnelles qui utilisent elles-mêmes d'autres briques fonctionnelles qui utilisent, etc.</p>
<p><img alt="Aperçu du jeu Infinite Turtles" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202212/InfiniteTurtles-750.jpeg"></p>
<h1>Tout dans la synchro</h1>
<p>La difficulté des problèmes n'est pas tant de les résoudre fonctionnellement, mais de les placer sur une grille restreinte. En effet, rapidement, la place se fait rare, et l'utilisation des briques déjà faites est nécessaire.</p>
<p>Cependant, le système que crée le jeu est très dépendant des synchronisations. En effet, les jetons arrivent avec un tempo déterminé par les tests et leur temps de trajet sur les tapis roulants est entièrement dépendant de la longueur de ces trajets, y compris des trajets dans les briques internes.</p>
<p>Dès les premiers niveaux, les défis proposés sont clairs : il va falloir trouver des moyens de synchroniser tous ces jetons pour qu'ils arrivent face au briques fonctionnelles au moment voulu. Le jeu guide vers des systèmes de synchronisation que l'on découvre et que l'on pourra appliquer plus tard.</p>
<p>Mais ces systèmes prennent de la place, beaucoup de place, car basé sur de l'envoi de messages par jetons. Ils nécessitent donc des tapis roulants encombrants.</p>
<h1>Et donc ?</h1>
<p>Je n'ai pas terminé tous les niveaux du jeu. Je suis allé assez loin, mais au bout d'un moment, j'avais l'impression que le problème à résoudre était essentiellement du routage de jeton et comment faire tenir le tout dans la grille.</p>
<p>Les fonctions d'édition avancées de la grille, qui ne sont pas présentée dès le début, ne sont pas toujours très agréable non plus. Quand on veut essayer différents type de routages afin de voir celui qui prend le moins d'espace, le comportement des tapis roulant est rapidement agaçant.</p>
<p>Un autre défaut, mais moindre, est la progression des niveaux, qui mériterait plus de fluidité. Les problèmes par lesquels ont passe ont un objectif, on le sent bien, mais on passe parfois du coq à l'âne, sur des voies que l'on sent se rejoindre au loin, mais qui produisent une sorte de « task switching » peu agréable.</p>
<p>Malgré ces petits soucis, et sur la vingtaine de niveaux que j'ai pu résoudre, j'ai trouvé le jeu amusant et plutôt original. Le modèle d'automation et le type de programmation qu'il induit sont plutôt originaux, avec une petite histoire métaphysique pour enrober le tout.</p>
<p>C'est un jeu qui se défend tout à fait dans sa catégorie, et si vous êtes curieux et aimez les jeux d'énigmes logiques, cela pourra vous plaire.</p>La Maison dans la colline, partie 12022-12-22T00:00:00+01:002022-12-22T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2022-12-22:/la-maison-dans-la-colline-partie-1.html<p>En juin 2022, sur Facebook, <a href="https://www.youtube.com/@Olipix">Olipix</a> lance un groupe avec l'objectif de donner un peu d'activité à des ordinateurs qui n'en n'ont pas beaucoup. En effet, si quelques anciennes machines bénéficient de nombreux nouveaux logiciels homebrew toujours de nos jours, certaines autres ont une base d'utilisateurs beaucoup plus restreinte. Et …</p><p>En juin 2022, sur Facebook, <a href="https://www.youtube.com/@Olipix">Olipix</a> lance un groupe avec l'objectif de donner un peu d'activité à des ordinateurs qui n'en n'ont pas beaucoup. En effet, si quelques anciennes machines bénéficient de nombreux nouveaux logiciels homebrew toujours de nos jours, certaines autres ont une base d'utilisateurs beaucoup plus restreinte. Et souvent une ludothèque maigrichonne.</p>
<p>Initialement, je n'ai pas trop prêté attention à l'initiative. J'étais justement en train de nettoyer mon compte Facebook avec l'idée de ne plus y mettre les pieds, tout en gardant un accès minimale « au cas où ». Mais quand on m'avertie qu'un vote a désigné le <strong>VG5000µ</strong> comme première machine, je dresse l'oreille.</p>
<p>Ça tombe bien, je m'étais remis à l'étude du VG5000µ après le hiatus de l'étude du <a href="https://www.triceraprog.fr/baisse-de-regime-en-apparence.html">Micral N</a>. C'est une excellente occasion pour relier les deux activités : continuer l'exploration de la machine, tout en développant un jeu.</p>
<p>Reste à trouver l'idée. J'aimerais quelque chose qui pousse un peu les capacités d'affichage de la machine. En attendant, je commence à mettre en place une chaîne d'outils de développements avec comme premier objectif le développement d'un programme de tests des différentes possibilités graphiques de l'EF9345.</p>
<p>L'idée derrière la batterie de tests est de documenter l'utilisation de ce processeur graphique en publiant les sources, de démontrer ses différents modes (ce qui a déjà été fait par le passé), et de proposer des tests pour pouvoir travailler les détails des timings et les commandes non implémentées actuellement dans les deux émulateurs du VG5000µ (<a href="http://dcvg5k.free.fr/">dcvg5k</a> et <a href="https://www.mamedev.org/">MAME</a>).</p>
<p>Je n'ai pas encore terminé cette batterie de tests, puisque je suis passé entre temps sur le développement du jeu. Je publierai ça sous peu, même si pas terminé, accompagné d'articles.</p>
<p>En attendant, en voici une copie d'écran.</p>
<p><img alt="Écran de jeu de la Maison dans la colline" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202212/20220927232738-MaisonColline-01.png"></p>Utilisation de z88dk pour le VG50002022-07-03T00:00:00+02:002022-07-03T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2022-07-03:/utilisation-de-z88dk-pour-le-vg5000.html<p>Encore ?! Oui... encore. Une nouvelle manière de gérer la construction d'un programme VG5000µ. Après la version <a href="https://www.triceraprog.fr/automatisation-utilisation-de-sublime-text-3.html">Sublime Text et z80asm</a> en 2018, puis la version <a href="https://www.triceraprog.fr/automatisation-utilisation-de-visual-studio-code.html">Visual Studio Code et sjasmplus</a> en 2020, je voulais essayer autre chose.</p>
<p>J'avais laissé de côté Sublime Text et z80asm pour deux raisons : le changement …</p><p>Encore ?! Oui... encore. Une nouvelle manière de gérer la construction d'un programme VG5000µ. Après la version <a href="https://www.triceraprog.fr/automatisation-utilisation-de-sublime-text-3.html">Sublime Text et z80asm</a> en 2018, puis la version <a href="https://www.triceraprog.fr/automatisation-utilisation-de-visual-studio-code.html">Visual Studio Code et sjasmplus</a> en 2020, je voulais essayer autre chose.</p>
<p>J'avais laissé de côté Sublime Text et z80asm pour deux raisons : le changement de license de Sublime Text que je n'avais pas apprécié, et le côté très simpliste de z80asm, dont je touchais des limites.</p>
<p>Pour un nouveau projet, je voulais utiliser <a href="https://z88dk.org/">z88dk</a>, un kit de développement pour machines Z80, avec du support C et ASM, ainsi que des bibliothèques standards. Je voulais aussi approfondir ma connaissance du support de toolchains avec CMake.</p>
<p>Alors oui, <code>cmake</code> pour un tout petit projet pour des machines des années 80, ça fait un peu surdimensionné... Je le concède. Et ça n'enlève en rien mon envie de fouiller de ce côté.</p>
<p>Un exemple, qui peut servir de base, est disponibles sur <a href="https://gitlab.com/mokona/vg5000-with-z88dk">GitLab</a> et <a href="https://github.com/Triceraprog/vg5000-with-z88dk">GitHub</a>. Il y a quelques limitations quand à l'environnement supporté, car je n'ai testé que sur mon ordinateur de développement.</p>
<p>Mais l'essentiel est là, et peut éviter des heures de recherches entre la documentation de CMake et StackOverflow, la première était connue pour son aridité et le second pour, en ce qui concerne CMake, ses 99% de réponses fausses, ou tout du moins obsolètes.</p>
<p>Par la suite, je vais décrire les différents éléments qui forment le projet CMake.</p>
<h3>CMakeLists.txt</h3>
<p>Tout projet cmake a pour point d'entrée un fichier <code>CMakeLists.txt</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nb">cmake_minimum_required</span><span class="p">(</span><span class="s">VERSION</span><span class="w"> </span><span class="s">3.20</span><span class="p">)</span>
</code></pre></div>
<p>Tout d'abord, on indique la version minimale de <code>cmake</code> à utiliser. Il est toujours préférable de mettre celle sur laquelle on a validé le fonctionnement, car <code>cmake</code> évolue souvent et tenter d'en mettre une plus ancienne est acrobatique.</p>
<div class="highlight"><pre><span></span><code><span class="nb">list</span><span class="p">(</span><span class="s">APPEND</span><span class="w"> </span><span class="s">CMAKE_MODULE_PATH</span><span class="w"> </span><span class="s2">"${CMAKE_CURRENT_SOURCE_DIR}/cmake/"</span><span class="p">)</span>
</code></pre></div>
<p>Je vais avoir besoin d'ajouter des scripts de description de machine et de compilateur localement, car non reconnus nativement, du moins aujourd'hui, par <code>cmake</code>. J'indique donc que certains scripts se trouvent dans le répertoire locale <code>cmake/</code>, en modifiant la variable <code>CMAKE_MODULE_PATH</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nb">project</span><span class="p">(</span><span class="s">vg_tests</span><span class="w"> </span><span class="s">C</span><span class="w"> </span><span class="s">ASM</span><span class="p">)</span>
</code></pre></div>
<p>Je déclare ensuite le nom du projet <code>vg_tests</code>, ainsi que les langages de programmation supportés.</p>
<div class="highlight"><pre><span></span><code><span class="c"># If need for cmake debug, the next line can help</span>
<span class="c"># set(CMAKE_VERBOSE_MAKEFILE 1)</span>
</code></pre></div>
<p>Comme indiqué, mettre la variable <code>CMAKE_VERBOSE_MAKEFILE</code> à 1 permet d'avoir des informations sur les actions qui sont faites lors de la génération du projet. Très pratique pour comprendre quelles sont les lignes de commandes générées, avec leurs options. Essentiel pour mettre au point quand on tâtonne.</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_RUNTIME_OUTPUT_DIRECTORY</span><span class="w"> </span><span class="s">../output</span><span class="p">)</span>
</code></pre></div>
<p>La variable <code>CMAKE_RUNTIME_OUTPUT_DIRECTORY</code> indique où sont les artefacts de sortie. Dans notre cas, c'est là que se trouveront les <code>.k7</code> utilisables pour le vg5000µ (ou les <code>.wav</code>, si vous le désirez).</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="p">(</span><span class="s">SOURCE_FILES</span><span class="w"> </span><span class="s">src/main.c</span><span class="w"> </span><span class="s">src/auxiliary.asm</span><span class="p">)</span>
</code></pre></div>
<p><code>SOURCE_FILES</code> est une variable interne à ce script. C'est une habitude que de passer par une variable intermédiaire pour spécifier la liste des fichiers sources. Dans certains cas, on peut avoir besoin de la réutiliser.</p>
<p>Ici, je vais compiler et assembler un fichier C et un fichier assembleur.</p>
<div class="highlight"><pre><span></span><code><span class="nb">add_executable</span><span class="p">(</span><span class="o">${</span><span class="nv">PROJECT_NAME</span><span class="o">}</span><span class="w"> </span><span class="o">${</span><span class="nv">SOURCE_FILES</span><span class="o">}</span><span class="p">)</span>
<span class="nb">target_compile_options</span><span class="p">(</span><span class="o">${</span><span class="nv">PROJECT_NAME</span><span class="o">}</span><span class="w"> </span><span class="s">PRIVATE</span><span class="w"> </span><span class="s">-I</span><span class="o">$ENV{</span><span class="nv">Z88DK_HOME</span><span class="o">}</span><span class="s">/include</span><span class="w"> </span><span class="s">-Isrc/</span><span class="w"> </span><span class="s">-vn</span><span class="w"> </span><span class="s">-m</span><span class="p">)</span>
<span class="nb">target_link_options</span><span class="p">(</span><span class="o">${</span><span class="nv">PROJECT_NAME</span><span class="o">}</span><span class="w"> </span><span class="s">PRIVATE</span><span class="w"> </span><span class="s">-m</span><span class="w"> </span><span class="s">-create-app</span><span class="w"> </span><span class="s">-subtype=default</span><span class="p">)</span>
</code></pre></div>
<p>La déclaration de l'exécutable utilise la variable <code>PROJECT_NAME</code>, qui prend le nom spécifié dans <code>project()</code> au tout début, et y associe la liste des fichiers sources.</p>
<p>Puis sont définies les options de compilations et les options pour l'éditeur de liens. La nature des options ne sont pas du domaine de cet article. Elles seront transmises à <code>zcc</code>, qui est la commande générique pour toutes les opérations de construction dans <code>z88dk</code>.</p>
<div class="highlight"><pre><span></span><code><span class="c"># Fixes the k7 format for old z88dk versions.</span>
<span class="nb">set</span><span class="p">(</span><span class="s">INPUT_FOR_FIX</span><span class="w"> </span><span class="o">${</span><span class="nv">CMAKE_RUNTIME_OUTPUT_DIRECTORY</span><span class="o">}</span><span class="s">/</span><span class="o">${</span><span class="nv">PROJECT_NAME</span><span class="o">}</span><span class="s">.k7</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">ZERO_FILE</span><span class="w"> </span><span class="o">${</span><span class="nv">CMAKE_SOURCE_DIR</span><span class="o">}</span><span class="s">/zero-file</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">OUTPUT_FOR_FIX</span><span class="w"> </span><span class="o">${</span><span class="nv">CMAKE_RUNTIME_OUTPUT_DIRECTORY</span><span class="o">}</span><span class="s">/</span><span class="o">${</span><span class="nv">PROJECT_NAME</span><span class="o">}</span><span class="s">.fix.k7</span><span class="p">)</span>
<span class="nb">add_custom_command</span><span class="p">(</span><span class="s">OUTPUT</span><span class="w"> </span><span class="s">k7_fix</span>
<span class="w"> </span><span class="s">DEPENDS</span><span class="w"> </span><span class="o">${</span><span class="nv">PROJECT_NAME</span><span class="o">}</span>
<span class="w"> </span><span class="s">COMMAND</span><span class="w"> </span><span class="o">${</span><span class="nv">CMAKE_COMMAND</span><span class="o">}</span><span class="w"> </span><span class="s">-E</span><span class="w"> </span><span class="s">cat</span><span class="w"> </span><span class="o">${</span><span class="nv">INPUT_FOR_FIX</span><span class="o">}</span><span class="w"> </span><span class="o">${</span><span class="nv">ZERO_FILE</span><span class="o">}</span><span class="w"> </span><span class="s">></span><span class="w"> </span><span class="o">${</span><span class="nv">OUTPUT_FOR_FIX</span><span class="o">}</span>
<span class="w"> </span><span class="p">)</span>
<span class="nb">add_custom_target</span><span class="p">(</span><span class="o">${</span><span class="nv">PROJECT_NAME</span><span class="o">}</span><span class="s">-fix</span><span class="w"> </span><span class="s">ALL</span><span class="w"> </span><span class="s">DEPENDS</span><span class="w"> </span><span class="s">k7_fix</span><span class="p">)</span>
</code></pre></div>
<p>La version actuelle de <code>z88dk</code> a un bug au niveau de la génération des données du VG5000µ. Il manque des octets à la fin, qui sont attendues par la ROM pour valider la fin du fichier.</p>
<p>J'ai soumis un fix, qui a été accepté, mais le temps que cela soit déployé partout, j'ajoute cette <code>custom_target</code>, qui utilise une <code>custom_command</code>. Le fichier <code>zero-file</code> est le fichier nécessaire à l'ajustement, et ne contient que des <code>0</code>.</p>
<h3>Compilation croisée</h3>
<p>Lancer <code>cmake</code> tel quel ne va pas fonctionner, car par défaut, cela utilisera les outils de compilation de la machine hôte. Il faut donc spécifier une environnement de compilation croisée, c'est-à-dire les outils pour compiler pour une machine cible, et non la machine hôte.</p>
<p>Pour cela, <code>cmake</code> a un mécanisme de déclaration de compilation croisée. Lors de l'initialisation de <code>cmake</code>, il faut spécifier la variable <code>CMAKE_TOOLCHAIN_FILE</code>. Ici, <code>cmake -DCMAKE_TOOLCHAIN_FILE=z88dk-vg5000.cmake</code>.</p>
<p>Ici, on entre un peu dans le tâtonnement. Ce que j'expliquer fonctionne, mais est-ce que c'est carré ? C'est une bonne question.</p>
<p>Voici l'explication du fichier <code>z88dk-vg5000.cmake</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_SYSTEM_NAME</span><span class="w"> </span><span class="s">vg5000</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_SYSTEM_PROCESSOR</span><span class="w"> </span><span class="s">Z80</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_C_COMPILER_ID</span><span class="w"> </span><span class="s">z88dk</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_ASM_COMPILER_ID</span><span class="w"> </span><span class="s">z88dk</span><span class="p">)</span>
</code></pre></div>
<p>En premier lieu, on fourni des valeurs à des variables internes de <code>cmake</code> indiquant le nom du système, le processeur, et des identifiants du compilateur et de l'assembleur. Ces variables seront utilisées par <code>cmake</code> pour déterminer quels fichiers de description il doit chercher.</p>
<p>Il n'est pas toujours très clair de savoir quelle variable influe sur quoi. Il faut se fier aux messages d'erreurs lorsqu'un fichier n'est pas trouvé...</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_TRY_COMPILE_TARGET_TYPE</span><span class="w"> </span><span class="s">STATIC_LIBRARY</span><span class="p">)</span>
</code></pre></div>
<p>Cette variable indique à <code>cmake</code> si, pour valider le fonctionnement de la chaîne de compilation, un essai se fera sur une bibliothèque ou sur un exécutable. L'exécutable, pour être validé, doit être lancé. Comme je ne définie pas de moyen de lancer l'exécutable, je demande à ne faire l'essai de compilation que sur une bibliothèque, ce qui est en fait le défaut lors d'une compilation croisée.</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="p">(</span><span class="s">TOOLCHAIN_PREFIX</span><span class="w"> </span><span class="s">z88dk</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_C_COMPILER</span><span class="w"> </span><span class="o">${</span><span class="nv">TOOLCHAIN_PREFIX</span><span class="o">}</span><span class="s">.zcc</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_ASM_COMPILER</span><span class="w"> </span><span class="o">${</span><span class="nv">TOOLCHAIN_PREFIX</span><span class="o">}</span><span class="s">.zcc</span><span class="p">)</span>
</code></pre></div>
<p>Ici, j'indique le nom des compilateurs et assembleur. Pour <code>cmake</code>, tout ce qui prend une source et sort un fichier objet est un <code>compiler</code>. Pour <code>z88dk</code>, je passe par le <code>frontend</code> <code>zcc</code> plutôt que les exécutable eux-mêmes, ce qui semble être préféré dans la documentation (et dans la façon dont est construit le paquetage).</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_DEPENDS_USE_COMPILER</span><span class="w"> </span><span class="s">True</span><span class="p">)</span>
</code></pre></div>
<p>J'indique aussi à <code>cmake</code> d'utiliser le compilateur pour trouver les dépendances entre les fichiers.</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_C_COMPILE_OBJECT</span><span class="w"> </span><span class="s2">"<CMAKE_C_COMPILER> +vg5k <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> -c <SOURCE>"</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_C_LINK_EXECUTABLE</span><span class="w"> </span><span class="s2">"<CMAKE_C_COMPILER> +vg5k <FLAGS> <OBJECTS> -o <TARGET> <CMAKE_C_LINK_FLAGS> <LINK_FLAGS> <LINK_LIBRARIES>"</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_ASM_COMPILE_OBJECT</span><span class="w"> </span><span class="s2">"<CMAKE_C_COMPILER> +vg5k <DEFINES> <INCLUDES> <FLAGS> -o <OBJECT> -c <SOURCE>"</span><span class="p">)</span>
</code></pre></div>
<p>On y est presque. Dans un cas classique où le compilateur se comporte de manière classique (disons comme un <code>gcc</code>, un <code>clang</code> ou autre), on pourrait se passer de ces lignes. Mais <code>zcc</code> à besoin comme premier paramètre de la plateforme cible (<code>+vg5k</code> ici).</p>
<p>J'indique donc à <code>cmake</code> comment générer la ligne de commande pour les fichiers <code>C</code> et <code>ASM</code>, avec une syntaxe de template.</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_FIND_ROOT_PATH_MODE_PROGRAM</span><span class="w"> </span><span class="s">NEVER</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_FIND_ROOT_PATH_MODE_LIBRARY</span><span class="w"> </span><span class="s">NEVER</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_FIND_ROOT_PATH_MODE_INCLUDE</span><span class="w"> </span><span class="s">NEVER</span><span class="p">)</span>
</code></pre></div>
<p>Et enfin, je demande à <code>cmake</code> de ne pas chercher à résoudre les commandes <code>find_library</code>, que je n'utiliserai pas.</p>
<h3>Et ce n'est pas fini !</h3>
<p>Le fichier de compilation croisée indique que l'on compile pour <strong>VG5000µ</strong>. Mais <code>cmake</code> ne connait pas cette plateforme, et va donc chercher un script qui lui en dirait plus.</p>
<p>Ce que j'ai fait n'est probablement pas entièrement correct, car j'associe la plateforme avec la chaîne de compilation. Et je fais ça dans le fichier <code>cmake/Platform/vg5000.cmake</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nb">set_property</span><span class="p">(</span><span class="s">GLOBAL</span><span class="w"> </span><span class="s">PROPERTY</span><span class="w"> </span><span class="s">TARGET_SUPPORTS_SHARED_LIBS</span><span class="w"> </span><span class="s">FALSE</span><span class="p">)</span>
</code></pre></div>
<p><code>z88dk</code> ne supporte pas un systmème de bibliothèque dynamiques (style <code>DLL</code>, <code>so</code> ou <code>dynlib</code>).</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_C_OUTPUT_EXTENSION</span><span class="w"> </span><span class="s">.o</span><span class="p">)</span>
</code></pre></div>
<p><code>z88dk</code> ne reconnaît que l'extension <code>.o</code> comme fichiers objets. On indique donc à <code>cmake</code> de produire des fichiers objets avec cette extension.</p>
<div class="highlight"><pre><span></span><code><span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_SYSTEM_INCLUDE_PATH</span><span class="w"> </span><span class="o">$ENV{</span><span class="nv">Z88DK_HOME</span><span class="o">}</span><span class="s">/include</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_SYSTEM_LIBRARY_PATH</span><span class="w"> </span><span class="o">$ENV{</span><span class="nv">Z88DK_HOME</span><span class="o">}</span><span class="s">/lib</span><span class="p">)</span>
<span class="nb">set</span><span class="p">(</span><span class="s">CMAKE_SYSTEM_PROGRAM_PATH</span><span class="w"> </span><span class="o">$ENV{</span><span class="nv">Z88DK_HOME</span><span class="o">}</span><span class="s">/bin</span><span class="p">)</span>
</code></pre></div>
<p>Ces variables ont l'air d'être les pendants des <code>CMAKE_FIND_ROOT_PATH_MODE_*</code> indiqués dans le fichier de compilation croisée. Le fonctionnement n'est pas hyper clair.</p>
<p>Voilà, à présent <code>cmake</code> sait compiler un fichier <code>C</code> avec <code>z88dk</code> pour <strong>VG5000µ</strong>.</p>
<h3>Et l'assembleur ?</h3>
<p>Pour une raison que je n'ai pas creusé, mais probablement concernant la séparation des plateformes des compilateurs, le support d'un assembleur nécessite un autre fichier, différent du précédent <code>vg5000.cmake</code>.</p>
<p>C'est dans <code>cmake/Compiler/z88dk-ASM.cmake</code> que <code>cmake</code> va chercher l'outil pour traiter l'<code>ASM</code> pour <code>z88dk</code>. Ce qui se tient. Pourquoi est-ce qu'il ne va pas chercher <code>z88dk-C.cmake</code> au même endroit, cela m'échappe...</p>
<p>Le contenu est strictement identique à celui du fichier <code>vg5000.cmake</code>, puisque l'on s'adresse au même outil <code>zcc</code>.</p>
<h3>Et la cerise optionnelle</h3>
<p>Le fichier <code>z88dk-clion.yaml</code> est un fichier qui ajoute un support de <code>z88dk</code> (assez succin) à <code>Clion</code>. Par défaut, lors de la génération de <code>cmake</code>, l'IDE va essayer de trouver un certain nombre d'information en interrogeant le compilateur. Les <code>#define</code> par exemple, ou les répertoires d'inclusion par défaut.</p>
<p>Mais <code>zcc</code> ne réagissant par bien à la question, Clion affiche un warning. Il est cependant possible de lui indiquer manuellement les informations recherchées, et c'est le but de ce fichier. Cependant, sa description tombe hors du sujet de cet article.</p>
<h3>Conclusion</h3>
<p>Ça a été une aventure, comme à chaque fois que l'on sort des sentiers battus avec <code>cmake</code>. J'y ai appris un peu plus de choses, ce qui était probablement l'objectif initial. Et j'ai une façon de générer un programme <strong>VG5000µ</strong> à partir de tout outil qui utilise <code>cmake</code>, ce qui couvre aussi Visual Studio Code.</p>Apprendre l'assembleur... mais comment ?2022-03-05T00:00:00+01:002022-03-05T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2022-03-05:/apprendre-lassembleur-mais-comment.html<p>Il y a peu, j'ai eu une discussion à propos de l'apprentissage de l'assembleur. La discussion était partie de l'envie d'une personne de créer un jeu sur MSX, mais directement au niveau de la machine, plutôt que de passer par un langage de haut niveau, comme le BASIC natif. Et …</p><p>Il y a peu, j'ai eu une discussion à propos de l'apprentissage de l'assembleur. La discussion était partie de l'envie d'une personne de créer un jeu sur MSX, mais directement au niveau de la machine, plutôt que de passer par un langage de haut niveau, comme le BASIC natif. Et pourquoi pas. Une donnée importante : la personne en question connaît déjà la programmation, c'est donc un abord de nouveau langage dont on parle, et non des concepts généraux du développement d'un programme.</p>
<p>Lorsque l'on aborde un langage de plus haut niveau, que ce soit BASIC ou Pascal, on va se concentrer sur la manière d'exprimer des concepts dans ce langage en particulier. Lorsque l'on connaît déjà un autre langage de même famille (large), il s'agit même souvent de comprendre quelle sont les particularité du langage appris.</p>
<p>Lorsque l'on aborde une machine en particulier dans un langage de haut niveau, il s'agit en suite de connaître les bons appels, les bonnes fonctions, particulières à cette machine.</p>
<p>Mais comment démarrer en assembleur ? Avec l'assembleur, toute la couche d'abstraction est retirée, il n'y a plus de concept de variables, de fonctions, de structures de données, on se retrouve face à face à la machine. Cela implique en premier lieu qu'apprendre l'assembleur, c'est apprendre le fonctionnement particulier de cette machine, et même pas seulement du fonctionnement du processeur.</p>
<p>Quand on regarde les ressources qui enseignent l'assembleur, que ce soit pour une machine ou seulement un processeur, on se retrouve avec une structure assez similaire où il est d'abord expliqué l'arithmétique et la logique binaire, la représentation des entiers, puis sont abordés les registres et globalement l'architecture du processeur. Ce sont des dizaines de pages à comprendre, suivre... et la plupart du temps sans exercice pratique, sans mise en application. Cela semble sous-entendre que pour démarrer la moindre opération, il faut lire une centaine de pages.</p>
<p>Lorsque l'on a déjà des connaissances sur d'autres machines et processeurs similaire, cela peut aller vite. Mais lorsque c'est le premier abord du mode du bas niveau, c'est beaucoup à ingurgiter et probablement très démotivant.</p>
<p>Je me suis posé la question de comment pourrait être abordé la question, j'ai pris quelques notes et je vais m'essayer à l'exercice dans des articles qui suivront. Mon objectif est de pouvoir construire de la connaissance sur la programmation en assembleur pour des personnages qui n'en n'auraient jamais fait.</p>
<p>Voyons ce que cela donne...</p>Baisse de régime... en apparence.2022-03-04T00:00:00+01:002022-03-04T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2022-03-04:/baisse-de-regime-en-apparence.html<p>Après une grosse activité sur le VG5000µ principalement, il y a eu bien moins d'article sur Triceraprog, et la troisième vidéo sur les langages de programmation qui est prévue depuis deux ans n'est toujours pas là. Mais que se passe-t-il ?</p>
<p>En Janvier 2021, l'association <a href="https://www.mo5.com">M05.COM</a> m'a proposé de travailler …</p><p>Après une grosse activité sur le VG5000µ principalement, il y a eu bien moins d'article sur Triceraprog, et la troisième vidéo sur les langages de programmation qui est prévue depuis deux ans n'est toujours pas là. Mais que se passe-t-il ?</p>
<p>En Janvier 2021, l'association <a href="https://www.mo5.com">M05.COM</a> m'a proposé de travailler sur <a href="https://micral.mo5.com/">le projet de restauration et de documentation d'un « Micral N »</a>. Toute l'année 2021, avec d'autres membres de l'association, nous nous sommes plongé dans la compréhension de cette machine historique, ainsi que dans la création d'une version virtuelle qui a permis d'en comprendre les détails.</p>
<p>C'est une activité passionnante, j'ai appris énormément de choses et j'ai même pu écrire (ou porter) une poignée de logiciels pour cette machine. Forcément, mes autres sujets en ont pâti et le VG5000µ est un peu en pause. Mais il est toujours sur mon bureau prêt à reprendre du service.</p>
<p>La communication des avancées sur le « Micral N » ayant besoin d'être coordonnée avec l'association, je ne parle pas des avancées sur Triceraprog. En tout cas pas pour le moment, à part la <a href="https://www.triceraprog.fr/breve-presentation-de-lintel-8008.html">petite présentation du 8008</a> que j'ai écrite il y a quelques mois.</p>
<p>Un live Twitch est visible où nous présentons la machine. Malheureusement un peu gâché par des problèmes de micros.</p>
<p>J'ai hâte de pouvoir parler de cette machine plus en détails.</p>
<div class="embed-yt text-center" data-video-id="BE7X611r_Uk" style="width: 560; height:315">
<div class="embed-yt-play"></div>
</div>Récréation 3D, Apple II2021-12-15T00:00:00+01:002021-12-15T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2021-12-15:/recreation-3d-apple-ii.html<p>L'un des tout premiers micro ordinateurs de très grande popularité, particulièrement aux États-Unis. L'Apple II.</p>
<p>Voici une recréation en synthèse, avec un moniteur ambre posé lui-même sur une paire de lecteurs Disk II.</p>
<p><img alt="Apple II" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202112/20211216-Apple-II-Render-750.jpeg"></p>Récréation 3D, CDC 64002021-11-18T00:00:00+01:002021-11-18T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2021-11-18:/recreation-3d-cdc-6400.html<p>Le langage Pascal, nommé ainsi en référence à Blaise Pascal, concepteur de la <a href="https://www.triceraprog.fr/recreation-3d-pascaline.html">Pascaline</a>, a été développé sur une machine nommée CDC 6400. C'est une machine de type <em>mainframe</em>, un gros truc, même si la version 6400 est une version allégée d'une autre de la gamme, et dont l'unité de …</p><p>Le langage Pascal, nommé ainsi en référence à Blaise Pascal, concepteur de la <a href="https://www.triceraprog.fr/recreation-3d-pascaline.html">Pascaline</a>, a été développé sur une machine nommée CDC 6400. C'est une machine de type <em>mainframe</em>, un gros truc, même si la version 6400 est une version allégée d'une autre de la gamme, et dont l'unité de mémoire adressée a une largeur de 60 bits.</p>
<p>Voici une recréation en synthèse, où l'on voit en premier lieu la console avec ses deux écrans ronds, hérités des écrans de RADARs. Les calculs sont fait dans les grosses armoires que je n'ai pas détaillées.</p>
<p><img alt="CDC6400" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202111/20211117-Rendu-002-750.jpeg"></p>Brève présentation de l'Intel 80082021-11-01T00:00:00+01:002021-11-01T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2021-11-01:/breve-presentation-de-lintel-8008.html<p>Le 8008 d'Intel est un processeur assez particulier. C'est le premier micro-processeur 8 bits, et on peut même dire : le premier micro-processeur tout court. Son grand frère, le 4004, étant plutôt considéré comme un micro-contrôleur. D'autres sites expliquent le pourquoi de cette distinction.</p>
<p>Je veux ici me pencher sur le …</p><p>Le 8008 d'Intel est un processeur assez particulier. C'est le premier micro-processeur 8 bits, et on peut même dire : le premier micro-processeur tout court. Son grand frère, le 4004, étant plutôt considéré comme un micro-contrôleur. D'autres sites expliquent le pourquoi de cette distinction.</p>
<p>Je veux ici me pencher sur le 8008 sous certains aspects techniques. Une brève introduction d'une puce qui va en générer d'autres (8080, 8086, Z80,...)</p>
<p>Cette puce, je l'ai étudiée à travers son utilisation dans le Micral N de la R2E, mais ceci est une autre histoire. Le 8008 est rapidement utilisé dans une série de micro-ordinateurs (le terme apparaît avec le Micral N) sur un cours laps de temps. Dans le désordre, MCM/70, SCELBI, Intellec-8, Mark-8, MCS 8, Bill-1,... Certaines de ces machines reçoivent très rapidement une mise à jour vers le 8080, beaucoup plus flexible. Cependant, c'est bien avec le 8008 que cela commence.</p>
<h2>La puce</h2>
<p>Et le voici en image de synthèse. Ou du moins, dans l'un des boîtiers existant, car il en existe plusieurs. C'est celui que j'avais en référence, et c'est donc celui que j'ai modélisé.</p>
<p><img alt="Image de synthèse d'un 8008 avec brochage." src="https://www.triceraprog.fr/images/202108/Intel-8008-750.png"></p>
<p>Une première chose étonnante : pas de masse. Le 8008 prend du +5V et du -9V. Il semble que la raison soit une volonté d'être compatible avec des circuits électroniques 5V, mais avec un besoin d'une tension totale de 14V... Ce n'est pas trop mon rayon, alors je laisse ça en note. Pour plus de précisions, jusqu'au niveau transistor, je vous dirige vers l'<a href="http://www.righto.com/2016/12/die-photos-and-analysis-of_24.html">analyse en anglais</a> de Ken Shirriff.</p>
<h2>Branchements</h2>
<p>Mis à part VCC et VDD, les deux tensions nécessaires, le 8008 communique avec l'extérieur à travers 16 autres broches.</p>
<p>À noter que le 8008 travaille en « logique physique inversée » par rapport aux signaux logiques : le 0 logique est à +5V, le 1 logique est entre ~0.8V et -9V.</p>
<h3>Broche de synchronisation</h3>
<p>En entrée, le 8008 reçoit en entrée deux horloges déphasées, de même fréquence (500 kHz) : Ø1 et Ø2. Ces deux horloges lui permettent de cadencer les opérations internes.</p>
<p>À partir de ces impulsions, le 8008 forme en sortie le signal <code>SYNC/</code> qui est haut pendant un premier cycle Ø1, Ø2, puis bas pendant un second cycle Ø1, Ø2. Le tout forme un état processeur complet.</p>
<p>Vu qu'il y a un temps de 2µs entre deux fronts de Ø1, cela signifie qu'un état du 8008 dure 4µs.</p>
<p><img alt="Chronogramme des horloges et de la synchro du 8008." src="https://www.triceraprog.fr/images/202111/8008-clock-sync.png"></p>
<h3>Broches d'états</h3>
<p>Trois autres broches en sortie : <code>S1</code>, <code>S2</code> et <code>S3</code>, forment un triplet indiquant dans quel état le processeur se trouve.</p>
<p>Les états sont les suivants : <code>T1</code> et <code>T1I</code>, <code>T2</code>, <code>T3</code>, <code>T4</code>, <code>T5</code>, <code>WAIT</code> et <code>STOPPED</code>.</p>
<p>Les états <code>T1</code> et <code>T2</code> forment ensemble une demande d'information au reste du système. Lors de T1, le 8008 place la partie basse de l'adresse de l'information à obtenir sur les broches d'entrées/sorties. Lors de T2 y est placée la partie haute de l'adresse, ainsi que deux bits indiquant le type de cycle actuel et donc la nature de la donnée attendue : est-ce une instruction, une lecture ou une écriture de donnée en mémoire, ou une opération d'entrée/sortie ?</p>
<p>Cette manière de placer une adresse sur le bus se fera toujours de la même manière lors de différents cycles d'une instruction, et toujours sur les états T1 et T2 : d'abord les 8 bits bas de l'adresse, puis les 6 bits haut (le 8008 a un espace d'adressage de 14 bits), complétés par 2 bits indiquant au système ce que ce couple d'information représente.</p>
<p><code>T1I</code> est une variante de T1 qui est utilisée lorsque le processeur a validé une interruption. L'état est le même que T1 à l'exception du traitement du compteur d'instruction (le registre interne PC), qui n'est pas avancé. Après T1I, le 8008 passera à un état T2.</p>
<p>Lors de l'état <code>T3</code>, le processeur lit la donnée obtenue. Si c'est une instruction à exécuter, celle-ci est stockée pour les états suivants, sauf pour l'instruction <code>HLT</code>, qui est immédiate.</p>
<p>Les états <code>T4</code> et <code>T5</code> sont des états supplémentaires qui existent dans certaines instructions.</p>
<p>Et chaque instruction peut répéter l'ensemble de ces états dans l'ordre pour compléter son exécution. L'instruction <code>INr</code> (incrément d'un registre) va s'exécuter en un seul cycle avec les états T1, T2, T3, T4 et T5, pour un total de 5 états. L'instruction <code>JMP</code> (branchement à une adresse absolue) aura besoin de trois cycles : le premier et le second avec T1, T2 et T3, le troisième avec T1, T2, T3, T4 et T5, pour un total de 11 états.</p>
<p><img alt="Chronogramme des horloges et de la synchro du 8008." src="https://www.triceraprog.fr/images/202111/8008-Instruction-States.png"></p>
<h3>Broches d'entrée</h3>
<p>Le 8008 possède deux broches en entrée en plus des deux phases d'horloges : <code>INTERRUPT/</code> et <code>READY/</code>.</p>
<h4><code>READY/</code></h4>
<p>La broche READY/ permet de synchroniser le temps que prend l'acquisition d'une donnée avec le 8008. En effet, le cycle T3 s'attend à pouvoir lire une donnée sur les broches d'entrées/sorties. L'emplacement de cette donnée a été complètement caractérisée (emplacement et nature) lors du cycle T2.</p>
<p>Pour être certain que le monde extérieur est prêt, le 8008 ne passera de l'état T2 à T3 que si READY/ est au 1 logique. Dans le cas contraire, il se placera dans l'état WAIT et patientera... Dès que READY/ est signalé par le reste du système, le 8008 reprend sa course en passant vers l'état T3.</p>
<h4><code>INTERRUPT/</code></h4>
<p>La broche INTERRUPT/, comme son nom l'indique, place le 8008 dans un état d'interruption. La gestion des interruptions par le 8008 est très rudimentaire. Lorsqu'une instruction est terminée, si INTERRUPT/ est signalée, alors le processeur passe en état T1I plutôt que T1.</p>
<p>Et c'est tout !</p>
<p>C'est aussi le seul moyen pour le processeur de sortir de l'état STOPPED si une instruction <code>HLT</code> (halt) a été exécutée. Et de la même manière, le premier état sera alors T1I.</p>
<p>Comme indiquée plus haut, la différence entre l'état T1 et T1I est que pour T1, le registre PC est incrémenté après l'émission de l'adresse. Pas dans le cas de T1I. L'idée est pour le système de placer sur le bus lors d'un état T1I une instruction particulière, généralement <code>RST</code> (Restart).</p>
<p>L'instruction <code>RST</code> est une instruction d'un octet de taille qui permet de brancher sur une sous-routine (comme un <code>CAL</code>) à une des 8 adresses multiples de 8 en mémoire. Elle est faite pour être utilisée dans ce contexte.</p>
<p>Le 8008 doit donc être aidé par le reste du système pour gérer des interruptions. Le processeur lui-même ne fait que le strict minimum... </p>
<h3>Les broches d'entrées/sorties</h3>
<p>Il reste 8 broches à décrire, et celles-ci fonctionnent en entrée comme en sortie. Elles forment l'interface d'échange de données entre le 8008 et le reste du système. Ces broches ne sont pas spécialisées : toute donnée passe par là, que ce soit un morceau d'adresse, une données à échanger avec la mémoire ou avec un périphérique.</p>
<p>Comme indiqué plus haut, une combinaison de l'état courant du 8008 et des deux bits hauts placés en T2 sur ces broches permet de déterminer la fonction de ces 8 broches.</p>
<p>Sur T1 et T2, ce sont des broches en sortie qui indiquent deux morceaux d'adresse ainsi que la nature du cycle. Sauf dans le cas d'une données adressée à un périphérique, qui se trouvera en cycle T1 du second cycle.</p>
<p>En T3, ce sont des broches en entrée qui vont y lire une donnée... ou rien (dans le cas de <code>OUT</code> lors du second cycle).</p>
<p>Les broches ne servent pas lors des autres états.</p>
<h3>Les registres</h3>
<p>Le 8008 possède des registres accessibles par le code utilisateur, ainsi que quelques registres internes, non accessibles.</p>
<p>Passons sur la plupart des registres internes, qui servent au fonctionnement... interne (deux registres temporaires, celui d'instruction, de cycle,...) mais arrêtons nous sur une particularité : le pointeur d'instruction, généralement nommé <code>PC</code> sur les micro-processeurs, n'est pas accessible au code exécuté !</p>
<h3>Le PC</h3>
<p>En premier lieu, il faut dire que ce registre n'existe pas vraiment. Le 8008 possède une mémoire de 8 x 14 bits organisée en pile, munie de son pointeur de pile. L'adresse pointée est le <code>PC</code> actuel. Lors d'un branchement, l'adresse pointée est mise à jour, avec une mise à jour potentielle du pointeur dans le cas d'instruction d'appel de sous-routine (<code>CAL</code> par exemple).</p>
<p>Cela signifie au passage que le 8008, sans aide externe et avec ses seules instructions, ne peut pas dépasser une profondeur d'appel de plus de 8. Ou plutôt, il peut, mais la mémoire étant cyclique, il ne pourra pas <em>remonter</em> correctement.</p>
<p>Et le contenu de cette mémoire est parfaitement inaccessible. Il n'y a pas d'instruction permettant de lire le contenu de la pile, ni le niveau du pointeur.</p>
<h3>Les autres registres</h3>
<p>Les registres accessibles sont <code>A</code>, <code>B</code>, <code>C</code>, <code>D</code>, <code>E</code>, <code>H</code> et <code>L</code>. Rien de très étonnant si vous connaissez le <strong>Z80</strong> par exemple.</p>
<p><code>A</code> est l'accumulateur. Toutes les opérations logiques et arithmétiques se font par rapport à A, et le résultat potentiel est stocké dans A. Sauf pour les opérations d'incréments et décréments, qui ne peuvent être appliqués que sur les registres généraux qui suivent.</p>
<p><code>B</code>, <code>C</code>, <code>D</code> et <code>E</code> sont 4 registres généraux. On peut copier le contenu de n'importe lequel vers n'importe quel autre (y compris l'accumulateur). On peut même copier le contenu d'un registre vers lui-même. Et c'est d'ailleurs, par convention, la copie de A vers A (<code>LAA</code>) qui forme l'instruction <code>NOP</code> du 8008.</p>
<p><code>H</code> et <code>L</code> sont 2 autres registres généraux qui se comportent comme les précédents, mais qui ont une capacité supplémentaire. Pris ensemble, ils forme <code>HL</code>, nommé aussi <code>M</code>, un pseudo registre de pointeur vers la mémoire.</p>
<p>Ainsi, l'instruction <code>LAM</code> copie le contenu du pseudo registre <code>M</code> vers l'accumulateur. Autrement dit, l'octet pointé par la paire <code>HL</code> est chargée dans A.</p>
<h2>Mot de la fin.</h2>
<p>Le 8008, conçu et construit par Intel, est avant tout une commande pour reproduire dans un petit volume les fonctionnalités d'un appareil existant. C'est sous cet aspect qu'il est à comprendre et comprendre une partie des choix techniques et donc de ses limitations. </p>Récréation 3D, Pascaline2021-10-19T00:00:00+02:002021-10-19T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2021-10-19:/recreation-3d-pascaline.html<p>Depuis longtemps, très longtemps, les humains cherchent des moyens pour aider aux calculs. Blaise Pascal fut de ceux-ci, lorsqu'il conçu un modèle fonctionnel d'une machine mécanique permettant d'additionner et de soustraire des nombres.</p>
<p>La Pascaline devient la première machine à calculer de bureau commercialisée (même si elle le fut à …</p><p>Depuis longtemps, très longtemps, les humains cherchent des moyens pour aider aux calculs. Blaise Pascal fut de ceux-ci, lorsqu'il conçu un modèle fonctionnel d'une machine mécanique permettant d'additionner et de soustraire des nombres.</p>
<p>La Pascaline devient la première machine à calculer de bureau commercialisée (même si elle le fut à très peu d'exemplaires).</p>
<p>En voici une modélisation que j'ai terminée récemment.</p>
<p><img alt="une Pascaline" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202110/Render-Pascaline-002-750-sepia.jpeg"></p>VG5000µ, Schémas de principe mis à jour en v1.42021-04-29T00:00:00+02:002021-04-29T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2021-04-29:/vg5000m-schemas-de-principe-mis-a-jour-en-v14.html<p>Il y a deux ans et demi, <a href="https://www.triceraprog.fr/vg5000m-schemas-de-principe.html">je publiais ici</a> une remise au propre du <strong>schéma de principe</strong> de la documentation du VG5000µ.</p>
<p>Cette première version a été mise à jour avec la découvertes d'erreurs, ou de précisions à apporter.</p>
<p>Cette fois-ci, c'est l'œil exercé de <em>6502man</em> du forum <strong>system-cfg …</strong></p><p>Il y a deux ans et demi, <a href="https://www.triceraprog.fr/vg5000m-schemas-de-principe.html">je publiais ici</a> une remise au propre du <strong>schéma de principe</strong> de la documentation du VG5000µ.</p>
<p>Cette première version a été mise à jour avec la découvertes d'erreurs, ou de précisions à apporter.</p>
<p>Cette fois-ci, c'est l'œil exercé de <em>6502man</em> du forum <strong>system-cfg</strong> qui, lors de la réparation d'un VG5000µ, m'a fait parvenir des corrections. Et je le remercie ici à nouveau.</p>
<p>Les modifications par rapport à la v1.3 sont :</p>
<ul>
<li>retirer la broche <code>A14</code> de la ROM <code>7802</code> et les broches <code>A13</code> des deux RAM en accès direct par le Z80, <code>7804</code> et <code>7805</code>. Du copier coller raté très probablement.</li>
<li>correction d'un inversion des broches 26 et 28 sur la ROM <code>7802</code>. Le +5V arrive sur 28.</li>
<li>inversion des broches 4 et 5 de la porte NAND fournie par <code>7812</code>. Les broches indiquées sur les versions précédentes sont fidèles au schéma de la documentation, mais le relevé sur le matériel montre le contraire. Il est possible qu'il existe différente carte mère, mais je donne la priorité à un relevé vérifié. D'un point de vue logique, inverser les branches d'une porte NAND ne change rien.</li>
<li>ajout de la mention 4Mhz sur la sortie HP du VDP <code>7801</code>, pour plus d'information lors de la recherche de panne.</li>
</ul>
<p>Ce qui donne, mis à jour.</p>
<h4>La platine principale</h4>
<p>Image cliquable pour une version en haute définition. (mise à jour 29 avril 2021)</p>
<p><a href="https://www.triceraprog.fr/images/202104/VG5000-Schema-v1.4.png"><img alt="Platine principale" src="https://www.triceraprog.fr/images/202104/VG5000-Schema-v1.4-750.png"></a></p>
<h4>La platine K7/Son</h4>
<p>Image cliquable pour une version en haute définition. (mise à jour 9 sept. 2018)</p>
<p><a href="https://www.triceraprog.fr/images/201809/VG5000-Schema-k7-v1.2.png"><img alt="Platine K7/Son" src="https://www.triceraprog.fr/images/201809/VG5000-Schema-k7-v1.2-750.png"></a></p>
<h4>Note</h4>
<p>Le schéma a été mis à jour dans <a href="https://www.triceraprog.fr/vg5000m-schemas-de-principe-mis-a-jour-en-v15.html">un nouvel article</a>.</p>Programmer sur Nintendo Switch en BASIC2021-03-28T00:00:00+01:002021-03-28T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2021-03-28:/programmer-sur-nintendo-switch-en-basic.html<p>« <strong>Petit Computer</strong> » est un environnement de programmation qui est apparu initialement sur la Nintendo DSi. Cet environnement se programme dans un dialecte de <strong>BASIC</strong> du nom de « <strong>Smile BASIC</strong> ». Et c'est sous le nom « Smile BASIC v4 » qu'il est disponible en version numérique sur l'eShop de la <strong>Nintendo Switch</strong>.</p>
<p>Après …</p><p>« <strong>Petit Computer</strong> » est un environnement de programmation qui est apparu initialement sur la Nintendo DSi. Cet environnement se programme dans un dialecte de <strong>BASIC</strong> du nom de « <strong>Smile BASIC</strong> ». Et c'est sous le nom « Smile BASIC v4 » qu'il est disponible en version numérique sur l'eShop de la <strong>Nintendo Switch</strong>.</p>
<p>Après un lancement un peu chaotique en Europe au printemps 2020, à cause d'une erreur dans la classification d'âge il semblerait, il a été à nouveau disponible. Une fois téléchargé et après avoir branché un clavier et une souris en USB sur la Switch, la console se transforme en <strong>ordinateur programmable en BASIC</strong>. Et c'est amusant !</p>
<h3>Un environnement presque à l'ancienne...</h3>
<p>Au <strong>démarrage</strong>, on est pris en main avec des explications sur l'environnement. Les explications ne sont pas très poussées, et même après avoir parcouru le <strong>très bavard tutoriel</strong>, je pense que des apprentis programmeurs resteront un peu sur leur faim.</p>
<p>Ah, une autre précision : le logiciel n'existe qu'en anglais et en japonais.</p>
<p>Niveau documentation, le site officiel donne une <strong>référence plutôt complète</strong>, et trois <em>PDF</em>s d'explications parcellaires qui donnent quelques idées du fonctionnement.</p>
<p>Très pratique cependant, l'<strong>aide en ligne</strong> est disponible et est très complète. <code>F1</code> après un mot clé donne les explications. <code>F1</code> sans mot clé amène au manuel général.</p>
<p>Malgré tout cela, au début, on retrouve un peu la sensation d'une machine à l'ancienne : il semble y avoir plein de possibilités, mais il va falloir les découvrir en tâtonnant, et faisant des essais. En ce sens, « <strong>Smile BASIC</strong> » est ludique.</p>
<h3>Cloud et Partage</h3>
<p>Moyennant l'achat d'un « <strong>ticket</strong> » <strong>supplémentaire</strong> à l'achat du logiciel, on peut sauvegarder et surtout partager ses créations sur un serveur dédié à la communauté Smile BASIC. Et puisqu'il y a partage, il est aussi possible de <strong>télécharger les créations</strong> des autres, pour s'en inspirer, <strong>les modifier</strong> ou tout simplement y <strong>jouer</strong>.</p>
<p>Sans le ticket, on ne pourra charger qu'une seule création toutes les huit heures, et pas partager les siennes.</p>
<p>Par contre, pas moyen d'échanger des données avec le monde extérieur, comme un PC. Ou du moins pas simplement. Ce qui est créé dans Smile BASIC reste dans Smile BASIC. Cela inclus le code BASIC tout autant que les musiques, sprites,...</p>
<h3>Ça ressemble à quoi ?</h3>
<p>Lorsque l'on passe en mode « création », on arrive sur un écran qui ne dépayse pas lorsque l'on est habitué de vieilles machines.</p>
<p>La résolution de l'écran est de 400 par 240, mais cela peut se changer jusqu'à du 720p.</p>
<p><em>Note: mes captures bavent à cause de mon périphérique d'acquisition, ce n'est pas le cas de l'image en sortie de la Switch, qui est très bonne.</em></p>
<p><img alt="Le mode direct de Smile Basic 4" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202103/20210327-SmileBasic-Direct.png"></p>
<p>En <strong>mode direct</strong>, on peut taper des commandes et voir le résultat. Il est possible de dessiner grâce à des primitives telles que point, ligne, cercle, rectangle ou triangle. Le système propose aussi un système de <strong>sprites</strong> avec collisions possibles et de la composition de plans d'affichages.</p>
<p>Cela peut commencer très simple, mais être poussé assez loin.</p>
<p><img alt="Commandes de dessin avec Smile Basic 4" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202103/20210327-SmileBasic-Graphics.png"></p>
<p>Et pour créer ces sprites, ou n'importe quel dessin, un <strong>logiciel</strong> de <strong>pixel art</strong> est disponible sur <code>F10</code>.</p>
<p><img alt="Editeur graphique Smile Basic 4" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202103/20210327-SmileBasic-Gahaku.png"></p>
<p>La programmation est sans numéro de ligne. On bascule dans un <strong>éditeur de texte</strong> pour entrer un programme résident. Il est possible de sauver les fichiers bien entendu, et d'en rappeler d'autres. Le tout, comme je le mentionnais, avec une aide en ligne.</p>
<p><img alt="L'aide en ligne Smile Basic 4" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202103/20210327-SmileBasic-Help.png"></p>
<h3>Conclusion pour le moment</h3>
<p>« Smile BASIC » est plutôt <strong>sympathique</strong>. On est un peu perdu en arrivant dessus la première fois, mais à force d'essais, de lectures de la référence et de l'aide en ligne, on peut commencer à faire des petites choses sympa.</p>
<p>Le système, bien qu'assez complet, n'est pas là pour pousser la Switch à fond, ce n'est pas le but. L'idée est plutôt de s'amuser à programmer dans un environnement simple, avec des outils et ressources clés en main (il y a aussi des effets sonores et des musiques disponibles de base).</p>
<p>La communauté à l'air assez réduite hors Japon cependant, c'est un peu dommage, mais assez compréhensible.</p>
<p><img alt="Un petit programme" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202103/20210327-SmileBasic-Program.png"></p>Automatisation : utilisation de Visual Studio Code2020-12-27T00:00:00+01:002020-12-27T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2020-12-27:/automatisation-utilisation-de-visual-studio-code.html<p>Il y a <a href="https://www.triceraprog.fr/automatisation-utilisation-de-sublime-text-3.html">presque trois ans</a> (déjà !), j'avais mis en place un <strong>environnement de programmation</strong> pour me permettre de mettre au point un programme en assembleur <strong>Z80</strong> et de l'envoyer vers <strong>MAME</strong>, afin de réduire le nombre d'opérations manuelles. Le tout à partir de <strong>Sublime Text 3</strong>.</p>
<p>Peut-être parce que …</p><p>Il y a <a href="https://www.triceraprog.fr/automatisation-utilisation-de-sublime-text-3.html">presque trois ans</a> (déjà !), j'avais mis en place un <strong>environnement de programmation</strong> pour me permettre de mettre au point un programme en assembleur <strong>Z80</strong> et de l'envoyer vers <strong>MAME</strong>, afin de réduire le nombre d'opérations manuelles. Le tout à partir de <strong>Sublime Text 3</strong>.</p>
<p>Peut-être parce que la documentation de cet éditeur n'est pas des plus détaillée, ou peut-être parce que c'est avant tout un éditeur de texte, les extensions autour de l'assembleur Z80 sont peu nombreuses. C'est plutôt du côté de <strong>Visual Studio Code</strong> que ces extensions sont apparues.</p>
<p>Récemment, j'ai donc fait deux changements dans ma chaîne de mise au point pour <strong>VG5000µ</strong>. Tout d'abord, j'utilise à présent <strong>Visual Studio Code</strong> comme éditeur, et ensuite, j'ai changé d'assembleur.</p>
<h2>Visual Studio Code</h2>
<p>Tout comme Sublime Text 3, l'important pour moi est que l'éditeur puisse fonctionner sur diverses plateformes, et entre autre sur celle que j'utilise pour mes projets rétro : <strong>Ubuntu Linux</strong>. De ce côté, c'est ok.</p>
<p>Pour la syntaxe colorée, j'utilise le plugin <strong>Z80 Macro-Assembleur</strong>. Le plugin apporte aussi un « problem matcher », qui est la façon pour l'éditeur de savoir si des erreurs ont été levées lors de phase de construction.</p>
<p>Il offre aussi un support d'<strong>Intellisense</strong>, mais que je n'ai pas encore mis en fonctionnement. A priori, ça ne fonctionne pas tout seul.</p>
<p>J'ai aussi ajouté <strong>Z80 Assembly meter</strong> qui permet d'évaluer le nombre de cycles pris par du code sélectionné, avec une option <code>MSX</code> qui allonge d'un cycle les instructions, ce qui est aussi valable pour le <strong>VG5000µ</strong>.</p>
<h2>L'assembleur</h2>
<p><strong>Auparavant</strong>, j'utilisais l'assembleur <strong>z80asm</strong> livré avec l'environnement <strong>z88dk</strong>. C'était un choix historique venant de mes essais en C sur le Z80. Cet assembleur fonctionne bien, mais est très minimaliste. En effet, un assembleur derrière un compilateur n'a pas besoin de beaucoup d'aides, le compilateur pouvant générer le code in extenso.</p>
<p>Lorsque l'on met au point du code à la main, rapidement, pouvoir écrire des macros, manipuler des adresses via des expressions, devient un outil nécessaire.</p>
<p>C'est vers <strong>sjasmplus</strong> que je me suis tourné. Comme souvent, l'assembleur est fait avec une machine particulière en tête, ici la ligne des <strong>Spectrum</strong>. Mais ce n'est pas bien grave. L'assembleur a des <strong>macros</strong>, est assez <strong>souple</strong> sur la syntaxe, a pas mal d'<strong>instructions</strong> pour déclarer ce que l'on veut faire.</p>
<h2>La tuyauterie</h2>
<p>Voici le fichier <code>tasks.json</code> que j'ai écris. Il est probablement perfectible car je ne connais pas toutes les subtilités de <strong>Visual Studio Code</strong>, mais il fait l'affaire pour le moment.</p>
<p>Pour le fonctionnement du script <code>vgboot.lua</code>, je vous renvois à <a href="https://www.triceraprog.fr/injecter-un-programme-dans-un-emulateur-vg5000m-partie-ii.html">cet article</a>. Le script n'a pas bougé depuis.</p>
<div class="highlight"><pre><span></span><code> "version": "2.0.0",
"tasks": [
{
"label": "sjasmplus - Build",
"type": "shell",
// Options: No fake instructions, Warning as Errors, Multi arg is ',,'
"command": "sjasmplus --syntax=FLwa <span class="cp">${</span><span class="n">fileBasename</span><span class="cp">}</span> --raw=<span class="cp">${</span><span class="n">fileBasenameNoExtension</span><span class="cp">}</span>.bin --lst",
"problemMatcher": [
"<span class="nv">$errmatcher</span>-sjasmplus"
],
"group": {
"kind": "build",
"isDefault": true
},
"options": {
"cwd": "<span class="cp">${</span><span class="n">relativeFileDirname</span><span class="cp">}</span>"
}
},
{
"label": "sjasmplus - Run on Mame",
"type": "shell",
"command": "mame64 vg5k -ramsize 48k -nomax -window -autoboot_delay 0 -autoboot_script vgboot.lua -debug -debugger none -natural",
"problemMatcher": [
"<span class="nv">$errmatcher</span>-sjasmplus"
],
"dependsOn": [
"sjasmplus - Build"
],
"options": {
"cwd": "<span class="cp">${</span><span class="n">relativeFileDirname</span><span class="cp">}</span>"
}
},
{
"label": "sjasmplus - Debug on Mame",
"type": "shell",
"command": "mame64 vg5k -ramsize 48k -nomax -window -autoboot_delay 0 -autoboot_script vgboot.lua -debug -debugger qt -natural",
"problemMatcher": [
"<span class="nv">$errmatcher</span>-sjasmplus"
],
"dependsOn": [
"sjasmplus - Build"
],
"options": {
"cwd": "<span class="cp">${</span><span class="n">relativeFileDirname</span><span class="cp">}</span>"
}
}
]
}
</code></pre></div>
<h2>La suite</h2>
<p>Les environnements pour des machines connues vont encore plus loin, avec par exemple de l'intégration de debugger directement dans <strong>Visual Studio Code</strong>. Il y a aussi un support de tests unitaires, qui me plaît bien. Reste qu'il faut faire quelques branchements pour que cela fonctionne pour un VG5000µ. Je ne sais pas encore si j'irai vers là.</p>VG5000µ, Set Point assembleur depuis le BASIC2020-12-16T00:00:00+01:002020-12-16T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2020-12-16:/vg5000m-set-point-assembleur-depuis-le-basic.html<p>Cet article est la suite de deux précédents articles. Le <strong>premier</strong>, en plusieurs parties, était l'implémentation de <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-afficher-le-point.html">l'affichage d'un « gros pixel »</a> sur l'écran du VG5000µ. Le <strong>second</strong> était celui sur la possibilité (<a href="https://www.triceraprog.fr/vg5000m-ajouter-des-instructions-au-basic.html">ou plutôt la difficulté</a>) d'ajouter des commandes au BASIC du VG5000µ.</p>
<p>Il existe cependant une façon d'ajouter des …</p><p>Cet article est la suite de deux précédents articles. Le <strong>premier</strong>, en plusieurs parties, était l'implémentation de <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-afficher-le-point.html">l'affichage d'un « gros pixel »</a> sur l'écran du VG5000µ. Le <strong>second</strong> était celui sur la possibilité (<a href="https://www.triceraprog.fr/vg5000m-ajouter-des-instructions-au-basic.html">ou plutôt la difficulté</a>) d'ajouter des commandes au BASIC du VG5000µ.</p>
<p>Il existe cependant une façon d'ajouter des commandes au BASIC... ou presque. Cette possibilité est évoquée brièvement dans le livre <strong>« Clefs pour VG5000 »</strong> page 98. À charge au lecteur de se débrouiller.</p>
<p>Cette méthode, indiquée dans le livre, est à vrai dire celle qui est utilisée par l'implémentation BASIC de la machine. Chaque routine d'instruction démarre avec <code>HL</code> qui pointe dans le buffer du texte BASIC sur l'octet suivant le token d'instruction, et donc sur ses éventuels paramètres.</p>
<p>C'est donc le cas, comme les autres, de l'instruction <code>CALL</code>. La routine de l'instruction se charge de lire l'adresse puis de l'appeler. Par lecture de ce paramètre, <code>HL</code> a été positionné après celui-ci et c'est donc dans cet état que la routine utilisateur est appelée.</p>
<h2>Exemple</h2>
<p>Voici un exemple rapide d'une commande qui peut s'appeler par <code>CALL &"7000",X,Y</code>, X et Y étant les coordonnées du point à afficher. La routine <code>setpoint</code> est celle de l'article cité au début.</p>
<p>L'idée derrière le premier <code>jp</code> est de commencer par une série de <code>jp</code> qui amène aux différentes routines qui seront donc <code>CALL &"7000"</code>, <code>CALL &"7003"</code>, <code>CALL &"7006"</code>,...</p>
<p>On peut même aller jusqu'à mettre l'adresse dans une variable pour obtenir quelque chose comme <code>CALL SP,X,Y</code> (les variables ont deux caractères maximum significatif sur VG5000µ).</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">chkchr</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">8</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">getbyt</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">0086</span><span class="w"></span>
<span class="w"> </span><span class="k">org</span><span class="w"> </span><span class="kc">$</span><span class="mi">7000</span><span class="w"></span>
<span class="nl">entry:</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">call_setpoint</span><span class="w"></span>
<span class="nl">call_setpoint:</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">bc</span><span class="w"> </span><span class="c1">; Sauvegarde des registres, sauf HL, dont on a besoin</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">chkchr</span><span class="w"> </span><span class="c1">; Il doit y avoir une virgule, ou c'est une erreur de syntaxe</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="s">','</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">getbyt</span><span class="w"> </span><span class="c1">; Puis une expression 8 bits entière pour la première coordonnée X dans A et (D)E</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">de</span><span class="w"> </span><span class="c1">; Sauve cette valeur sur la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">chkchr</span><span class="w"> </span><span class="c1">; Il doit y avoir une virgule, ou c'est une erreur de syntaxe</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="s">','</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">getbyt</span><span class="w"> </span><span class="c1">; Puis une expression 8 bits entière pour la seconde coordonnée Y dans A et (D)E</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="p">(</span><span class="nb">sp</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; Récupère la coordonnée X (dans L) en échange du pointeur sur le texte BASIC</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,</span><span class="w"> </span><span class="nv">a</span><span class="w"> </span><span class="c1">; Place la coordonnée Y dans H</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">setpoint</span><span class="w"> </span><span class="c1">; Appel l'affichage du point avec les coordonnées dans HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; Restaure les registres, dont HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<h2>D'autres choses ?</h2>
<p>Le livre indique aussi la possibilité d'utiliser <code>deint ($0083)</code>. Ce n'est cependant pas possible directement. Cette fonction prend ce qui est dans l'accumulateur flottant et le place en tant qu'entier dans <code>DE</code>. Mais il faut donc au préalable charger l'accumulateur flottant.</p>
<p>C'est possible avec <code>eval_num_ex ($284d)</code>, qui doit donc être appelé au préalable, car c'est cette routine qui analyse l'expression et vérifie qu'elle est numérique.</p>
<h2>Conclusion</h2>
<p>Grâce à cette méthode, il est donc possible de créer une petite bibliothèques de routines assembleurs appelable avec des paramètres depuis le BASIC.</p>
<p><img alt="Affichage des résultats de tests dans MAME" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202012/VG5000-Square-SetPoint-ASM.png"></p>VG5000µ, le commentaire de la ROM2020-12-12T00:00:00+01:002020-12-12T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2020-12-12:/vg5000m-le-commentaire-de-la-rom.html<p>Le 8 avril 2017, un message sur le forum <a href="https://forum.system-cfg.com/viewtopic.php?f=1&t=7979">system-cfg</a> posait la question de l'existence d'un <strong>commentaire extensif</strong> de la <strong>ROM</strong> du VG5000µ. J'y répondais que j'avais quelques notes.</p>
<p>Depuis, sur ce site, j'ai <strong>décortiqué</strong> un certain nombre de parties, par curiosité personnelle, ou pour répondre à des questions qui …</p><p>Le 8 avril 2017, un message sur le forum <a href="https://forum.system-cfg.com/viewtopic.php?f=1&t=7979">system-cfg</a> posait la question de l'existence d'un <strong>commentaire extensif</strong> de la <strong>ROM</strong> du VG5000µ. J'y répondais que j'avais quelques notes.</p>
<p>Depuis, sur ce site, j'ai <strong>décortiqué</strong> un certain nombre de parties, par curiosité personnelle, ou pour répondre à des questions qui se posaient sur le même forum. Au fur et à mesure, je me suis embarqué dans le commentaire exhaustif de la ROM.</p>
<h2>Le commentaire</h2>
<p>Ce fut long, <strong>plutôt long</strong>.</p>
<p>Et je <strong>publie</strong> le résultat <strong>aujourd'hui</strong>. Ce résultat prend la forme principale de deux fichiers de commentaires, l'un pour la <strong>ROM 1.0</strong> et l'autre pour la <strong>ROM 1.1</strong>. Le tout est disponible <a href="https://github.com/Triceraprog/vg5000_rom_comments">ici sur GitHub</a>.</p>
<p>Ce que je ne publie pas aujourd'hui est l'outil qui permet de prend la ROM d'un côté, le fichier de commentaires de l'autre et qui génère un listing assembleur commenté qui peut-être assemblé à nouveau. Cet outil, que j'ai développé en parallèle, nécessite un bon coup de nettoyage avant publication. Peut-être que je le publierai avant son nettoyage complet, mais je dois au moins effectuer une relecture de sécurité.</p>
<h2>Terminé ?</h2>
<p>Les commentaires ayant été écrits sur une durée de <strong>trois ans</strong>, et malgré une passe de <strong>relecture</strong> récente, il reste probablement des <strong>incohérences</strong> de nommage, et peut-être aussi quelques fautes de frappe ou d'orthographe par-ci par-là. Il est tout à fait possible de me les mentionner.</p>
<p>Dans le futur, en fonction d'autres développement autour du <strong>VG5000µ</strong>, il est possible que je revienne dessus et que je corrige ou que j'augmente les informations.</p>
<p>Il y a aussi parfois des noms d'<strong>articles entre crochets</strong>. Il s'agit d'articles que j'ai prévu de faire, s'ils ne le sont pas déjà, sur ce site. On verra...</p>
<h2>C'est en français !</h2>
<p>Oui. C'est en <strong>français</strong>. Comme la machine. J'ai hésite à taper les commentaires en anglais ou en français. Au final, je suis parti pour le français en essayant de ne pas utiliser trop d'anglicismes, à part les évidents du domaine.</p>
<p>Si quelqu'un veut se lancer dans une traduction dans une autre langue, la licence est « Attribution - Partage dans les Mêmes Conditions 4.0 International » (CC BY-SA 4.0). Allez-y !</p>VG5000µ, Schémas de principe mis à jour2020-11-17T00:00:00+01:002020-11-17T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2020-11-17:/vg5000m-schemas-de-principe-mis-a-jour.html<p>Il y a deux ans, <a href="https://www.triceraprog.fr/vg5000m-schemas-de-principe.html">je publiais ici</a> une remise au propre du <strong>schéma de principe</strong> de la documentation du VG5000µ.</p>
<p>Alors que je finalise le commentaire de la ROM, je me suis aperçu qu'il y avait une erreur au niveau du décodage des entrées sorties. C'est à présent corrigé …</p><p>Il y a deux ans, <a href="https://www.triceraprog.fr/vg5000m-schemas-de-principe.html">je publiais ici</a> une remise au propre du <strong>schéma de principe</strong> de la documentation du VG5000µ.</p>
<p>Alors que je finalise le commentaire de la ROM, je me suis aperçu qu'il y avait une erreur au niveau du décodage des entrées sorties. C'est à présent corrigé (ainsi que sur l'article original).</p>
<p>Au niveau de <strong>7807</strong>, j'avais inversé les entrées <code>\RD</code> et <code>\WR</code>, ce qui n'avait pas de sens pour l'entrée <code>C</code> du <strong>74LS138</strong>.</p>
<p>J'en ai profité pour améliorer la nomenclature des signaux au niveau de ce même composant, en cohérence avec le datasheet.</p>
<h4>La platine principale</h4>
<p>Image cliquable pour une version en haute définition. (mise à jour 17 nov. 2020)</p>
<p><a href="https://www.triceraprog.fr/images/202011/VG5000-Schema-v1.3.png"><img alt="Platine principale" src="https://www.triceraprog.fr/images/202011/VG5000-Schema-v1.3-750.png"></a></p>
<h4>La platine K7/Son</h4>
<p>Image cliquable pour une version en haute définition. (mise à jour 9 sept. 2018)</p>
<p><a href="https://www.triceraprog.fr/images/201809/VG5000-Schema-k7-v1.2.png"><img alt="Platine K7/Son" src="https://www.triceraprog.fr/images/201809/VG5000-Schema-k7-v1.2-750.png"></a></p>
<h4>Note</h4>
<p>Le schéma a été mis à jour dans <a href="https://www.triceraprog.fr/vg5000m-schemas-de-principe-mis-a-jour-en-v15.html">un nouvel article</a>.</p>VG5000µ, deux mises à jour sur MAME2020-09-06T00:00:00+02:002020-09-06T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2020-09-06:/vg5000m-deux-mises-a-jour-sur-mame.html<p>Lors du commentaire systématique de la ROM du <strong>VG5000µ</strong>, je suis arrivé sur les routines de <strong>lecture</strong> et <strong>écriture</strong> sur <strong>cassette</strong>. Comme d'habitude, afin de vérifier le fonctionnement de la machine, je fais des tests. Si je fais parfois des tests sur le matériel réel, la plupart du temps, un …</p><p>Lors du commentaire systématique de la ROM du <strong>VG5000µ</strong>, je suis arrivé sur les routines de <strong>lecture</strong> et <strong>écriture</strong> sur <strong>cassette</strong>. Comme d'habitude, afin de vérifier le fonctionnement de la machine, je fais des tests. Si je fais parfois des tests sur le matériel réel, la plupart du temps, un test sur émulateur suffit, voire est beaucoup plus simple, permettant de dérouler une routine et la suivre avec des données bien choisies.</p>
<p>J'utilise essentiellement <a href="https://github.com/mamedev/mame"><strong>MAME</strong></a> pour cela, qui est muni d'un debuggeur qui répond à mes attentes. J'utilise parfois <a href="http://dcvg5k.free.fr/"><strong>dcvg5k</strong></a>, qui est plus orienté sur une utilisation de la machine simple et pratique.</p>
<p>Cependant, pour la cassette, <strong>aucun des deux</strong> ne convenait. Les deux émulateurs ne savent lire que le format <code>K7</code>, qui a le mérite d'être <strong>extrêmement simple</strong> et <strong>facilement lisible</strong>, mais qui a l'inconvénient d'être inadéquat au bon déroulement des routines de lecture et d'écriture.</p>
<p>La format <code>K7</code> est une simple liste des octets décodés depuis un enregistrement réel. C'est un format <strong>pratique</strong> car <strong>très compact</strong>. L'émulateur <strong>dcvg5k</strong> s'en sert pour injecter ou extraire les valeurs en <strong>court-circuitant</strong> la routine de lecture et d'écriture de la <strong>ROM</strong>. Si j'ai bien compris, l'émulateur intercepte l'exécution normale à des endroits bien choisis et prend la main pour une lecture ou écriture très rapide.</p>
<p>C'est une idée qui se défend pour une utilisation de la machine en <strong>évitant</strong> des <strong>temps d'attentes</strong> associés aux cassettes.</p>
<p>Dans mon cas d'<strong>étude</strong> des routines, c'est par contre <strong>hors-jeu</strong>. J'ai besoin d'un signal audio. Je me suis donc tourné vers <strong>MAME</strong> pour voir si je pouvais ajouter le traitement d'un fichier <code>.WAV</code> avec ma ROM 1.1 non modifiée.</p>
<h2>Ajout de lecture/écriture audio</h2>
<p>La première opération a été d'ajouter le support du format <code>.WAV</code> sur <strong>MAME</strong>.</p>
<p>Comme on peut l'imaginer, la plupart des <strong>services de base</strong> existent déjà quelque part dans <strong>MAME</strong>. C'est l'avantage d'un tel mastodonte. Mais être un mastodonte à aussi un <strong>inconvénient</strong> : il faut savoir <strong>où</strong> ça se trouve et <strong>comment</strong> ça s'utilise.</p>
<p>Une fois trouvé, c'est assez simple. Dans le fichier <code>src/lib/formats/vg5k_cas.cpp</code>, à côté de la déclaration du support du type de fichier <code>K7</code>, il suffit d'ajouter la déclaration du support <code>.WAV</code>. Toute la gestion de fichier et de magnétophone virtuel est alors pris en charge.</p>
<div class="highlight"><pre><span></span><code><span class="n">CASSETTE_FORMATLIST_START</span><span class="p">(</span><span class="n">vg5k_cassette_formats</span><span class="p">)</span>
<span class="n">CASSETTE_FORMAT</span><span class="p">(</span><span class="n">vg5k_k7_format</span><span class="p">)</span>
<span class="n">CASSETTE_FORMAT</span><span class="p">(</span><span class="n">wavfile_format</span><span class="p">)</span>
<span class="n">CASSETTE_FORMATLIST_END</span>
</code></pre></div>
<h3>Premiers problèmes</h3>
<p>Je me doutais que cela n'allait pas fonctionner directement, puisque la <strong>machine</strong> était <strong>annotée</strong> comme <strong>ne fonctionnant pas</strong>, à cause d'un problème de lecture à 1200 bauds. J'avais comme deuxième objectif de corriger cela, afin de pouvoir analyser les routines complètement.</p>
<p>Avec la prise en charge du format <code>.WAV</code>, l'émulation réussi à lire un fichier audio sans soucis. Mais méfiance, la routine du <strong>VG5000µ</strong> se sert de l'amorce du fichier, une suite de signaux haut/bas, pour se calibrer.</p>
<p>Le deuxième test est donc de <strong>sauver</strong> un fichier <code>.WAV</code> puis de le <strong>recharger</strong>. Et là, <strong>cela ne fonctionne pas</strong>. Le fichier est bien sauvé, mais impossible à relire.</p>
<p>En <strong>examinant</strong> le fichier obtenu, je vois que les <strong>timings</strong> sont complètement <strong>farfelus</strong>. Ils ne correspondent pas à la théorie décrite dans le manuel technique, ils ne correspondent ni à 1200 bauds ni à 2400 bauds, et <strong>ils ne correspondent pas</strong> à la comparaison avec des fichiers écrits par du matériel réel.</p>
<h3>Le moteur démarre</h3>
<p>Au passage, les manipulations sous <strong>MAME</strong> sont pénibles, car la gestion du <strong>contrôle du moteur</strong> du magnétophone n'est pas là. Là encore, la plus grande partie du temps passé est de comprendre comment <strong>MAME</strong> gère le système. Au final, tout est là, il suffit de le brancher sur le bon signal envoyé sur le port I/O de la cassette.</p>
<p>Chose faite en modifiant la fonction <code>void vg5k_state::cassette_w(uint8_t data)</code> de <code>src/mame/drivers/vg5k.cpp</code> et en y ajoutant cette ligne :</p>
<div class="highlight"><pre><span></span><code><span class="n">m_cassette</span><span class="o">-></span><span class="n">change_state</span><span class="p">(</span><span class="n">BIT</span><span class="p">(</span><span class="n">data</span><span class="p">,</span><span class="w"> </span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">?</span><span class="w"> </span><span class="n">CASSETTE_MOTOR_ENABLED</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="n">CASSETTE_MOTOR_DISABLED</span><span class="w"> </span><span class="p">,</span><span class="w"> </span><span class="n">CASSETTE_MASK_MOTOR</span><span class="p">);</span><span class="w"></span>
</code></pre></div>
<p>J'en profite pour réécrire cette petite fonction pour faire ressortir un peu mieux la sémantique des données envoyées sur le port I/O.</p>
<p>À présent, les fonctions d'accès à la cassette (<code>CLOAD</code>, <code>CSAVE</code> et les autres) <strong>contrôlent le moteur</strong> simulé du lecteur dans <strong>MAME</strong>, est c'est bien plus pratique à utiliser !</p>
<h3>Le Z80 doit attendre</h3>
<p>Retour sur les <strong>problèmes de timings</strong>. Je vous évite tout le cheminement et les tests quand soudain me vient un doute. Est-ce que l'émulation tourne à la bonne vitesse ? Oui, la <strong>fréquence</strong> déclarée pour le <strong>Z80</strong> est la bonne (même si techniquement, elle devrait découler de celle du VDP), mais cela ne suffit pas !</p>
<p>En effet, le <strong>VG5000µ</strong> insère un état <code>WAIT</code> supplémentaire pendant sa phase M1 (fetch). Et ça change tout.</p>
<p>Un <strong>petit détour</strong> par le <strong>Z80</strong> ici. Lors de la phase M1 (opcode fetch), qui s'opère en 4 cycles d'horloge, le 2 premiers cycles placent l'adresse du <code>PC</code> sur le bus d'adresse puis signalent une requête de lecture mémoire. Lors du cycle 3 (<strong>T3</strong>), la valeur de l'instruction est lue depuis le bus de données. Les cycles <strong>T3</strong> et <strong>T4</strong> conjointement servent pour le rafraîchissement des mémoires dynamiques, laissons ça de côté.</p>
<p>Le <strong>Z80</strong> prévoit que le <strong>matériel</strong> puisse ne pas être prêt à temps pour <strong>livrer</strong> sur le <strong>bus de données</strong> l'instruction lue en cycle <strong>T3</strong>. La ligne <code>WAIT</code> est donc vérifiée à la fin de <strong>T2</strong>. Si la ligne est validée, alors le <strong>Z80</strong> passe en mode <code>WAIT</code> en ajoutant des cycles supplémentaires d'attente, jusqu'à ce que la ligne <code>WAIT</code> soit relâchée.</p>
<p>Et le driver <strong>MAME</strong> ne respecte pas ça.</p>
<p>Le <strong>VG5000µ</strong> tourne trop vite ! Les timings sont faux et l'écriture sur cassette ne fonctionne pas. Reste à savoir comment corriger ça.</p>
<h3>À la recherche du cycle en plus</h3>
<p>Une <strong>première piste</strong> est d'aller voir comment est gérée cette fonctionnalité dans l'émulation Z80 de <strong>MAME</strong>. Mauvaise nouvelle, <strong>elle ne l'est pas</strong>. Ou plutôt, la ligne <code>WAIT</code> est bien émulée, mais uniquement entre les instructions. Cela est bien assez nécessaire, il semblerait, pour l'émulation de son utilisation par des <strong>périphériques</strong> qui demandent au <strong>Z80</strong> d'attendre.</p>
<p>Mais la détection au cycle <strong>T2</strong> de la phase <strong>M1</strong> n'est pas là. C'est d'ailleurs indiqué dans les commentaires comme une amélioration possible... Que je ne me sens pas d'ajouter.</p>
<p><strong>Deuxième piste</strong> : allez voir ce que font les autres. Côté <strong>Amstrad CPC</strong> et <strong>MSX</strong>, les tables de timings d'opcode sont <strong>ajustées</strong>. Il semblerait qu'il y ait d'<strong>autres raisons</strong> que ce seul état d'attente, même si c'est une des raisons. Les <strong>réutiliser</strong> seraient une option... pourvu qu'elles soient utilisables, ce que je trouve un peu lourd à vérifier.</p>
<p>Reste que cela m'ennuie car ce n'est pas vraiment ce qu'il se passe dans la machine. Je fouille encore. Je trouve un driver qui annonce que faute d'avoir trouvé comment faire, la machine est trop rapide...</p>
<p>Et enfin, je trouve <strong>une solution</strong> qui <strong>me plaît</strong>, utilisée par un autre driver. Utiliser une fonction de rappel sur le rafraîchissement des mémoires dynamiques, qui, étonnamment, est émulée par <strong>MAME</strong>. Il suffit alors, dans cette fonction de rappel, de dire à l'émulateur du <strong>Z80</strong> qu'il devra exécuter un cycle de plus.</p>
<div class="highlight"><pre><span></span><code><span class="kt">void</span><span class="w"> </span><span class="nf">vg5k_state::z80_m1_w</span><span class="p">(</span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">data</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">m_maincpu</span><span class="o">-></span><span class="n">adjust_icount</span><span class="p">(</span><span class="mi">-1</span><span class="p">);</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<h3>Ça fonctionne !</h3>
<p>Et tout à coup, <strong>tous les timings cassette</strong> que je surveillais <strong>sont corrects</strong> ! La sauvegarde génère un fichier audio qui ressemble à quelque chose (même s'il est bien trop carré pour être pris pour un vrai signal sorti d'une machine réelle, mais ce n'est pas bien grave), et surtout, ce <strong>fichier est relu</strong> sans problème par l'émulateur.</p>
<p>Et ceci est à <strong>2400 bauds</strong> comme à <strong>1200 bauds</strong>.</p>
<p>Les fichiers sont aussi compris par l'utilitaire de transformation en format <code>K7</code> qui accompagne <strong>dcvg5k</strong>.</p>
<p>Des fichiers audio écrits depuis un vrai <strong>VG5000µ</strong> sont lus aussi. Il me reste le dernier test : lire sur du vrai matériel un fichier audio écrit par <strong>MAME</strong>. J'ai bon espoir que cela fonctionne, mais faute d'avoir effectué de vrais tests, le driver reste en <em>non fonctionnel</em>.</p>
<h2>Support de la touche DELTA</h2>
<p>Le <strong>VG5000µ</strong> possède sur son clavier cette touche non nommée qui est juste désignée par un <strong>triangle</strong> dans la documentation, connue aussi sous le nom de touche <strong>DELTA</strong>.</p>
<p>Puisque j'étais dans <strong>MAME</strong>, pourquoi pas <strong>ajouter cette fonctionnalité</strong> ? En effet, le <strong>soft reset</strong> qui consiste à remettre le <code>PC</code> à 0, ce que fait <strong>MAME</strong> par défaut, ne permet pas de simuler le soft reset tel qu'implémenté sur la machine à travers la touche <strong>DELTA</strong>.</p>
<p>Or, ce <strong>soft reset</strong> peut-être <strong>routé</strong> vers une <strong>routine utilisateur</strong> pour d'autres usages, comme par exemple, relancer directement un programme.</p>
<h3>Une touche à part</h3>
<p>Cette touche est à part dans sa connexion au système. Chaque touche du clavier forme une matrice qui est lue à travers le bus d'entrée/sortie du <strong>Z80</strong>. Mais pas celle-ci.</p>
<p>La touche <strong>DELTA</strong> est (presque) directement <strong>branchée</strong> à la ligne <code>NMI</code> du <strong>Z80</strong>. Autrement dit, appuyer sur la touche provoque une <strong>interruption non masquable</strong> dans le <strong>Z80</strong>. Par défaut, cette interruption appelle une routine qui vérifie si la touche <strong>CTRL</strong> est aussi appuyée et dans ce cas, provoque un soft reset. Tout ceci après avoir appelé une potentielle <strong>routine utilisateur</strong> qui, de base, ne fait rien.</p>
<p>Comme d'habitude, tout cela est bien entendu pris en charge par <strong>MAME</strong>, reste à savoir comment. Je suis allé voir du côté d'ancien matériels, où les <strong>switchs</strong> étaient branchés directement sur des fonctions, sans vraiment constituer un <em>clavier</em>.</p>
<p>La <strong>solution</strong> trouvée est la suivante. Tout d'abord, déclarer un nouveau port d'entrée/sortie qui associe au <strong>changement d'état</strong> d'une touche (j'ai choisi la touche <strong>End/Fin</strong> du clavier, mais c'est configurable par l'utilisateur) une <strong>fonction de rappel</strong>.</p>
<div class="highlight"><pre><span></span><code> <span class="n">PORT_START</span><span class="p">(</span><span class="s">"direct"</span><span class="p">)</span>
<span class="n">PORT_BIT</span><span class="p">(</span> <span class="mi">0</span><span class="n">x01</span><span class="p">,</span> <span class="n">IP_ACTIVE_LOW</span><span class="p">,</span> <span class="n">IPT_KEYBOARD</span><span class="p">)</span> <span class="n">PORT_CODE</span><span class="p">(</span><span class="n">KEYCODE_END</span><span class="p">)</span> <span class="n">PORT_NAME</span><span class="p">(</span><span class="s">"DELTA"</span><span class="p">)</span> <span class="n">PORT_CHANGED_MEMBER</span><span class="p">(</span><span class="n">DEVICE_SELF</span><span class="p">,</span> <span class="n">vg5k_state</span><span class="p">,</span> <span class="n">delta_button</span><span class="p">,</span> <span class="mi">0</span><span class="p">)</span>
</code></pre></div>
<p>La fonction de rappel signale tout simplement la ligne <code>NMI</code> du <strong>Z80</strong> pendant un bref instant :</p>
<div class="highlight"><pre><span></span><code><span class="n">INPUT_CHANGED_MEMBER</span><span class="p">(</span><span class="n">vg5k_state</span><span class="o">::</span><span class="n">delta_button</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="o">!</span><span class="n">newval</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">m_maincpu</span><span class="o">-></span><span class="n">pulse_input_line</span><span class="p">(</span><span class="n">INPUT_LINE_NMI</span><span class="p">,</span><span class="w"> </span><span class="n">attotime</span><span class="o">::</span><span class="n">zero</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Et voilà. La touche <strong>DELTA</strong> est émulée. On peut vérifier le fonctionnement des routines utilisateur sur <strong>CTRL+DELTA</strong>.</p>
<p><strong>Attention</strong>, elle ne l'est que lorsque le clavier est en more <em>réel</em> dans <strong>MAME</strong>, et non en mode <em>naturel</em>, ce dernier mode cherchant une correspondance <em>naturelle</em> entre le clavier de l'hôte et la machine émulée.</p>
<h2>C'est où ?</h2>
<p>Les <strong>deux patchs</strong> ont été <strong>intégrés</strong> dans la <strong>branche principale</strong> de <strong>MAME</strong>, c'est donc dès à présent disponible sur <a href="https://github.com/mamedev/mame">le dépôt</a>, ou bien dans une future version officielle de <strong>MAME</strong>.</p>VG5000µ, deux routines (quasi) identiques2020-08-16T00:00:00+02:002020-08-16T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2020-08-16:/vg5000m-deux-routines-quasi-identiques.html<p>Hier, en continuant le <strong>commentaire</strong> systématique de la <strong>ROM</strong> du <strong>VG5000µ</strong>, j'ai eu une impression de déjà-vu. Il me semblait bien avoir déjà commenté la partie « redéfinir un caractère étendu » (commandes <code>SETE</code> et <code>SETG</code> en BASIC).</p>
<p>L'impression persiste, voire s’accroît, au fur et à mesure et je me décide …</p><p>Hier, en continuant le <strong>commentaire</strong> systématique de la <strong>ROM</strong> du <strong>VG5000µ</strong>, j'ai eu une impression de déjà-vu. Il me semblait bien avoir déjà commenté la partie « redéfinir un caractère étendu » (commandes <code>SETE</code> et <code>SETG</code> en BASIC).</p>
<p>L'impression persiste, voire s’accroît, au fur et à mesure et je me décide à aller voir dans ce que j'ai déjà commenté. J'ai commencé, lentement, ce commentaire en août 2017, avec moult pauses, mais aussi du « butinage ». Il y a donc des parties que je ne me rappelle plus avoir déjà traitées.</p>
<p>Et effectivement, un peu plus loin, je vois des <strong>commentaires similaires</strong> à ceux que je suis en train d'écrire. Les termes sont un peu différents, car au fil des commentaires, j'ai fait évoluer certains termes ou la manière de commenter, mais en regardant de plus près, oui, c'est évident, il y a <strong>deux fois la même routine</strong> dans la ROM.</p>
<p>Je ne sais pas si c'est quelque chose de connu, alors voici mes commentaires.</p>
<h3>Le VDP</h3>
<p>Le <strong>Video Display Processor</strong> du VG5000µ, un <strong>EF9345</strong>, est utilisé par le BASIC de manière généralement indirecte. Les commandes comme les changement de couleur, de type de caractères ou encore de position du curseur, modifient des <em>registres</em> internes en RAM. Ces <em>registres</em> sont juste des emplacements réservés dans la RAM principale, celle accessible au Z80, ils n'ont rien de spécial.</p>
<p>Ces registres se trouvent à partir de <code>$47fa</code> et pointés en tout temps par le registre IX.</p>
<p>Le BASIC maintient aussi une <strong>représentation de l'écran</strong>, en <code>$4000</code>, et afficher quelque chose en BASIC revient à modifier cette représentation d'écran, en se basant sur les registres BASIC.</p>
<p>À chaque <strong>rafraîchissement</strong> demandé par le VDP, via l'IRQ du Z80 et si le BASIC considère qu'il est temps de mettre à jour l'affichage, un <strong>transfert des données</strong> est fait vers le VDP.</p>
<p>C'est le fonctionnement de base.</p>
<p>Cependant, la ROM contient aussi des routines qui s'adressent directement au VDP. On peut <strong>envoyer un caractère</strong> dans la mémoire écran du VDP sans passer par la représentation maintenue par le BASIC. Bien évidemment, si on laisse faire le rafraîchissement du BASIC, cette donnée sera écrasée rapidement. On peut aussi <strong>lire un caractère</strong> depuis la mémoire écran du VDP.</p>
<p>Ces quatre routines, <code>putahl</code>, <code>putici</code>, <code>getahl</code> et <code>getici</code> ne sont <strong>jamais utilisées</strong> par la ROM. Ce sont des routines mises à disposition pour l'utilisateur à une adresse fixe, dont le seul code est un branchement à l'implémentation.</p>
<p>Une autre routine mise à disposition est <code>setext</code>, qui s'occupe de la <strong>redéfinition d'un caractère</strong> texte ou graphique dans le VDP. Le VDP est en effet configuré par défaut pour offrir 4 « polices de caractères », dont 2 en ROM, et 2 en RAM (celle accessible directement par le VDP). Les deux en RAM peuvent être modifiées via cette routine <code>setext</code> en fournissant le numéro du caractère à changer et 10 octets qui représentent les 10 lignes d'affichage du caractère.</p>
<p><code>setext</code> se trouve en <code>$001b</code> et branche immédiatement sur son implémentation en <code>$0d85</code>.</p>
<h3>setext vs. SETE(T/G)</h3>
<p>Le <strong>BASIC</strong> offre deux commandes pour redéfinir les caractères : <code>SETET</code>, pour <strong>redéfinir un caractère</strong> <em>texte</em>, et <code>SETEG</code>, pour <strong>redéfinir un caractère</strong> <em>graphique</em>. La différence entre caractère texte et graphique sort du périmètre de cet article.</p>
<p>Du point de vue de l'interpréteur BASIC, il n'y a qu'<strong>une seule commande</strong> <code>SETE</code>, qui vérifie si le caractère suivant est <code>T</code> ou <code>G</code>. On pourrait dire que la dernière lettre de la commande est vue par l'interpréteur comme son premier paramètre.</p>
<p>L'exécution de <code>SETE</code> se trouve en <code>$0ced</code> dans la ROM BASIC.</p>
<p>On pourrait s'attendre à ce que <code>SETE</code> utilise la routine <code>setext</code>. Ou bien que les deux routines aient une partie commune. <em>Ce n'est pas le cas</em>.</p>
<p>Des 141 octets de l'implémentation de <code>setext</code>, environ 100 (à la louche) sont strictement identiques dans <code>SETE</code>, routine qui elle pèse 160 octets (en incluant sa routine annexe, qu'elle est la seule à appeler).</p>
<h3>Différences</h3>
<p>Les deux routines font la même chose, dans le même ordre :</p>
<ul>
<li><strong>Couper le rafraîchissement</strong> de l'écran depuis sa représentation RAM,</li>
<li><strong>Déterminer</strong> si on veut un caractère graphique ou texte,</li>
<li>Récupérer le <strong>numéro du caractère</strong>,</li>
<li>Envoyer les <strong>commandes</strong> au VDP pour préparer la définition,</li>
<li>Envoyer les <strong>10 lignes</strong> au VDP,</li>
<li><strong>Rétablir le rafraîchissement</strong> de l'écran.</li>
</ul>
<p>Pour <code>setext</code>, c'est très simple, les informations sont dans le registre <code>A</code> pour le caractère et son type, et <code>HL</code> pointe vers les données.</p>
<p>Pour <code>SETE</code>, c'est un peu plus complexe. Le type de caractère est déterminé par la présence de <code>T</code> ou <code>G</code>, il y a une vérification de la validité du premier paramètre de la commande (numéro de caractère), puis la chaîne de caractères de description, suite de nombres hexadécimaux en ASCII, doit être décodée. C'est la raison pour laquelle la routine est plus longue.</p>
<p>Cependant, le <strong>corps de la routine</strong> qui envoie toutes les commandes est <strong>identique</strong>, à la récupération de la donnée prêt. Et bien que les deux routines utilisent bien les mêmes appels pour envoyer une commande au VDP ou attendre que celui-ci soit prêt à recevoir une commande, il doit bien y avoir une centaine d'octets pouvant être mis en commun.</p>
<h3>Pourquoi ?</h3>
<p>Je n'ai pas d'explication à la duplication de cette routine. Il reste un peu de place inutilisé dans la ROM, et peut être qu'il n'était plus nécessaire d'optimiser la place prise et que c'est juste resté « comme ça ».</p>
<p>Ce n'est pas, à mon avis, une question de performances avec <code>setext</code> qui serait plus rapide. Elle l'est, en effet, mais pourrait l'être encore plus. En effet, <code>setext</code> garde de <code>SETE</code> le fait de « retourner » chaque octet des descriptions de ligne pour les passer d'une visualisation humaine au format demandé par le VDP. La suite de commandes à envoyer pourraient aussi être rendue plus rapide à l'aide d'un buffer de commandes et l'utilisation de la routine <code>regst</code>.</p>
<h3>Amélioration ?</h3>
<p>Une amélioration possible de la ROM serait de <strong>réécrire ces deux routines</strong>. Il y aurait peut-être même moyen de profiter de la place gagnée pour caser deux commandes <code>PSET</code> et <code>PRESET</code>, qui font cruellement défaut sur le VG5000µ.</p>Considérations sur le langage LOGO2020-08-15T00:00:00+02:002020-08-15T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2020-08-15:/considerations-sur-le-langage-logo.html<p>Quand je vois passer des mentions du langage <strong>LOGO</strong>, elles sont généralement peu flatteuses. C'est compréhensible, la plupart des personnes qui se souviennent de ce langage de programmation y ont été exposées pendant leurs jeunes années d'études, lors du <strong>Plan Informatique pour Tous</strong>. Peu sont ceux qui ont creusé plus …</p><p>Quand je vois passer des mentions du langage <strong>LOGO</strong>, elles sont généralement peu flatteuses. C'est compréhensible, la plupart des personnes qui se souviennent de ce langage de programmation y ont été exposées pendant leurs jeunes années d'études, lors du <strong>Plan Informatique pour Tous</strong>. Peu sont ceux qui ont creusé plus tard ce qu'ils avaient découverts au moyen d'ordinateurs poussifs et de cours pas toujours maîtrisés par des enseignants pas toujours bien convaincus.</p>
<p>Mes quelques <strong>souvenirs</strong> de séances en salle informatiques sont plutôt sur des activités de manipulation du crayon optique sur « Colorpaint ». Je ne sais plus si j'ai fait du <strong>LOGO</strong> en classe, mais c'est probable. Je me souviens cependant avec netteté la rencontre avec un professeur, lors de la fréquentation d'un « <strong>club informatique</strong> » pendant des vacances, qui pour une raison ou une autre (mon intérêt enthousiaste à la programmation ?) m'a donné quelques cours de <strong>LOGO</strong>. Et lors d'une conversation dans laquelle j'avais du mentionner que je programmais en BASIC, il m'a dit cette phrase qui est restée gravée dans ma mémoire « <em>le LOGO est bien plus puissant que le BASIC</em> ».</p>
<p>Je me souviens que cette phrase m'avait <strong>intriguée</strong>, mais pas forcément <strong>convaincue</strong>. Et de retour chez moi, j'ai continué mes aventures en <strong>BASIC</strong>. J'ai croisé <strong>LOGO</strong> quelques autres fois, de loin, sans m'y attarder.</p>
<p>Ce n'est que beaucoup plus tard, quand j'ai commencé à faire de la programmation mon métier, que j'ai repensé à cette phrase. J'ai regardé ce qu'était LOGO de manière plus expérimentée et j'ai compris. J'ai compris la justesse de l'assertion. LOGO était bien autrement plus puissant que le BASIC. Mais d'une manière que je ne pouvais pas comprendre auparavant.</p>
<p>C'est que la notion de « <strong>puissance</strong> » appliquée à un langage, ainsi qu'à son environnement, revêt de nombreux aspects. Cela peut être la <strong>vitesse d'exécution</strong> du programme, la <strong>facilité d'écriture</strong>, les <strong>possibilités d'interactivité</strong> avec la machine, la diversité des <strong>structures</strong> de <strong>code</strong> et de <strong>données</strong>.</p>
<p>Sur des ordinateurs 8 bits, la <strong>vitesse d'exécution</strong> du BASIC l'emporte haut la main, même sur des BASIC interprétés comme ils l'étaient en majorité.</p>
<p>Sur la <strong>facilité d'écriture</strong>, le BASIC est aussi très simple. C'était son but initial. LOGO n'est pas particulièrement complexe à écrire, mais comporte quelques étrangetés syntaxiques et pièges dont je parlerai dans un autre article. Et LOGO est aussi un peu plus verbeux.</p>
<p>En <strong>possibilité d'interaction</strong> avec la machine, BASIC l'emporte probablement. Tous les LOGO ne se valent pas, mais l'orientation pédagogique sur les machines 8 bits poussaient les concepteurs à offrir essentiellement des manipulations de l'écran via la tortue, et quelques sons. BASIC, en tant que porte d'entrée de la machine, était souvent dotés des instructions nécessaires pour en démontrer ses capacités. Avec de notables exceptions cependant, mais ceci est une autre histoire.</p>
<p>Vient ensuite la <strong>structure de code et de données</strong>. Et c'est là que LOGO renverse BASIC d'une pichenette. Le BASIC de l'époque est très linéaire, son édition se fait encore par numéro de lignes, sans labels. La structuration est possible bien entendu, mais les outils sont maigres. Niveau structure de donnée, le BASIC connaît le tableau multidimensionnel de taille fixe... et c'est tout.</p>
<p>LOGO arrive avec ses <strong>fonctions</strong> qui supportent la <strong>récursivité</strong>, avec des <strong>contextes locaux</strong>, un <strong>nommage</strong> des procédures et une <strong>édition</strong> sans numéro de lignes, qui facilite les mises au point.</p>
<p>Côté données, LOGO connaît la liste. Et avec une <strong>liste dynamique</strong>, de nouveaux horizons s'étendent. Des listes de nombres, des listes de mots, des listes de listes, des listes de commandes à exécuter...</p>
<p>L'<strong>expressivité</strong> du langage est beaucoup plus intéressante, et les programmes plus <strong>concis</strong>, plus <strong>clairs</strong>. C'est en ce sens que LOGO était bien plus puissant.</p>
<p>Mais tout ceci nécessite de la puissance de calcul et de la mémoire que les machines familiales de l'époque n'ont pas. Et cette malheureuse empreinte qui va rester principalement : <em>on ne fait rien de bien sérieux avec LOGO</em>.</p>
<p>Pour donner un petit aperçu de LOGO et de son évolution, j'ai réalisé une petite <strong>vidéo</strong> (moins de 10 minutes) que vous pouvez voir ici.</p>
<div class="embed-yt text-center" data-video-id="1AeJQU_jibE" style="width: 560; height:315">
<div class="embed-yt-play"></div>
</div>
<p>(billet posté aussi sur <a href="https://www.puupuu.org/dotclear/index.php?post/2020/08/15/Consid%C3%A9rations-sur-le-langage-LOGO">mon blog perso</a>).</p>Récréation 3D, Cartouche Atari 8002020-06-08T00:00:00+02:002020-06-08T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2020-06-08:/recreation-3d-cartouche-atari-800.html<p>J'avais une cartouche « Atari Writer » qui traînait sur le bureau depuis quelques temps, et je voulais faire un exercice rapide de modélisation avec <a href="https://www.blender.org/">Blender</a>.</p>
<p>Cela donne cette image, assez simple, mais qui a été un bon exercice.</p>
<p><img alt="Tortue Jeulin T2" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202006/20200606-Cartouche-Atari-AtariWriter-750.png"></p>VG5000µ, nombres aléatoires2020-05-10T00:00:00+02:002020-05-10T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2020-05-10:/vg5000m-nombres-aleatoires.html<p>Comment donc les <strong>nombres aléatoires</strong> sont-ils générés sur un <strong>VG5000µ</strong>. C'est ce que je vous propose de suivre aujourd'hui en décortiquant le code.</p>
<p><strong>Afin de suivre</strong>, il est important de comprendre comment les nombres sont stockés sur VG5000µ, et je vous propose pour cela un petit détour par <a href="https://www.triceraprog.fr/vg5000m-traitement-des-nombres.html">cet article …</a></p><p>Comment donc les <strong>nombres aléatoires</strong> sont-ils générés sur un <strong>VG5000µ</strong>. C'est ce que je vous propose de suivre aujourd'hui en décortiquant le code.</p>
<p><strong>Afin de suivre</strong>, il est important de comprendre comment les nombres sont stockés sur VG5000µ, et je vous propose pour cela un petit détour par <a href="https://www.triceraprog.fr/vg5000m-traitement-des-nombres.html">cet article</a>.</p>
<p><strong>Petit rappel avant de commencer</strong> : un générateur de nombres <em>aléatoires</em> est une procédure qui émet une suite de nombres sur un intervalle, cette suite tentant d'avoir des propriétés intéressantes qui donnent l'<strong>illusion</strong> de l'aléatoire. La suite est cependant parfaitement définie, même si pas toujours simple à suivre, et c'est ce que nous allons voir par la suite.</p>
<h3>L'initialisation</h3>
<p>Tout commence très tôt pour le générateur de nombres aléatoires. Dès <strong>l’initialisation</strong> de la machine, une série de valeurs est copiée depuis la ROM vers les variables systèmes. Cela se passe en <code>$1071</code>, juste après l'initialisation de l'affichage.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">initvalues</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">bc</span><span class="p">,</span><span class="kc">$</span><span class="mi">0065</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">ramlow</span><span class="w"></span>
<span class="w"> </span><span class="nf">ldir</span><span class="w"></span>
</code></pre></div>
<p>Il y a donc 101 valeurs ($65) copiées depuis <code>initvalues</code> (<code>$1194</code>) vers <code>ramlow</code> ($4830). Parmi celles-ci, les suivantes sont copiés vers <code>$4844</code> et nous intéressent aujourd'hui.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">00</span><span class="p">,</span><span class="kc">$</span><span class="mi">00</span><span class="p">,</span><span class="kc">$</span><span class="mi">00</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">35</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">ca</span><span class="p">,</span><span class="kc">$</span><span class="mi">99</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">39</span><span class="p">,</span><span class="kc">$</span><span class="mi">1</span><span class="nv">c</span><span class="p">,</span><span class="kc">$</span><span class="mi">76</span><span class="p">,</span><span class="kc">$</span><span class="mi">98</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">22</span><span class="p">,</span><span class="kc">$</span><span class="mi">95</span><span class="p">,</span><span class="kc">$</span><span class="nv">b3</span><span class="p">,</span><span class="kc">$</span><span class="mi">98</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">0</span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">dd</span><span class="p">,</span><span class="kc">$</span><span class="mi">47</span><span class="p">,</span><span class="kc">$</span><span class="mi">98</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">53</span><span class="p">,</span><span class="kc">$</span><span class="nv">d1</span><span class="p">,</span><span class="kc">$</span><span class="mi">99</span><span class="p">,</span><span class="kc">$</span><span class="mi">99</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">0</span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">1</span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">9</span><span class="nv">f</span><span class="p">,</span><span class="kc">$</span><span class="mi">98</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">65</span><span class="p">,</span><span class="kc">$</span><span class="nv">bc</span><span class="p">,</span><span class="kc">$</span><span class="nv">cd</span><span class="p">,</span><span class="kc">$</span><span class="mi">98</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="nv">d6</span><span class="p">,</span><span class="kc">$</span><span class="mi">77</span><span class="p">,</span><span class="kc">$</span><span class="mi">3</span><span class="nv">e</span><span class="p">,</span><span class="kc">$</span><span class="mi">98</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">52</span><span class="p">,</span><span class="kc">$</span><span class="nv">c7</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">f</span><span class="p">,</span><span class="kc">$</span><span class="mi">80</span><span class="w"></span>
</code></pre></div>
<p>Les <strong>trois premiers octets</strong> sont les <strong>trois index</strong> avec lesquels le générateur va jouer. Nous les appellerons les trois <strong>seeds</strong>. Suivent 8 nombres plutôt grands, positifs et négatifs (le premier vaut <code>-26514538</code>). Et enfin vient le nombre d'origine, qui vaut <code>0.8116351366043091</code>, que vous pouvez retrouver sous sa forme arrondie en tapant <code>PRINT RND(0)</code> dès l'allumage du VG5000µ.</p>
<p><img alt="Tête sur VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202005/20200510-VG5000-RND-Boot.png"></p>
<p>Cette table n'est pas la seule qui va être utilisée par le générateur. Il en existe <strong>une autre</strong>, qui sera utilisée depuis la ROM, en <code>$093d</code>, d'une longueur de 3.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">68</span><span class="p">,</span><span class="kc">$</span><span class="nv">b1</span><span class="p">,</span><span class="kc">$</span><span class="mi">46</span><span class="p">,</span><span class="kc">$</span><span class="mi">68</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">99</span><span class="p">,</span><span class="kc">$</span><span class="nv">e9</span><span class="p">,</span><span class="kc">$</span><span class="mi">92</span><span class="p">,</span><span class="kc">$</span><span class="mi">69</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">10</span><span class="p">,</span><span class="kc">$</span><span class="nv">d1</span><span class="p">,</span><span class="kc">$</span><span class="mi">75</span><span class="p">,</span><span class="kc">$</span><span class="mi">68</span><span class="w"></span>
</code></pre></div>
<h3>L'instruction RND</h3>
<p>L'instruction <code>RND</code> commence en <code>$090d</code> par un petit <strong>préambule</strong> qui vérifie l'argument passé à la fonction. Cet argument est disponible dans <code>FAC</code>, l'accumulateur flottant (voir <a href="https://www.triceraprog.fr/vg5000m-traitement-des-nombres.html">précédemment</a>)</p>
<div class="highlight"><pre><span></span><code><span class="nl">inst_rnd:</span><span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">getsign</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">rnd_seed_2</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">m</span><span class="p">,</span><span class="nv">reseed</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">rnd_gen</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">hl_to_fac</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">rnd_seed_2</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="nv">z</span><span class="w"></span>
</code></pre></div>
<p>Ce préambule teste en premier lieu le <strong>signe de l'argument</strong>. S'il est <strong>négatif</strong>, la routine branche vers <code>reseed</code>, que nous verrons <strong>plus loin</strong>. C'est au passage un comportement qui n'est indiqué ni dans le manuel d'utilisation du VG5000µ, ni dans les « Clés pour VG5000 », qui donnent de fausses informations (je vous laisse regarder).</p>
<p>Dans le cas du branchement, <code>HL</code> point vers la <strong>seed 2</strong> (et je ne vois aucun intérêt à ce que cela ne soit pas fait après le branchement...)</p>
<p>Si l'argument est <strong>nul</strong> ou <strong>positif</strong>, alors la nombre pointé par <code>HL</code>, qui est la variable système du dernier nombre généré ,est copié dans <code>FAC</code>. Souvenez-vous, c'est à cette adresse qu'a été placé à l'initialisation le nombre <code>0.8116351</code>.</p>
<p>On refait pointer <code>HL</code> vers la <strong>seed 2</strong> puis, si l’argument était <code>0</code>, on sort de la routine immédiatement (le flag <code>Z</code>éro a été conservé depuis <code>rst getsign</code>).</p>
<h4>Le calcul</h4>
<p>Puisqu'on est à présent dans le cas où l’<strong>argument est positif</strong>, il convient de <strong>générer un nouveau nombre</strong>. Ce nouveau nombre est basé sur, d'une part, le nombre généré précédemment, et d'autre part, les trois index qui avaient été initialisés à zéro (voir ci-dessus).</p>
<h5><strong>Première étape</strong></h5>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">and</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">07</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="kc">$</span><span class="mi">00</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">hl_to_bcde</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">fp_mul</span><span class="w"></span>
</code></pre></div>
<p>La <strong>première section</strong> du code précédent récupère l'index <strong>seed 2</strong> en l'incrémentant de 1. En effet, <code>A</code> contient 1 depuis l'appel à <code>getsign</code>. Le résultat est pris modulo 8 et remonté en RAM.</p>
<p>Au passage, <code>B</code> est initialisé à <code>0</code> pour que <code>BC</code> puisse servir d'index, et <code>HL</code> va pointer un cran plus loin, sur le début de la table des coefficients initialisés au boot (la table des 8 valeurs).</p>
<p>La <strong>seconde section</strong> calcul le pointeur dans cette table en quadruplant <code>A</code>, qui est l'index, en format <code>BC</code> comme index à ajouter au pointeur de base <code>HL</code>.</p>
<p>L'appel <code>hl_to_bcde</code> copie le nombre pointé dans la table vers <code>BCDE</code>, puis l'appel à <code>fp_mul</code> effectue la multiplication avec le contenu de <code>FAC</code>.</p>
<p><strong>Résumé</strong> : <em>cette première étape est donc une multiplication du précédent nombre généré par un autre nombre, fixe, pris dans une table dans 8 valeurs tour à tour</em>.</p>
<p>Le tout premier appel à RND(1) va multiplier <code>16129081</code> (<code>$98 $76 $1c $39) avec</code>0.8116351366043091<code>(</code>$80 $4f $c7 $52`).</p>
<p>Cela donne <code>13090929</code> (<code>$98 $47$c0 $71</code>). Cela peut se vérifier dans <code>FAC</code> (<code>$49e6</code>). Attention, le nombre est octet par octet dans le sens inverse à celui que j'utilise ici.</p>
<h5><strong>Seconde étape</strong></h5>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">rnd_seed_1</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">and</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">03</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="kc">$</span><span class="mi">00</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">01</span><span class="w"></span>
<span class="w"> </span><span class="nf">adc</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">rnd_seed_1</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">rnd_add</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mi">4</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">fp_add_hl</span><span class="w"></span>
<span class="nl">afterreseed:</span><span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">fac_to_bcde</span><span class="w"></span>
</code></pre></div>
<p>La <strong>seconde étape</strong> se divise elle aussi en <strong>deux sections</strong>.</p>
<p>Dans la <strong>première section</strong>, on récupère la <strong>seed 1</strong>, qui est incrémentée de 1 et modulo 4. Cependant, la valeur 0 est interdite. Par une comparaison avec 1 et un ajout à 0 (via <code>B</code>) avec retenue, si l'index était à 0, alors il est poussé à 1.</p>
<p>C'est donc en fait un index <strong>modulo 3</strong> que l'on obtient.</p>
<p>Et cet index forme un pointeur via <code>HL</code> de manière similaire à l'étape précédente, dans la <strong>table de trois valeurs</strong> de la <code>ROM</code> mentionné au début de l'article.</p>
<p>Cette valeur est alors <strong>ajoutée</strong> à <code>FAC</code>. L'appel à <code>fp_add_hl</code> se charge de l'étape intermédiaire de chargement de la valeur dans <code>BCDE</code>. Puis le résultat est ramené dans <code>BCDE</code>.</p>
<p>Le label est un branchement venant du <code>reseed</code> que nous verrons plus loin.</p>
<p><strong>Résumé</strong> : <em>cette seconde étape est une addition du nombre obtenue à la première étape avec un des trois nombres pris dans la deuxième table, pris tour à tour</em>.</p>
<p>Le tout premier appel additionne <code>13090929</code> (<code>$98 $47 $c0 $71</code> ) avec <code>4.626181e-08</code> (<code>$68 $b1 $46 $68</code>). Ce second nombre est bien trop petit par rapport au premier. Cette addition ne change rien... dans ce cas-ci. Nous verrons plus tard à quoi cette addition peut servir.</p>
<h4><strong>Troisième étape</strong></h4>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">e</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">e</span><span class="p">,</span><span class="nv">c</span><span class="w"></span>
<span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">f</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
</code></pre></div>
<p>Dans cette <strong>troisième étape</strong>, le générateur fait des <strong>mélanges</strong>. Les 8 bits de poids les plus faibles sont mis de côté et les 8 bits de poids fort sont placés dans les 8 bits de poids faible.</p>
<p>Les 8 bits mis de côté sont <code>XOR</code>és avec <code>b01001111</code>. Ce qui signifie que certains bits sont inversés. Puis le résultat est placé dans les 8 bits de poids fort.</p>
<p>Cette opération ne me semble pas avoir de sens arithmétique. Cela semble être juste un mélange. Peut-être pour amener de l'entropie dans les bits de poids faible... Peut-être.</p>
<p><strong>Résumé</strong> : <em>cet étape mélange les parties de la mantisse et change quelques bits</em>.</p>
<p>Le nombre est à présent <code>12501063</code> (<code>$98 $3e $c0 $47</code>).</p>
<h4><strong>Étape intermédiaire</strong></h4>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="kc">$</span><span class="mi">80</span><span class="w"></span>
<span class="w"> </span><span class="nf">dec</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="kc">$</span><span class="mi">80</span><span class="w"></span>
</code></pre></div>
<p>Cette étape profite que <code>HL</code> pointe actuellement (depuis la récupération de <code>FAC</code> dans <code>BCDE</code>) sur l'octet après <code>FAC</code> pour <strong>préparer le terrain</strong> pour plus tard. Cet octet contient le complément à 1 du bit de signe du nombre de <code>FAC</code>. En mettant cet octet à <code>$80</code>, on force la <strong>valeur</strong> à être <strong>positive</strong>.</p>
<p><code>HL</code> pointe ensuite sur l'<strong>octet précédent</strong>, l'exposant, et met celui-ci à <code>$80</code>, c'est-à-dire $2^ 0$ (voir l'article sur les nombres, toujours).</p>
<p><strong>Résumé</strong> : <em>le nombre final sera un nombre positif et l'exposant est fixé à $2^0 = 1$</em>.</p>
<p>Pas d’influence, pour le moment sur le nombre tenu dans <code>BCDE</code>.</p>
<h4><strong>Quatrième étape</strong>*</h4>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">rnd_seed_0</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">ab</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nz</span><span class="p">,</span><span class="nv">rnd_cnt</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">c</span><span class="w"></span>
<span class="w"> </span><span class="nf">dec</span><span class="w"> </span><span class="nv">d</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">e</span><span class="w"></span>
</code></pre></div>
<p>C'est la <strong>dernière étape</strong> du calcul. Dans la <strong>première partie</strong>, la <strong>seed 0</strong> est incrémentée et récupérée dans <code>A</code>.</p>
<p>Si la soustraction par <code>171</code> (<code>$ab</code>) n'est pas nulle, on branche plus loin à l'étape finale. Sinon, le résultat (<code>0</code>) est replacé dans la <strong>seed 0</strong>. C'est donc un compteur jusqu'à <code>171</code> qui, lorsqu'il atteint cette valeur, <strong>modifie légèrement</strong> la mantisse.</p>
<p>Le <strong>premier</strong> et le <strong>troisième</strong> octets de la mantisse sont <strong>incrémentés</strong>, celui du <strong>milieu décrémenté</strong>, sans se soucier de débordements éventuels.</p>
<p><strong>Résumé</strong> : <em>une fois tous les 171 tirages, la mantisse est modifiée légèrement</em>.</p>
<p>Comme ici, c'est le premier tirage, il ne se passe rien.</p>
<h4><strong>Étape finale</strong></h4>
<div class="highlight"><pre><span></span><code><span class="nl">rnd_cnt:</span><span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">bcde_norm</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">rnd_gen</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">cpy_faclsb_hl</span><span class="w"></span>
</code></pre></div>
<p>La <strong>mantisse</strong> a été <strong>générée</strong>, l'<strong>exposant</strong> est <strong>là</strong>. Mais tout nombre dans <code>FAC</code> en sorti de routine doit être normalisé.</p>
<p>Comme indiqué dans l'article précédent sur la représentation des nombres, cela signifie que la mantisse et l'exposant vont être modifiée afin d'obtenir une mantisse avec un premier bit à 1 implicite, lui-même remplacé par le bit de signe.</p>
<p><strong>MAIS</strong> ! Il y a un twist ! La routine <code>bcde_norm</code> n'attend pas en entrée un nombre <code>BCDE</code>, mais une mantisse 32 bits <code>CDEB</code>. L'exposant du nombre actuel va donc se retrouver... en partie la moins significative du nombre, afin de nourrir, en quelque sorte, la partie droite de la mantisse lors de l'éventuel décalage vers la gauche.</p>
<p>C'est peut-être un peu obscure : je donne un exemple dans le résumé.</p>
<p>En sortie de normalisation, le nombre est bien dans <code>FAC</code>. Le contenu de <code>FAC</code> est alors copié à l'emplacement du dernier nombre généré.</p>
<p>C'est terminé !</p>
<p><strong>Résumé</strong> : <em>mise en forme du nombre, à la fois dans <code>FAC</code> comme résultat de la fonction, et de côté pour servir de base au prochain nombre généré (ou pour être retourné en case de <code>RND(0)</code>)</em>.</p>
<p>Nous en étions à <code>$98 $3e $c0 $47</code>. Mais la normalisation s'attend à une mantisse 32 bits, et c'est donc comme ça que va être perçue la mantisse : <code>$3e $c0 $47 $98</code>.</p>
<p>La normalisation doit <strong>déplacer la mantisse à gauche</strong> jusqu'à ce que le bit de <strong>poids fort</strong> soit à <code>1</code>. Il va falloir deux étapes pour cela :</p>
<ul>
<li>de <code>$3ec04798</code> à <code>$7d808f30</code>, puis</li>
<li>de <code>$7d808f30</code> à <code>$fb011e60</code></li>
</ul>
<p>Comme le bit de poids fort du dernier octet n'est pas à 1, il n'y a pas d'arrondi. Cet octet est abandonné, le bit de signe et l'exposant corrigé par le nombre d'étapes ($80 - 2 donne $7e).</p>
<p>Au final, nous avons obtenu : <code>$7e $7b $01 $1e</code>, soit <code>0.245121</code></p>
<p><img alt="Tête sur VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202005/20200510-VG5000-RND-Next.png"></p>
<h3>Suppléments</h3>
<h4>Re-seed</h4>
<p>Si l'<strong>argument</strong> de <code>RND()</code> est <strong>négatif</strong>, alors un branchement a lieu sur une routine de réinitialisation du générateur.</p>
<div class="highlight"><pre><span></span><code><span class="nl">reseed:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">dec</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">dec</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">afterreseed</span><span class="w"></span>
</code></pre></div>
<p>À l'arrivée dans cette partie, <code>HL</code> pointe sur <strong>seed 2</strong> et <code>A</code> est égal à <code>$FF</code>. Ce qui a pour résultat de mettre les trois octets à <code>$FF</code>.</p>
<p>Le branchement ramène dans le générateur à la <strong>fin de la seconde étape</strong>, c'est-à-dire après la multiplication et l'addition. Ce qui est dans <code>FAC</code> (l'argument négatif de <code>RND()</code>) est ramené dans <code>BCDE</code> et le reste des étapes est effectué.</p>
<p>C'est donc une <strong>nouvelle séquence</strong> qui démarre, dépendante de l'argument passé à <code>RND()</code>.</p>
<h4>Cas de l'addition</h4>
<p>Revenons sur la <strong>seconde étape</strong>, l'addition avec un nombre tout petit. Dans l'exemple que nous avons suivi pendant l'article, l'addition ne servait à rien, car la différence entre les exposants était trop grand et donc le nombre à addition non significatif.</p>
<p>La question à se poser est donc : <em>dans quels cas ces nombres deviennent-ils significatifs ?</em></p>
<p>Le plus petit d'entre eux est : <code>$68 $46 $b1 $68</code> qui a pour exposant <code>$68</code>. C'est-à-dire <code>$80</code> - 24.</p>
<p>Il faut donc un nombre strictement inférieur à <code>$00 $00 $00 $80</code> (<code>0.5</code>) pour que l'addition soit intéressante.</p>
<p>Sauf que... juste avant l'addition, la <strong>multiplication</strong> a été faite avec un nombre dont l'<strong>exposant</strong> était au minimum <code>$98</code>. Puisque dans une multiplication, les exposants s'ajoutent, cela implique que seuls des nombres avec un exposants à '$68' initialement vont être assez petits après la multiplication et être modifiés par l'addition.</p>
<p>C'est quelque chose de facile à déclencher en modifiant à la main le dernier nombre généré en mémoire. Mais est-ce que cela se passe si on laisse le générateur se dérouler normalement ?</p>
<p>Un <strong>expérience</strong> simple avec un <strong>debuggeur</strong> en mettant un point d'arrêt dans le code d'addition et en faisant tourner le générateur montre que... <strong>non</strong>. Cela n'arrive pas. Ou alors assez rarement pour résister à l'expérience.</p>
<p>Il est temps de se poser la question des <strong>bornes maximales</strong> et <strong>minimales</strong> des nombres générés.</p>
<p>Mais vu la longueur de l'article, ce sera pour le prochain...</p>VG5000µ, jouer avec les nombres2020-05-09T00:00:00+02:002020-05-09T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2020-05-09:/vg5000m-jouer-avec-les-nombres.html<p>Suite à l'<a href="https://www.triceraprog.fr/vg5000m-traitement-des-nombres.html">article précédent</a>, j'ai mis sur le dépôt <a href="https://github.com/Triceraprog/vg5000_tools">GitHub</a> un petit <strong>utilitaire Python</strong> qui reproduit les conversions entre la valeur du nombre et son codage en 4 octets.</p>
<p>Parfois, voir du code est plus simple qu'un long discours.</p>
<p>Et parce qu'on ne sait jamais trop quel sera la …</p><p>Suite à l'<a href="https://www.triceraprog.fr/vg5000m-traitement-des-nombres.html">article précédent</a>, j'ai mis sur le dépôt <a href="https://github.com/Triceraprog/vg5000_tools">GitHub</a> un petit <strong>utilitaire Python</strong> qui reproduit les conversions entre la valeur du nombre et son codage en 4 octets.</p>
<p>Parfois, voir du code est plus simple qu'un long discours.</p>
<p>Et parce qu'on ne sait jamais trop quel sera la vie future du dépôt, voici le code des deux principales fonctions de l'outil.</p>
<div class="highlight"><pre><span></span><code><span class="kn">import</span> <span class="nn">math</span>
<span class="k">def</span> <span class="nf">get_byte</span><span class="p">(</span><span class="n">number</span><span class="p">):</span>
<span class="sd">"""Takes the current number, and returns the next byte encoding it with the reminder of the number to encode. """</span>
<span class="n">number</span> <span class="o">*=</span> <span class="mi">256</span>
<span class="n">result</span> <span class="o">=</span> <span class="nb">int</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="k">return</span> <span class="n">result</span><span class="p">,</span> <span class="p">(</span><span class="n">number</span> <span class="o">-</span> <span class="n">result</span><span class="p">)</span>
<span class="k">def</span> <span class="nf">encode</span><span class="p">(</span><span class="n">number</span><span class="p">):</span>
<span class="sd">"""Gets a number, returns it's encoded four bytes (memory layout, so exponent at the end)."""</span>
<span class="c1"># If the number is zero, the encoding is immediate.</span>
<span class="c1"># In fact, only the exponent has to be 0.</span>
<span class="k">if</span> <span class="n">number</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">return</span> <span class="p">[</span><span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">,</span> <span class="mi">0</span><span class="p">]</span>
<span class="c1"># Gets the sign from the number for later encoding</span>
<span class="n">sign</span> <span class="o">=</span> <span class="mh">0x80</span> <span class="k">if</span> <span class="n">number</span> <span class="o"><</span> <span class="mi">0</span> <span class="k">else</span> <span class="mi">0</span>
<span class="c1"># We encode only positive numbers</span>
<span class="n">number</span> <span class="o">=</span> <span class="nb">abs</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="c1"># Shift the number so that the first fractional part bit</span>
<span class="c1"># of the mantissa is 1 (0.1 binary is 0.5 decimal)</span>
<span class="n">exp</span> <span class="o">=</span> <span class="mi">0</span>
<span class="k">while</span> <span class="n">number</span> <span class="o">>=</span> <span class="mf">0.5</span><span class="p">:</span>
<span class="n">number</span> <span class="o">/=</span> <span class="mi">2</span>
<span class="n">exp</span> <span class="o">+=</span> <span class="mi">1</span>
<span class="k">while</span> <span class="n">number</span> <span class="o"><</span> <span class="mf">0.5</span><span class="p">:</span>
<span class="n">number</span> <span class="o">*=</span> <span class="mi">2</span>
<span class="n">exp</span> <span class="o">-=</span> <span class="mi">1</span>
<span class="c1"># Gets the three bytes encoding the mantissa</span>
<span class="n">o1</span><span class="p">,</span> <span class="n">number</span> <span class="o">=</span> <span class="n">get_byte</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="n">o2</span><span class="p">,</span> <span class="n">number</span> <span class="o">=</span> <span class="n">get_byte</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="n">o3</span><span class="p">,</span> <span class="n">number</span> <span class="o">=</span> <span class="n">get_byte</span><span class="p">(</span><span class="n">number</span><span class="p">)</span>
<span class="c1"># Clears the most significant bit</span>
<span class="c1"># and replace it by the sign bit</span>
<span class="n">o1</span> <span class="o">&=</span> <span class="mh">0x7F</span>
<span class="n">o1</span> <span class="o">|=</span> <span class="n">sign</span>
<span class="c1"># Encode exponent</span>
<span class="n">exp</span> <span class="o">+=</span> <span class="mi">128</span>
<span class="c1"># Returns an array (Z80 memory layout)</span>
<span class="k">return</span> <span class="p">[</span><span class="n">o3</span><span class="p">,</span> <span class="n">o2</span><span class="p">,</span> <span class="n">o1</span><span class="p">,</span> <span class="n">exp</span><span class="p">]</span>
<span class="k">def</span> <span class="nf">decode</span><span class="p">(</span><span class="n">encoded</span><span class="p">):</span>
<span class="sd">""" Takes four encoded bytes in the memory layout, and returns the decoded value. """</span>
<span class="c1"># Gets the exponent</span>
<span class="n">exp</span> <span class="o">=</span> <span class="n">encoded</span><span class="p">[</span><span class="mi">3</span><span class="p">]</span>
<span class="c1"># If it's 0, we're done. The value is 0.</span>
<span class="k">if</span> <span class="n">exp</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="k">return</span> <span class="mi">0</span>
<span class="c1"># Extract value from the exponent</span>
<span class="n">exp</span> <span class="o">-=</span> <span class="mi">128</span>
<span class="c1"># Extract the sign bit from MSB</span>
<span class="n">sign</span> <span class="o">=</span> <span class="n">encoded</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">&</span> <span class="mh">0x80</span>
<span class="c1"># Sets the most significant bit implied 1 in the mantissa</span>
<span class="n">encoded</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">=</span> <span class="n">encoded</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span> <span class="o">|</span> <span class="mh">0x80</span>
<span class="c1"># Reconstruct the mantissa</span>
<span class="n">mantissa</span> <span class="o">=</span> <span class="n">encoded</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span>
<span class="n">mantissa</span> <span class="o">*=</span> <span class="mi">256</span>
<span class="n">mantissa</span> <span class="o">+=</span> <span class="n">encoded</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span>
<span class="n">mantissa</span> <span class="o">*=</span> <span class="mi">256</span>
<span class="n">mantissa</span> <span class="o">+=</span> <span class="n">encoded</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span>
<span class="c1"># Divide the number by the mantissa, corrected</span>
<span class="c1"># by the 24 bits we just shifted while reconstructing it</span>
<span class="n">mantissa</span> <span class="o">/=</span> <span class="n">math</span><span class="o">.</span><span class="n">pow</span><span class="p">(</span><span class="mi">2</span><span class="p">,</span> <span class="mi">24</span> <span class="o">-</span> <span class="n">exp</span><span class="p">)</span>
<span class="c1"># Apply the sign to the whole value</span>
<span class="k">if</span> <span class="n">sign</span><span class="p">:</span>
<span class="n">mantissa</span> <span class="o">=</span> <span class="o">-</span><span class="n">mantissa</span>
<span class="k">return</span> <span class="n">mantissa</span>
</code></pre></div>VG5000µ, traitement des nombres2020-05-08T00:00:00+02:002020-05-08T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2020-05-08:/vg5000m-traitement-des-nombres.html<p>Lors d'une discussion sur le forum <strong>system-cfg</strong> à propos de la fonction <code>RND</code> une question a été posée sur le format des nombres dans le VG5000µ. C'est une question qui revient et que je voulais documenter pour mémoire, me posant régulièrement la question et oubliant juste après...</p>
<h3>Différents formats</h3>
<p>Distinguons …</p><p>Lors d'une discussion sur le forum <strong>system-cfg</strong> à propos de la fonction <code>RND</code> une question a été posée sur le format des nombres dans le VG5000µ. C'est une question qui revient et que je voulais documenter pour mémoire, me posant régulièrement la question et oubliant juste après...</p>
<h3>Différents formats</h3>
<p>Distinguons déjà deux choses : les nombres manipulés par le <strong>système</strong>, et les nombres manipulés par le <strong>BASIC</strong>. Les premiers sont de diverses formes en fonction des besoins, de type entier, signé ou pas, sur 8 ou 16 bits la plupart du temps. Il n'y a pas grand chose à dire sur eux.</p>
<p>Les seconds sont ceux <strong>manipulés par le BASIC</strong>, qui est un BASIC Microsoft sur VG5000µ, et ce qui sera valide dans cet article le sera pour d'autres machines avec <strong>BASIC Microsoft</strong> et un processeur <strong>Z80</strong>. Au moins dans les grandes lignes, mais pour ce que j'ai vu en comparant une paire d'entre eux, souvent jusque dans les détails. Ces BASIC ne différent que de petites modifications ici ou là, de l'ordre de l'optimisation.</p>
<p>Le BASIC lui-même traite plusieurs types de nombres. On a déjà vu la manière dont il traitant de manière particulière les <strong>numéros de lignes</strong> après un <code>GOTO</code> ou un <code>GOSUB</code> dans l'<a href="https://www.triceraprog.fr/vg5000m-un-listing-en-basic.html">article sur l'arrangement des lignes</a>.</p>
<p>Le format qui nous intéresse aujourd'hui est celui <strong>utilisé pour les calculs</strong>, ainsi que celui utilisé dans les <strong>variables numériques</strong>. Dans l'<a href="https://www.triceraprog.fr/vg5000m-les-variables-en-memoire.html">article sur les variables</a>, j'étudiais comment les variables étaient créées, avec leur nom suivis de 4 octets. Mais que contiennent ces 4 octets ?</p>
<h3>Le format flottant</h3>
<p>Tous les nombres traités par le BASIC VG5000µ pour les calculs et les variables sont dans un format a <strong>virgule flottante</strong>. C'était déjà le cas avec le BASIC créé initialement à <strong>Dartmouth</strong>. L'idée était que les utilisateurs n'avaient pas à se poser de question sur le format interne, et pouvaient utiliser les nombres <strong>naturellement</strong>.</p>
<p>Plus tard, pour des raisons de performance (vitesse et consommation mémoire), des versions de BASIC ont ajouté un typage sur ces nombres. Comme le suffit <code>%</code> des variables numériques qui indiquent que la variable contient un nombre entier.</p>
<p>Sur le VG5000µ, <strong>pas de typage de nombre</strong>. Tout est au même format. Je nommerai ce format le format <code>BCDE</code> par la suite. <code>BCDE</code> car il est manipulé la plupart du temps à travers cette paire de registres (<code>BC</code> et <code>DE</code>). <code>BCDE</code> désigne donc aussi <strong>l'emplacement du nombre</strong> traité dans certaines situations.</p>
<p>Pour les calculs, le BASIC utilise un <strong>accumulateur flottant</strong>, que je nommerai <code>FAC</code> par la suite (<strong>Floating point ACumulator</strong>). L'essentiel du format est le même que lorsqu'il est au format <code>BCDE</code>.</p>
<p>L'accumulateur <code>FAC</code> est l'endroit où se situe « le nombre en cours ». À la sortie d'une fonction, le résultat s'y trouve et est donc disponible pour les calculs ou fonctions suivants.</p>
<p>Ainsi, dans une expression comme <code>INT(4 * 0.2)</code>, le <code>FAC</code> va d'abord contenir <code>4</code>, puis après la multiplication <code>0.8</code>, puis après <code>INT()</code>, contiendra <code>0</code>. Des instructions comme <code>PRINT</code> ou <code>LET</code> (explicite ou implicite), iront chercher cette valeur pour la traiter.</p>
<h3>Les mouvements</h3>
<p>Puisque <code>BCDE</code> et <code>FAC</code> sont étroitement liés, il existe des fonctions pour transférer le contenu de <code>BCDE</code> vers <code>FAC</code> (<code>\$05d2</code>) et inversement (<code>\$05dd</code>). Il est possible aussi de récupérer dans <code>BCDE</code> un nombre pointé par <code>HL</code> (<code>\$05e0</code>), et de monter dans <code>FAC</code> un nombre pointé par <code>HL</code> (<code>\$05cf</code>), en utilisant <code>BCDE</code> au passage.</p>
<h3>Le format</h3>
<p>Voici en premier lieu un tableau auquel je vais me référer pour expliquer le format.</p>
<table>
<thead>
<tr>
<th>B</th>
<th>C</th>
<th>D</th>
<th>E</th>
</tr>
</thead>
<tbody>
<tr>
<td>Exp.</td>
<td>S|MSB</td>
<td>Milieu</td>
<td>LSB</td>
</tr>
<tr>
<td>$49e9</td>
<td>$49e8</td>
<td>$49e7</td>
<td>$49e6</td>
</tr>
</tbody>
</table>
<ul>
<li><strong>Exp.</strong> est l'exposant du nombre (la puissance de 2 par laquelle est multipliée la mantisse).</li>
<li><strong>MSB</strong> (Most Significant Byte), Milieu et <strong>LSB</strong> (Least Significant Byte) forment la <strong>mantisse</strong>.</li>
<li><strong>S</strong> est le bit de signe et se trouve en bit 7 du <strong>MSB</strong>.</li>
<li>Les adresses représentent les positions dans <code>FAC</code> l'accumulateur flottant.</li>
</ul>
<p><strong>Attention</strong> : les adresses indiquées partent de la plus haute à la place basse. Lorsque vous inspectez la mémoire octet par octet, que ce soit dans <code>FAC</code> ou pour une valeur de variable, le nombre est donc dans l'autre sens <code>EDCB</code>, avec l'exposant en dernière position.</p>
<h4>La mantisse</h4>
<p>La <strong>mantisse</strong> couvre donc <strong>23 bits</strong>. Le 24ième bit, le plus significatif, est implicitement à 1. Dans le format codé, il est donc remplacé par un <strong>bit de signe</strong>. Un bit à 1 indique un nombre négatif, positif sinon.</p>
<p>La mantisse est lue comme <code>0.1xxxxxx xxxxxxxx xxxxxxxx</code> en binaire. Les <code>x</code> étant pris dans les 23 bits formés par MSB, Milieu et LSB.</p>
<h4>L'exposant</h4>
<p>L'<strong>exposant</strong> est centré sur 128 (ou $80 en hexadécimal). Cela signifie que <strong>128</strong> est l'exposant nul. Pour 129 l'exposant est 1, 127, et pour l'exposant est -1.</p>
<p>La valeur 0 ($00) pour l'exposant est <strong>spéciale</strong> : elle représente le <strong>nombre 0</strong>. Peu importe les autres octets, la valeur est nulle. Dans certains codage de nombre flottants, cet exposant zéro est utilisé pour représenter des valeurs particulières qui viennent compléter celles accessibles depuis un exposant non nul. Ici, ce n'est pas le cas. Un exposant à 0 signifie le nombre 0.</p>
<h4>La valeur du nombre</h4>
<p>La valeur du <strong>nombre représenté</strong> est égal à : $-1 . signe . 2^{exp.} . mantisse$.</p>
<p>Ceci mérite quelques exemples.</p>
<p><strong>Exemple 1</strong> : <code>0</code>, codé <code>00 xx xx xx</code>. Comme je l'ai écrit ci-dessus, peut importe la valeur des autres octets. Si l'exposant est à 0, c'est le nombre 0.</p>
<p><strong>Exemple 2</strong> : <code>1</code>, codé <code>81 00 00 00</code>. En effet, 1 est égal à $b0.1 . 2^1$ (je préfixe les nombres en binaire par <code>b</code>, pour faire la différence avec les nombres en base 10 que je ne préfixe pas).</p>
<p>L'exposant est donc 1, codé en 128 + 1 = 129, \$81 en hexadécimal. La mantisse est 'b0.1', codé en <code>00 00 00</code> puisque le premier <code>1</code> est implicite dans le codage.</p>
<p><strong>Example 3</strong> : <code>2</code>, codé <code>82 00 00 00</code>. Sur le même principe, 2 est égal à $b0.1 . 2^2$. Donc exposant 2 donne \$82 et la mantisse est là aussi 0 puisque b0.1 est implicite.</p>
<p><strong>Exemple 4</strong> : <code>-2</code>, codé <code>82 80 00 00</code>. Le code est similaire à celui de <code>2</code>, mais le bit de signe négatif est placé sur le 7ième bit de l'octet le plus significatif de la mantisse.</p>
<p><strong>Exemple 5</strong> : <code>0.1', code ``7d 4c cc cc</code>. Là, c'est un peu plus compliqué. <code>0.1</code> n'est pas une valeur qui tombe juste en binaire. Puisque la précision est limitée, il faut bien s'arrêter quelque part ; il faut bien comprendre que le nombre 'retenu' n'est pas vraiment <code>0.1</code>, juste un nombre approchant.</p>
<p>Le nombre s'écrit $2^{-3} . b0.110011001100110011001100$... ; 128 - 3 donne \$7d en hex. Une fois enlevé le premier bit de la mantisse, le reste s'écrit comme indiqué.</p>
<h4>Aparté 1</h4>
<p>Il est possible d'écrire</p>
<div class="highlight"><pre><span></span><code><span class="kd">LET</span><span class="w"> </span><span class="vg">A</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0.1</span>
<span class="kr">PRINT</span><span class="w"> </span><span class="vg">A</span>
</code></pre></div>
<p>Et le nombre affiché sera bien <code>0.1</code>. Pourtant, ce n'est pas le nombre encodé, qui est plus quelque chose comme : 0.09999999403953552</p>
<p>Mais ce nombre est arrondi lors de l'affichage.</p>
<h4>Aparté 2</h4>
<p>À noter aussi que chaque opération effectuée par le BASIC, qui en interne utilise 32 bits, se termine par un encodage du nombre, qui est arrondi aux 24 bits disponibles. Si les calculs ont besoins de plus de 24 bits significatifs, alors les résultats ne seront pas exacts. Il faut se méfier des nombres flottants.</p>
<h4>Aparté 3</h4>
<p>Rappelez-vous qu'en lisant la mémoire octet par octet, les nombres codés vont apparaître dans l'autre sens. Ainsi, si vous assignez à une variable le nombre <code>1</code> et que vous regardez dans la section de variable la valeur suivant le nom, vous y verrez <code>00 00 00 81</code>.</p>
<h4>Aparté 4</h4>
<p>En examinant la mémoire de <code>FAC</code> et en traçant les opérations, vous pourrez constater que l'octet suivant (<code>\$49ea</code>) bouge aussi. Il s'agit d'un octet utilisé pour stocker le signe des opérations. En effet, afin de faire des opérations sur les mantisses, il faut en extraire le bit de signe et y remettre le bit implicite. Un bit de signe est stocké à cet endroit (attention, il s'agit de son complément).</p>
<h3>Une opération</h3>
<p>Pour se faire une idée de comment sont traités les nombres, je vous <strong>invite</strong> à suivre le <strong>processus d'addition</strong>.</p>
<p>Tout commence en <code>$0705</code> par la récupération sur la pile du nombre qui vient d'être décodé depuis la ligne du BASIC. Dans <code>FAC</code> est déjà positionnée l'opérande précédente de l'opération.</p>
<div class="highlight"><pre><span></span><code><span class="nl">eval_add:</span><span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">fp_bcde_add</span><span class="w"></span>
</code></pre></div>
<p>La nouvelle opérande est maintenance dans <code>BCDE</code> dans le format expliqué dans les paragraphes précédents. Il faut donc maintenant ajouter <code>BCDE</code> et <code>FAC</code>.</p>
<p>En <code>$0310</code>, la routine commence par un test. Si l'exposant, qui est initialement dans <code>B</code>, est égal à <code>0</code>, on peut <strong>s’arrêter là</strong>. Ajouter <code>0</code> à <code>FAC</code> signifie ne pas le modifier. Le <strong>retour est immédiat</strong> :</p>
<div class="highlight"><pre><span></span><code><span class="nl">fp_bcde_add:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="nv">z</span><span class="w"></span>
</code></pre></div>
<p>Deuxième cas simple, si la valeur de l'exposant de <code>FAC</code> est 0, le résultat est le nombre présent dans <code>BCDE</code>, il suffit donc de le <strong>transférer</strong> dans <code>FAC</code> et c'est <strong>terminé</strong> :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">fac_exp</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">bcde_to_fac</span><span class="w"></span>
</code></pre></div>
<p>L'étape suivante est de s'assurer que le nombre dans <code>FAC</code> a un <strong>exposant plus grand</strong> que celui de <code>BCDE</code>. Si ça le cas, c'est parfait, sinon, on échange les deux nombres. L'opération utilise la pile temporairement. <code>A</code> qui contient la <strong>différence</strong> entre les deux <strong>exposants</strong>, prend la valeur de son <strong>opposée</strong>, pour rester cohérente.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">no_swap</span><span class="w"></span>
<span class="w"> </span><span class="nf">cpl</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">fac_to_stck</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">bcde_to_fac</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
</code></pre></div>
<p>À ce niveau, on a :</p>
<ul>
<li>dans <code>FAC</code> le nombre dont l'exposant est le plus grand,</li>
<li>dans <code>BCDE</code>, le nombre dont l'exposant est le plus petit,</li>
<li>dans <code>A</code>, la différence entre ces deux exposants.</li>
</ul>
<p>Si le nombre d'exposant le plus petit est <strong>insignifiant</strong> par rapport au plus grand, on peut <strong>s'arrêter là</strong>, le résultat de l'addition sera le plus grand des grands nombres. Puisque les nombres sont codés sur 24 bits significatifs, si la différences entre les exposants est de 25 ou plus, on peut sortir de la fonction :</p>
<div class="highlight"><pre><span></span><code><span class="nl">no_swap:</span><span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">19</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="nv">nc</span><span class="w"></span>
</code></pre></div>
<p>À présent que les <strong>nombres</strong> sont <strong>en place</strong> et qu'il est <strong>intéressant</strong> de les <strong>additionner</strong>, il faut les <strong>préparer</strong>. Pour le moment, le <strong>bit de signe</strong> est toujours codé dans les deux nombres. Ils sont aussi potentiellement à des exposants différents. Deux raisons pour lesquelles on ne peut pas additionner les mantisses pour le moment.</p>
<p>La première opération est d'<strong>extraire les signes</strong> et de renvoyer une indication sur l'opération à faire. Plus d'explication sur cette indication juste après.</p>
<p>Puis d'aligner les deux mantisses sur l'exposant le plus grand.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">ext_sign</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">div_mant</span><span class="w"></span>
</code></pre></div>
<p>Je n'entre pas dans la routine <code>ext_sign</code>, mais voici ce qu'elle fait. Elle prend le <strong>bit 7</strong> de l'octet le plus significatif de <code>FAC</code> et le remplace par le 1 implicite de la mantisse codée. Le bit de signe est inversé et est <strong>mis de côté</strong>, dans l'octet suivant l'exposant de <code>FAC</code>.</p>
<p>Puis la <strong>même opération</strong> est faite sur <code>BCDE</code> : extraction de signe, remplacement par 1. Le signe n'est pas mis de côté, mais est utilisé pour <strong>renvoyer</strong> dans <code>A</code> une indication : <code>0</code> si les signes étaient <strong>identiques</strong>, <code>1</code> s'ils étaient <strong>opposés</strong>.</p>
<p>Cette indication, qui est sauvée dans <code>H</code> pour pouvoir récupérer la valeur de <code>AF</code> sauvée sur la pile (<code>A</code> contient la différence entre les exposants), servira bientôt.</p>
<p>L'opération <code>div_mant</code> <strong>décale vers la droite</strong> la mantisse de <code>BCDE</code> d'autant de positions qu'indiquées par <code>A</code>. <strong>Attention</strong> : en sortie de cette fonction, la mantisse est disponible sur <code>CDEB</code>.</p>
<p>En effet, l'exposant n'est plus nécessaire, puisqu'il est identique à celui de <code>FAC</code>. On peut donc réutiliser <code>B</code> pour <strong>récupérer</strong> les bits <strong>résultants</strong> du décalage à droite et éviter de perdre trop de précision. La mantisse est <strong>temporairement sur 32 bits</strong> (même si seuls 24 sont toujours significatifs, puisque issus du nombre initial).</p>
<p>Il reste donc à passer à l'addition... enfin presque.</p>
<p>Dans la partie suivante, on récupère dans <code>A</code> l'indicateur donné précédemment par les signes. Si les signes étaient identiques, on passe à la suite. S'ils étaient différents, c'est une soustraction qui sera faite, en allant vers <code>min_bcde</code>.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">h</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">fac_lsb</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">p</span><span class="p">,</span><span class="nv">min_bcde</span><span class="w"></span>
</code></pre></div>
<p>Pourquoi cette <strong>différence de traitement</strong> en fonction des signes ?</p>
<ul>
<li>Si les signes des deux nombres sont <strong>positifs</strong>, il faut additionner les mantisses et le nombre sera <strong>positif</strong>.</li>
<li>Si les signes des deux nombres sont <strong>négatifs</strong>, on peut aussi additionner les mantisses comme des nombres <strong>positifs</strong>, et pendre l'opposée du tout. $(-a) + (-b)$ est la même chose que $-(a+b)$.</li>
<li>Si les signes des deux nombres sont <strong>opposés</strong>, on peut soustraire les mantisses et corriger le signe du résultat. En effet $a + (-b)$ est la même chose que $(a - b)$ et $(-a + b)$ est la même chose que $-(a-b)$.</li>
</ul>
<p>Le cas de l'<strong>addition des mantisses</strong> est le suivant :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">add_bcde</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">round</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">overflow</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,</span><span class="kc">$</span><span class="mi">01</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">shft_right</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">round</span><span class="w"></span>
</code></pre></div>
<p>Après l'appel à <code>add_bcde</code> qui ajoute la mantisse <code>CDE</code> avec celle de <code>FAC</code> octet par octet, un test est fait sur la <strong>retenue</strong>. Si l'addition n'a pas générée de retenue, alors on va vers la <strong>routine d'arrondi</strong> de <code>FAC</code>, qui terminera l'opération.</p>
<p>S'il y a eu une <strong>retenue</strong>, il faut augmenter l'<strong>exposant</strong> de 1, ce qui est fait en pointant <code>HL</code> un cran plus loin que la mantisse et <strong>augmentant</strong> la valeur de l'exposant qui s'y trouve. Si cette incrémentation a ramené l'exposant à <code>0</code>, c'est qu'il était déjà à son maximum. Le nombre est trop grand, une <strong>erreur de dépassement</strong> de capacité est lancée.</p>
<p>Sinon, il faut corriger la mantisse avec un décalage vers la droite de 1 bit (paramètre indiqué par <code>L</code>) puis brancher vers la routine d'arrondi.</p>
<p>Le cas de la <strong>soustraction des mantisses</strong> est le suivant :</p>
<div class="highlight"><pre><span></span><code><span class="nl">min_bcde:</span><span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">sbc</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">e</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">e</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">sbc</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">d</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">d</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">sbc</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">c</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">compl2</span><span class="w"></span>
</code></pre></div>
<p>Toute la première partie, jusqu'au <code>call</code>, est la <strong>soustraction de la mantisse</strong> de 'FAC' par la mantisse 'CDEB' octet par octet. Le résultat se trouve dans 'CDE'. L'exposant du nombre final est toujours celui de <code>FAC</code>.</p>
<p>Si le <strong>résultat</strong> de la soustraction est <strong>positif</strong>, alors il ne se passe rien de plus. Si une <strong>retenue</strong> a eue lieu pendant la soustraction, alors on prend son <strong>opposée</strong> en appelant <code>compl2</code>, afin de rendre la <strong>mantisse positive</strong>. Cette routine <strong>inverse</strong> aussi le <strong>bit de signe</strong> qui avait était extrait dans l'octet suivant <code>FAC</code>.</p>
<p>La routine qui suit dans le code étant la normalisation et arrondi d'un nombre, pas besoin de brancher, c'est terminée.</p>
<h4>Oui mais le signe ?</h4>
<p>Les opérations pour obtenir le signe du résultat final ne sont pas forcément évidente à suivre. Voici ce qu'il se passe :</p>
<ul>
<li>Dans le cas où on a utilisé l'<strong>addition des mantisses</strong>, le signe de <code>FAC</code> a été <strong>extrait</strong> de la <strong>première opérande</strong>.<ul>
<li>Dans le cas $a + b$, $a$ était positif, et le résultat est positif.</li>
<li>Dans le cas $-a-b$ qui est devenu $-(a+b)$, $a$ était négatif, et ce signe négatif sera hérité par le résultat. Transformant $a+b$ en $-(a+b)$.</li>
</ul>
</li>
<li>Dans le cas où on a utilisé la <strong>soustraction des mantisses</strong>, là encore, le signe de <code>FAC</code> a été <strong>extrait de la première opérande</strong>.<ul>
<li>Dans le cas $a - b$, le signe du résultat dépend de cette opération. Si $a > b$, <code>compl2</code> n'a pas été appelé et le résultat est positif. Dans le cas contraire, <code>compl2</code> a changé le bit de signe et le résultat est bien négatif, puisque la mantisse est positive.</li>
<li>Dans le cas $-a+b$, qui a été exécutée comme $-(a-b)$, initialement, le signe de <code>FAC</code> portait l'indication <strong>négatif</strong>, et le résultat de la soustraction l'aura, en fonction des deux cas, laissé comme tel, ou inversé.</li>
</ul>
</li>
</ul>
<h3>Re-codage final</h3>
<p>La routine qui suit, de <strong>normalisation</strong>, s'assure que le <strong>bit de poids</strong> fort est <strong>toujours 1</strong> (si ce n'est pas possible, c'est que le nombre est 0) et <strong>ajuste</strong> l'<strong>exposant</strong> en conséquence, puis <strong>arrondi</strong> le nombre. L'arrondi peut générer un <strong>dépassement de capacité</strong>.</p>
<p>L'addition n'a <strong>pas besoin</strong> de normalisation. Le résultat de l'addition des mantisses <strong>assure toujours</strong> que le bit de poids fort est 1 (au moins l'un des deux nombres était normalisé avec un bit de poids fort à 1, et si les deux l'étaient, alors cela a provoqué l'augmentation de l'exposant et le bit de poids fort est toujours 1, la retenu de 1 + 1 en binaire).</p>
<p>Le résultat de l'addition est à présent dans <code>FAC</code>, place à la suite des opérations...</p>Récréation 3D, Tortue Jeulin2020-01-29T00:00:00+01:002020-01-29T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2020-01-29:/recreation-3d-tortue-jeulin.html<p>Depuis quelques temps, je planche à mes heures perdues sur ma prochaine vidéo. Et le sujet est le langage de programmation <strong>Logo</strong>. Après avoir survolé le BASIC, cela me semblait une suite logique.</p>
<p>En me plongeant dans l'univers Logo, j'ai cherché à mieux connaître le robot qui y est associé …</p><p>Depuis quelques temps, je planche à mes heures perdues sur ma prochaine vidéo. Et le sujet est le langage de programmation <strong>Logo</strong>. Après avoir survolé le BASIC, cela me semblait une suite logique.</p>
<p>En me plongeant dans l'univers Logo, j'ai cherché à mieux connaître le robot qui y est associé : la <strong>tortue Jeulin</strong>. Et j'ai été très étonné de voir aussi peu de ressources dessus. Et pourtant, il semble qu'elle ne soit pas si rare chez les collectionneurs.</p>
<p>Grâce à l'aide de photographie envoyées sur le forum <strong>system-cfg</strong> par Fool-DupleX (merci à lui), j'ai tenté une modélisation. Ça m'a pris... un certain temps.</p>
<p>Le résultat n'est pas correct au milimètre, mais donne une relativement bonne idée.</p>
<p><img alt="Tortue Jeulin T2" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202001/20200127-Chassis-750.png">
<img alt="Tortue Jeulin T2" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/202001/20200129-Chassis-750.png"></p>VG5000µ, les hooks d'entrées/sorties2019-09-22T00:00:00+02:002019-09-22T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2019-09-22:/vg5000m-les-hooks-dentreessorties.html<p>Pour ces quatre nouveaux hooks, je ne suis pas très inspirés. Il s'agit de hook destiné au traitement des entrées sorties. Trois d'entre eux sont appelés lors d'une impression de caractères, le quatrième pour de l'acquisition.</p>
<h3>Routines en sorties</h3>
<p>Voici les trois premières :</p>
<ul>
<li>$47DF, <code>prthk</code> : début de commande PRINT.<ul>
<li>Est …</li></ul></li></ul><p>Pour ces quatre nouveaux hooks, je ne suis pas très inspirés. Il s'agit de hook destiné au traitement des entrées sorties. Trois d'entre eux sont appelés lors d'une impression de caractères, le quatrième pour de l'acquisition.</p>
<h3>Routines en sorties</h3>
<p>Voici les trois premières :</p>
<ul>
<li>$47DF, <code>prthk</code> : début de commande PRINT.<ul>
<li>Est appelé en tant que première instruction de l'exécution de l'instruction <code>PRINT</code>.</li>
<li>À ce moment là, <code>HL</code> pointe vers les arguments de <code>PRINT</code> et le flag <code>Z</code> est à 1 s'il n'y a rien dans ces arguments.</li>
<li>Si vous rendez la main à la routine, elle déroulement l'affichage.</li>
</ul>
</li>
<li>$47E2, <code>outhk</code> : début d'impression de caractère -> pour rerouter vers de nouvelles sorties<ul>
<li>Est appelé pour chaque caractère envoyé sur un périphérique de sortie (en <code>$3bd0</code>)</li>
<li>La variable système <code>(prtflg)</code> désigne le périphérique.</li>
<li>Le caractère à afficher est dans le registre <code>A</code>.</li>
<li>Attention, ce caractère est à comprendre par rapport aux modes d'affichage : est-ce qu'on est en caractères normaux ? semi-graphiques ? redéfinis par l'utilisateur ?</li>
</ul>
</li>
<li>$47E5, <code>crdhk</code> : début de retour chariot -> pour rerouter vers de nouvelles sorties<ul>
<li>Est appelé par chaque demande de retour à la ligne lors de l'émission des caractères sur le périphérique (en <code>$3c57</code>).</li>
<li>N'est pas appelé lors d'un retour chariot dans l'éditeur par contre.</li>
<li>La variable système <code>(prtflg)</code> désigne le périphérique.</li>
</ul>
</li>
</ul>
<p>La variable système (prtflg) désigne le périphérique en sortie selon les valeurs suivantes :</p>
<ul>
<li>0 - L'écran</li>
<li>1 - L'imprimante</li>
<li>255 - La cassette (en écriture)</li>
</ul>
<p>Les <strong>appels aux hooks</strong> se situent avant le tri vers les trois routines. La <strong>sélection</strong> ne se fait <strong>pas</strong> systématiquement <strong>dans le même ordre</strong>, ce qui signifie que les valeurs 2 à 254 n'envoient pas au même endroit pour les différentes fonctions.</p>
<p>Le <strong>retour chariot</strong> enverra sur l'imprimante et la sortie de caractère sur la cassette. <code>PRINT</code> ne considère que deux cas, l'imprimante et l'écran, et s'occupera de la position du chariot ou du curseur suivant le cas. Pour <strong>l'affichage</strong> en lui-même, c'est la routine d'envoi de caractère et de retour chariot qui sont utilisés.</p>
<p>Ainsi, si vous voulez supporter un <strong>nouveau périphérique</strong> en sortie, il faudra probablement effectuer un premier traitement au niveau du <code>PRINT</code> suivant si vous voulez être traité comme un écran ou une imprimante ; puis rendre la main, ou bien tout faire seul et jeter la première adresse de retour de la pile.</p>
<p>Du côté de la <strong>sortie de caractère</strong> et du <strong>retour chariot</strong>, il vous faudra prendre la main et jeter la première adresse de retour de la pile quoi qu'il arrive.</p>
<p>N'ayant pas de périphérique à tester (on pourrait imaginer une sortie série), je n'ai pas essayé.</p>
<h4>Précautions</h4>
<p>Pendant l'exécution d'une routine de sortie, n'appelez-pas les routines de la ROM qui elles-mêmes font de l'affichage. Vous vous appelleriez vous-même...</p>
<p>De toute façon, les routines d'affichages ne sont <strong>pas ré-entrantes</strong> et ne supportent pas d'être exécutées dans deux contextes simultanément. Cela explique pourquoi, si vous avez essayé comment moi d'utiliser des routines d'affichage pendant l'interruption d'affichage, des choses étranges se produisent.</p>
<p>En effet, si l'interruption à lieu pendant que la ROM est en train d'exécuter une de ces routines (un <code>PRINT</code> tout simplement), il y a de bonnes chances que cela se passe mal. Même si vous sauvez tous les registres. Le contexte est beaucoup plus gros que ça, avec des buffers de constructions de chaînes.</p>
<p>Mieux vaut avoir vos routines d'affichages spécialisées.</p>
<h3>Routine en entrée</h3>
<p>Pour l'entrée, il n'y a qu'un hook, celui de l'instruction <code>INPUT</code> :</p>
<ul>
<li>$47EB, <code>inphk</code> : début de commande INPUT<ul>
<li>Appelé en second lors de l'exécution de l'instruction BASIC <code>INPUT</code>, la première instruction étant la vérification que <code>INPUT</code> n'a pas été appelée en direct, hors programme.</li>
<li>Comme pour <code>PRINT</code>, <code>HL</code> pointe sur les arguments.</li>
<li>La variable système <code>(getflg)</code> désigne le périphérique en entrée. <code>0</code> pour le clavier, <code>255</code> pour la cassette. Il n'y a pas de périphérique écran en entrée...</li>
</ul>
</li>
</ul>
<p>Il est donc possible d'aller router l'<code>INPUT</code> vers un nouveau périphérique externe.</p>
<h3>Le manque d'idée...</h3>
<p>Oui... décidément, comme je n'ai pas de nouveau périphérique à router, je manque d'idée. Le programme que j'écris se contente donc de compter les appels aux différents <strong>hooks</strong> puis d'offrir une routine pour afficher les compteurs.</p>
<h4>Installation</h4>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">out_number</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">0726</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">out_str</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">36</span><span class="nv">aa</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">xcursor</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">4805</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">prthk</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">47</span><span class="nv">df</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">outhk</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">47</span><span class="nv">e2</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">crdhk</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">47</span><span class="nv">e5</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">inphk</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">47</span><span class="nv">eb</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">inthk</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">47</span><span class="nv">D0</span><span class="w"></span>
<span class="w"> </span><span class="k">org</span><span class="w"> </span><span class="kc">$</span><span class="mi">7</span><span class="nv">A00</span><span class="w"> </span><span class="c1">; Spécification de l'adresse mémoire d'implentation</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">AF</span><span class="w"> </span><span class="c1">; Sauvegarde des registres sur la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="nv">call_text</span><span class="w"> </span><span class="c1">; Affichage du CALL pour la routine d'Affichage</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_str</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="nv">call_routine</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_number</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,</span><span class="kc">$</span><span class="nv">C3</span><span class="w"> </span><span class="c1">; Mise en place des JP</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">prthk</span><span class="p">),</span><span class="w"> </span><span class="nv">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">outhk</span><span class="p">),</span><span class="w"> </span><span class="nv">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">crdhk</span><span class="p">),</span><span class="w"> </span><span class="nv">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inphk</span><span class="p">),</span><span class="w"> </span><span class="nv">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">HL</span><span class="p">,</span><span class="nv">prt_routine</span><span class="w"> </span><span class="c1">; Mise en place des adresses de saut</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">prthk</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span><span class="nv">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">HL</span><span class="p">,</span><span class="nv">out_routine</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">outhk</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span><span class="nv">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">HL</span><span class="p">,</span><span class="nv">cr_routine</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">crdhk</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span><span class="nv">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">HL</span><span class="p">,</span><span class="nv">inp_routine</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inphk</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span><span class="nv">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Restauration des registres depuis la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">AF</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="c1">; Retour au programme appelant</span><span class="w"></span>
<span class="nl">call_text:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">"CALL "</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">00</span><span class="w"></span>
</code></pre></div>
<p>Dans cette première partie, on effectue comme d'habitude l'installation des routines. Comme il y a <strong>quatre routines</strong> à mettre en place, on me d'un coup les quatre <code>JP</code> , puis dans la foulée les quatre adresses.</p>
<p>Mais auparavant, on affiche à l'écran un message indiquant l'instruction <code>CALL</code> à lancer pour provoquer l'affichage des compteurs.</p>
<p>Comme indiqué ci-dessus, appeler les <strong>routines d'affichages</strong> textes et nombres pendant une <strong>interruption</strong> ne <strong>fonctionne pas</strong> sans de nombreuses précautions. J'ai donc préféré offrir une <strong>routine</strong> qui <strong>affiche les compteurs</strong> sur un appel <strong>explicite</strong>. Et plutôt que de placer cet appel à une adresse fixe, elle est à la suite des autres routines. Son adresse étant variable en fonction des modifications des autres routines, je m'aide en affichant l'adresse à appeler.</p>
<h4>Comptage</h4>
<p>Le comptage se résume à quatre fois la même routine, à l'adresse de la variable de comptage près.</p>
<div class="highlight"><pre><span></span><code><span class="nl">prt_count:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="kc">$</span><span class="mi">0000</span><span class="w"></span>
<span class="nl">out_count:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="kc">$</span><span class="mi">0000</span><span class="w"></span>
<span class="nl">cr_count:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="kc">$</span><span class="mi">0000</span><span class="w"></span>
<span class="nl">inp_count:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="kc">$</span><span class="mi">0000</span><span class="w"></span>
<span class="nl">prt_routine:</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nv">prt_count</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">prt_count</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">out_routine:</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nv">out_count</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">out_count</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">cr_routine:</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nv">cr_count</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">cr_count</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">inp_routine:</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nv">inp_count</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inp_count</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>Au tout début, je réserve quatre emplacements de 16 bits pour les compteurs. Puis viennent les quatre routines qui chacune va augmenter le compteur associer lors d'un appel.</p>
<p>Prenons <code>prt_routine</code>. Tout d'abord, <code>HL</code> est <strong>sauvegardé</strong>. Comme d'habitude au début d'une instruction, ce registre <strong>pointe</strong> vers la <strong>ligne</strong> en train d'être <strong>exécutée</strong>, il est essentiel de préserver sa valeur.</p>
<p><code>HL</code> est ensuite <strong>chargé</strong> avec la <strong>valeur</strong> de compteur, cette valeur est <strong>incrémentée</strong> puis <strong>replacée</strong> dans le compteur.</p>
<p>On restaure <code>HL</code> et on revient. Vraiment simple.</p>
<h4>L'affichage des compteurs</h4>
<p>La dernière partie est une routine qui sera appelée explicitement par un <code>CALL</code> pour afficher la valeur des compteurs.</p>
<div class="highlight"><pre><span></span><code><span class="nl">call_routine:</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nv">xcursor</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">0020</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">xcursor</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nv">prt_count</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_number</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">0120</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">xcursor</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nv">out_count</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_number</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">0220</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">xcursor</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nv">cr_count</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_number</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">0320</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">xcursor</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nv">inp_count</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_number</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">xcursor</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>Vu qu'on appelle <code>out_number</code>, mieux vaut <strong>sauver</strong> pour commencer tous <strong>les registres</strong> (sauf IX et IY, mais ces registres sont particuliers pour la ROM). On les restaure en fin de routine.</p>
<p>Puis on <strong>récupère les coordonnées</strong> du curseur dans <code>HL</code>. Attention ici, l'adresse <code>xcursor</code> contient une valeur sur 8 bits. Elle est suivi par <code>ycursor</code> qui contient aussi une valeur sur 8 bits. Grâce au <code>LD HL, (xcursor)</code>, c'est donc les coordonnées <code>X</code> et <code>Y</code> qui sont chargées dans <code>HL</code> en une seule instruction.</p>
<p>Ces coordonnées seront elles-aussi restaurées en fin de routine. Pour remettre le curseur là où il se situait avant le <code>CALL</code>.</p>
<p>Vient ensuite une répétition de quatre fois la même séquence, aux valeurs près.</p>
<p>Tout d'abord, <code>HL</code> prend les coordonnées d'affichages du nombre que l'on va écrire. Dans la valeur sur 16 bits, <code>Y</code> arrive en premier, suivi de <code>X</code>. Ainsi <code>$0020</code> signifie ligne 0, colonne 32.</p>
<p>Cette <strong>position</strong> est placée dans la <strong>variable système</strong> <code>(xcursor)</code>. <code>HL</code> prend ensuite la valeur du <strong>compteur</strong> à afficher, et enfin <code>CALL out_number</code> se charger d'<strong>afficher</strong> le nombre entier contenu dans <code>HL</code> à la <strong>position courante</strong>.</p>
<h3>BASIC</h3>
<p>Voici un programme BASIC qui va monter la routine en mémoire. Suivi d'un <code>RUN</code> pour l'exécuter puis du <code>CALL</code> pour lancer le hook.</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="kr">CLEAR</span><span class="w"> </span><span class="il">50</span><span class="p">,</span><span class="o">&</span><span class="s2">"79FF"</span>
<span class="nl">20</span><span class="w"> </span><span class="vg">S</span><span class="o">=&</span><span class="s2">"7A00"</span>
<span class="nl">30</span><span class="w"> </span><span class="kr">READ</span><span class="w"> </span><span class="vg">A$</span>
<span class="nl">40</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">A$</span><span class="o">=</span><span class="s2">"FIN"</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">END</span>
<span class="nl">50</span><span class="w"> </span><span class="vg">A$</span><span class="o">=</span><span class="s2">"&"</span><span class="o">+</span><span class="kr">CHR$</span><span class="p">(</span><span class="il">34</span><span class="p">)</span><span class="o">+</span><span class="vg">A$</span><span class="o">+</span><span class="kr">CHR$</span><span class="p">(</span><span class="il">34</span><span class="p">)</span><span class="o">:</span><span class="vg">A</span><span class="o">=</span><span class="kr">VAL</span><span class="p">(</span><span class="vg">A$</span><span class="p">)</span>
<span class="nl">60</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="vg">S</span><span class="p">,</span><span class="vg">A</span>
<span class="nl">70</span><span class="w"> </span><span class="vg">S</span><span class="o">=</span><span class="vg">S</span><span class="o">+</span><span class="il">1</span>
<span class="nl">80</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">30</span>
<span class="nl">300</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="vg">F5</span><span class="p">,</span><span class="vg">E5</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">37</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">CD</span><span class="p">,</span><span class="vg">AA</span><span class="p">,</span><span class="il">36</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">6</span><span class="vg">D</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">CD</span><span class="p">,</span><span class="il">26</span><span class="p">,</span><span class="il">7</span><span class="p">,</span><span class="il">3</span><span class="vg">E</span><span class="p">,</span><span class="vg">C3</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="vg">DF</span><span class="p">,</span><span class="il">47</span><span class="p">,</span><span class="il">32</span>
<span class="nl">310</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="vg">E2</span><span class="p">,</span><span class="il">47</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="vg">E5</span><span class="p">,</span><span class="il">47</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="vg">EB</span><span class="p">,</span><span class="il">47</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">45</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="vg">E0</span><span class="p">,</span><span class="il">47</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">4</span><span class="vg">F</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="vg">E3</span><span class="p">,</span><span class="il">47</span>
<span class="nl">320</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">21</span><span class="p">,</span><span class="il">59</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="vg">E6</span><span class="p">,</span><span class="il">47</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">63</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="vg">EC</span><span class="p">,</span><span class="il">47</span><span class="p">,</span><span class="vg">E1</span><span class="p">,</span><span class="vg">F1</span><span class="p">,</span><span class="vg">C9</span><span class="p">,</span><span class="il">43</span><span class="p">,</span><span class="il">41</span><span class="p">,</span><span class="il">4</span><span class="vg">C</span><span class="p">,</span><span class="il">4</span><span class="vg">C</span><span class="p">,</span><span class="il">20</span>
<span class="nl">330</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">0</span><span class="p">,</span><span class="il">0</span><span class="p">,</span><span class="il">0</span><span class="p">,</span><span class="il">0</span><span class="p">,</span><span class="il">0</span><span class="p">,</span><span class="il">0</span><span class="p">,</span><span class="il">0</span><span class="p">,</span><span class="il">0</span><span class="p">,</span><span class="il">0</span><span class="p">,</span><span class="vg">E5</span><span class="p">,</span><span class="il">2</span><span class="vg">A</span><span class="p">,</span><span class="il">3</span><span class="vg">D</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">23</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="il">3</span><span class="vg">D</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">E1</span><span class="p">,</span><span class="vg">C9</span><span class="p">,</span><span class="vg">E5</span>
<span class="nl">340</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">2</span><span class="vg">A</span><span class="p">,</span><span class="il">3</span><span class="vg">F</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">23</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="il">3</span><span class="vg">F</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">E1</span><span class="p">,</span><span class="vg">C9</span><span class="p">,</span><span class="vg">E5</span><span class="p">,</span><span class="il">2</span><span class="vg">A</span><span class="p">,</span><span class="il">41</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">23</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="il">41</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">E1</span><span class="p">,</span><span class="vg">C9</span><span class="p">,</span><span class="vg">E5</span>
<span class="nl">350</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">2</span><span class="vg">A</span><span class="p">,</span><span class="il">43</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">23</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="il">43</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">E1</span><span class="p">,</span><span class="vg">C9</span><span class="p">,</span><span class="vg">E5</span><span class="p">,</span><span class="vg">C5</span><span class="p">,</span><span class="vg">D5</span><span class="p">,</span><span class="vg">F5</span><span class="p">,</span><span class="il">2</span><span class="vg">A</span><span class="p">,</span><span class="il">5</span><span class="p">,</span><span class="il">48</span><span class="p">,</span><span class="vg">E5</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">20</span><span class="p">,</span><span class="il">0</span>
<span class="nl">360</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">22</span><span class="p">,</span><span class="il">5</span><span class="p">,</span><span class="il">48</span><span class="p">,</span><span class="il">2</span><span class="vg">A</span><span class="p">,</span><span class="il">3</span><span class="vg">D</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">CD</span><span class="p">,</span><span class="il">26</span><span class="p">,</span><span class="il">7</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">20</span><span class="p">,</span><span class="il">1</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="il">5</span><span class="p">,</span><span class="il">48</span><span class="p">,</span><span class="il">2</span><span class="vg">A</span><span class="p">,</span><span class="il">3</span><span class="vg">F</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">CD</span><span class="p">,</span><span class="il">26</span>
<span class="nl">370</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">7</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">20</span><span class="p">,</span><span class="il">2</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="il">5</span><span class="p">,</span><span class="il">48</span><span class="p">,</span><span class="il">2</span><span class="vg">A</span><span class="p">,</span><span class="il">41</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">CD</span><span class="p">,</span><span class="il">26</span><span class="p">,</span><span class="il">7</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">20</span><span class="p">,</span><span class="il">3</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="il">5</span><span class="p">,</span><span class="il">48</span><span class="p">,</span><span class="il">2</span><span class="vg">A</span>
<span class="nl">380</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">43</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">CD</span><span class="p">,</span><span class="il">26</span><span class="p">,</span><span class="il">7</span><span class="p">,</span><span class="vg">E1</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="il">5</span><span class="p">,</span><span class="il">48</span><span class="p">,</span><span class="vg">F1</span><span class="p">,</span><span class="vg">D1</span><span class="p">,</span><span class="vg">C1</span><span class="p">,</span><span class="vg">E1</span><span class="p">,</span><span class="vg">C9</span>
<span class="nl">1000</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="vg">FIN</span>
<span class="kr">RUN</span>
<span class="kr">CALL</span><span class="w"> </span><span class="o">&</span><span class="s2">"7A00"</span>
</code></pre></div>
<p>Pour éviter d'avoir à tout rentrer au clavier, voici le <a href="https://www.triceraprog.fr/files/201909/hk_io.k7">fichier .k7</a>. À charger avec <code>CLOAD</code>, suivi d'un <code>RUN</code> et du <code>CALL &"7A00"</code>.</p>
<p>Une fois le programme chargé et les routines installées via le <code>CALL</code>, vous pouvez regarder les effets sur les compteurs avec un programme similaire à celui qui suit (pensez à faire un <code>NEW</code> pour partir à vide) :</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="kr">CALL</span><span class="w"> </span><span class="vg">xxxxx</span>
<span class="nl">20</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="s2">"HELLO"</span>
<span class="nl">30</span><span class="w"> </span><span class="kr">INPUT</span><span class="w"> </span><span class="vg">A$</span>
<span class="nl">40</span><span class="w"> </span><span class="kr">INPUT</span><span class="w"> </span><span class="vg">A$</span>
<span class="nl">50</span><span class="w"> </span><span class="kr">CALL</span><span class="w"> </span><span class="vg">xxxxx</span>
</code></pre></div>
<p>En remplaçant bien entendu les <code>xxxxx</code> par la valeur indiquée lors du <code>CALL &"7A00"</code></p>
<h3>Le résultat</h3>
<p>Et voici le résultat d'une session comme indiquée ci-dessus.</p>
<p><img alt="Un CALL avec paramètres" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201909/VG5000-Hook-IO.png"></p>VG5000µ, les hooks d'appel2019-09-09T00:00:00+02:002019-09-09T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2019-09-09:/vg5000m-les-hooks-dappel.html<p>Dans la série des <a href="https://www.triceraprog.fr/tag/hooks.html">hooks</a> sur <a href="https://www.triceraprog.fr/tag/vg5000.html">VG5000µ</a>, voyons en cette fois la paire probablement la plus simple. Ça sera donc rapide.</p>
<h3>Le hook <em>CALL</em></h3>
<p>Du nom de <code>calhk</code> et d'adresse <code>$47D3</code>, ce hook est utilisé en interne par la commande BASIC <code>CALL</code>.</p>
<p>Le code de cette instruction est extrêmement <strong>simple …</strong></p><p>Dans la série des <a href="https://www.triceraprog.fr/tag/hooks.html">hooks</a> sur <a href="https://www.triceraprog.fr/tag/vg5000.html">VG5000µ</a>, voyons en cette fois la paire probablement la plus simple. Ça sera donc rapide.</p>
<h3>Le hook <em>CALL</em></h3>
<p>Du nom de <code>calhk</code> et d'adresse <code>$47D3</code>, ce hook est utilisé en interne par la commande BASIC <code>CALL</code>.</p>
<p>Le code de cette instruction est extrêmement <strong>simple</strong> :</p>
<div class="highlight"><pre><span></span><code><span class="nl">inst_call:</span><span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">eval_num_ex</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">deint_impl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">c3</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">calhk</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">calhk</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">calhk</span><span class="w"></span>
</code></pre></div>
<p>En premier, l'argument passé à <code>CALL</code> est évalué, puis est ensuite <strong>transformé</strong> en entier sur 16 bits.</p>
<p>Cette adresse, précédée par l'opcode pour <code>JP</code> à une <strong>adresse absolue</strong> 16 bits est placée dans le hook <code>calhk</code>. La routine saute enfin vers cette adresse, qui agit comme un tremplin vers l'adresse indiquée à l'instruction <code>CALL</code>.</p>
<p>Comme c'est le <code>RET</code> de la routine appelée qui fera office de <code>RET</code> pour l'ensemble de l'instruction <code>CALL</code>, la préservation de l'environnement est à la charge de la routine appelée. Essentiellement, il vous faut préserver <code>HL</code>, qui pointe sur la fin de l'instruction. Si vous ne le faite pas, il y a de bonnes chances que vous obteniez un <code>Erreur de Syntaxe</code> en retour d'instruction.</p>
<p>Pour un exemple d'utilisation, voyez les <a href="https://www.triceraprog.fr/tag/hooks.html">articles précédents</a>, qui montent une routine assembleur en mémoire puis l’appellent.</p>
<h4>Récupérer des paramètres</h4>
<p>Que HL pointe juste après le <code>CALL</code> se révèle <strong>pratique</strong> pour récupérer des <strong>arguments</strong> potentiels. Dans l'article sur les <a href="https://www.triceraprog.fr/vg5000m-les-hooks-de-peripheriques.html">hooks de périphériques</a>, j'étais allé chercher un argument de type chaîne de caractères. Cette fois, je vais aller chercher <strong>trois arguments</strong> de type nombre. Le premier entier sur <strong>8 bits</strong>, le second sur <strong>16 bits</strong>, et le troisième un nombre quelconque (dans les limites du VG5000µ).</p>
<p>Les deux premiers arguments seront <strong>obligatoires</strong>, le troisième <strong>optionnel</strong>.</p>
<p>Grâce à ça, il est possible d'ajouter des commandes au BASIC, sans leur donner un nom cependant.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">out_number</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">0726</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">out_fp</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">0731</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">deint_impl</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">2552</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">eval_num_ex</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">284</span><span class="nv">d</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">type_eq_num</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">2850</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">read_expr</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">2861</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">getbyt_impl</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">2</span><span class="nv">aa5</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">out_str</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">36</span><span class="nv">aa</span><span class="w"></span>
<span class="w"> </span><span class="k">org</span><span class="w"> </span><span class="kc">$</span><span class="mi">7</span><span class="nv">A00</span><span class="w"> </span><span class="c1">; Spécification de l'adresse mémoire d’implantation</span><span class="w"></span>
<span class="nl">cmd_routine:</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="kc">$</span><span class="mi">08</span><span class="w"> </span><span class="c1">; Vérification obligatoire du caractère qui suit</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="s">','</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">getbyt_impl</span><span class="w"> </span><span class="c1">; Récupération d'un entier 8 bits dans A</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">C</span><span class="p">,</span><span class="nv">A</span><span class="w"> </span><span class="c1">; Sauvegarde du premier paramètre dans C</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">B</span><span class="p">,</span><span class="kc">$</span><span class="mi">00</span><span class="w"> </span><span class="c1">; BC contient le premier paramètre</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="kc">$</span><span class="mi">08</span><span class="w"> </span><span class="c1">; Vérification obligatoire du caractère qui suit</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="s">','</span><span class="w"> </span><span class="c1">; La virgule de séparation</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">BC</span><span class="w"> </span><span class="c1">; Sauvegarde du premier paramètre dans la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">eval_num_ex</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">deint_impl</span><span class="w"> </span><span class="c1">; Lecture d'un entier signé 16 bits dans DE</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">DE</span><span class="w"> </span><span class="c1">; Sauvegarde du second paramètre dans la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,</span><span class="s">'('</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="kc">$</span><span class="mi">18</span><span class="w"> </span><span class="c1">; Affichage de la parenthèse ouvrante</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="p">(</span><span class="nb">SP</span><span class="p">),</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Récupération du deuxième paramètre, sauvegarde du pointeur</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_number</span><span class="w"> </span><span class="c1">; Affiche le contenu de HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,</span><span class="s">','</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="kc">$</span><span class="mi">18</span><span class="w"> </span><span class="c1">; Affichage de la virgule</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="p">(</span><span class="nb">SP</span><span class="p">),</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Récupération du premier paramètre, sauvegarde du pointeur</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_number</span><span class="w"> </span><span class="c1">; Affiche le contenu de HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Récupération du pointeur d'exécution</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="kc">$</span><span class="mi">10</span><span class="w"> </span><span class="c1">; Lecture du caractère suivant</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">Z</span><span class="p">,</span><span class="nv">no_third</span><span class="w"> </span><span class="c1">; Fin de la ligne, il n'y a pas de troisième paramètre</span><span class="w"></span>
<span class="w"> </span><span class="nf">dec</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Sinon, on revient en arrière pour vérifier le caractère suivant</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="kc">$</span><span class="mi">08</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="s">','</span><span class="w"> </span><span class="c1">; Qui doit être une virgule</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,</span><span class="s">','</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="kc">$</span><span class="mi">18</span><span class="w"> </span><span class="c1">; Affichage de la virgule</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">read_expr</span><span class="w"> </span><span class="c1">; Lecture du troisième paramètre comme une expression</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Sauvegarde du pointeur d’exécution</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">type_eq_num</span><span class="w"> </span><span class="c1">; Vérification du type de paramètre (numérique)</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_fp</span><span class="w"> </span><span class="c1">; Construction de la chaîne de caractères correspondant au nombre</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_str</span><span class="w"> </span><span class="c1">; Affichage de la chaîne</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Restauration du pointeur d’exécution</span><span class="w"></span>
<span class="nl">no_third:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,</span><span class="s">')'</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="kc">$</span><span class="mi">18</span><span class="w"> </span><span class="c1">; Affichage de la parenthèse fermante</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>C'est <strong>assez long</strong>, il y a peut-être plus optimisé, le principal ici est que la suite d'instructions soit <strong>lisible</strong> et que les morceaux différents allant chercher les arguments puissent être pris <strong>indépendamment</strong> pour <strong>réutilisation</strong>.</p>
<p>Le code est commenté, mais nécessite peut-être quelques autres éclaircissement :</p>
<ul>
<li><code>rst $08</code> <strong>vérifie</strong> que le caractère codé juste après le <code>RST</code> est pointé par <code>HL</code>. Si ce n'est pas le cas, une erreur de syntaxe est levée. Le <code>DEFB</code> qui suit le <code>RST</code> est bien entendu sauté, le retour de la routine se fait juste après. La ROM se sert beaucoup de cette séquence pour vérifier la syntaxe de commandes et d'expressions.</li>
<li><code>rst $18</code> <strong>affiche</strong> le caractère présent dans A.</li>
<li><code>read_expr</code> <strong>évalue</strong> une <strong>expression</strong> de n'importe quel type et met le résultat dans l'accumulateur flottant.</li>
<li><code>eval_num_ex</code> lire une expression (via <code>read_expr</code>) et vérifie dans la foulée si elle est <strong>numérique</strong>.</li>
<li><code>getbyt_impl</code> appelle <code>eval_num_ex</code>, la converti en <strong>entier</strong>, et vérifie que le résultat tient sur <strong>8 bits</strong>. Le résultat est dans A.</li>
<li><code>deint_impl</code> effectue une troncature entière sur <strong>16 bits</strong> du nombre présent dans l'accumulateur flottant. Le résultat est dans DE.</li>
<li><code>out_number</code> <strong>affiche</strong> le nombre (entier positif) présent dans HL.</li>
<li><code>out_fp</code> écrit dans un buffer temporaire une chaîne représentant le <strong>nombre</strong> présent dans l'<strong>accumulateur flottant</strong>. Le pointeur vers la chaîne est renvoyé dans <code>HL</code> et la chaîne se termine par <code>0</code>.</li>
<li><code>out_str</code> <strong>affiche</strong> la chaîne pointée par <code>HL</code> se terminant par <code>0</code>.</li>
</ul>
<p>On notera au passage que lors de l'affichage, les deux premiers paramètres sont inversés... c'était juste plus simple à écrire.</p>
<h3>Le hook <code>RST</code></h3>
<p>Le second hook d'appel est <code>rsthk</code>, à l'adresse <code>$47DC</code>. Son fonctionnement n'est pas atteignable depuis le BASIC.</p>
<p>La fonction <code>RST</code> du Z80 est une sorte de <code>CALL</code> sur 1 octet. Il permet de brancher à une série de 8 adresses prédéfinies : $0000, $0008, $0010, $0018, $0020, $0028, $0030, $0038. Suivant la syntaxe de l'assembleur, soit l'adresse, soit le numéro du <em>restart</em> (entre 0 et 7) est accepté.</p>
<p>Toutes ces adresses sont allouées à des <strong>fonctions souvent appelées</strong>, cela permet de gagner de la place. Comme il n'y a pas beaucoup d'instructions possibles entre deux adresses de <em>restart</em>, la routine est souvent placée ailleurs, et certains emplacements sont remplis avec des données.</p>
<p>Par exemple, entre <code>RST $00</code>, qui fait un démarrage complet de la machine et <code>RST $08</code>, qui fait un test de syntaxe, se situe la chaîne ".1.1" pour la ROM 1.1. C'est cette chaîne qui est affichée au démarrage de la machine.</p>
<h4>Les différents RST</h4>
<p>Puisqu'on y est, voici les différents RST sur VG5000µ, brièvement.</p>
<ul>
<li>$00 : redémarrage complet de la machine</li>
<li>$08 : vérification de la présence d'un caractère pointé par <code>HL</code>, ou lancement d'une erreur de syntaxe (pour vérifier une virgule entre deux arguments par exemple)</li>
<li>$10 : acquisition d'un caractère depuis la chaîne pointée par <code>HL</code> (avec quelques flags concernant sa nature, et en sautant les espaces)</li>
<li>$18 : envoie d'un caractère sur le périphérique sélectionné (écran, imprimante, modem)</li>
<li>$20 : comparaison de <code>HL</code> et <code>DE</code>, qui a lieu très souvent.</li>
<li>$28 : renvoie -!, 0 ou 1 en fonction du signe de l'accumulateur flottant</li>
<li>$30 : libre</li>
<li>$38 : vecteur d'interruption (utilisé par l'affichage)</li>
</ul>
<h3>Un <code>RST</code> libre</h3>
<p>Le <em>restart</em> <code>$30</code> est donc libre, et son code est le suivant :</p>
<div class="highlight"><pre><span></span><code><span class="nl">usrrst:</span><span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">rsthk</span><span class="w"></span>
</code></pre></div>
<p>Net et précis. Il suffit donc d'ajouter en <code>rsthk</code> un <code>JP</code> suivi d'une adresse absolue, et vous pouvez alors utiliser <code>RST $30</code> dans vos routines pour un appel très fréquent.</p>
<p>Cependant, il y a une limite : il n'existe qu'un seul hook libre pour tout le monde. À garder en tête si vous mélanger des routines qui veulent chacune utiliser cette instruction.</p>
<p>À noter aussi que si <code>RST $30</code> ne prend qu'un octet dans votre routine, le saut utilise deux indirections pour un total de 31 <em>T States</em> contre 17 pour un <code>CALL</code>.</p>
<h2>Le résultat</h2>
<p>Voici le résultat de l'appel de la routine via <code>CALL</code> avec décodage de paramètre.</p>
<p><img alt="Un CALL avec paramètres" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201909/VG5000-Call-Params.png"></p>VG5000µ, les hooks de périphériques2019-09-05T00:00:00+02:002019-09-05T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2019-09-05:/vg5000m-les-hooks-de-peripheriques.html<p>L'<a href="https://www.triceraprog.fr/vg5000m-les-hooks.html">article précédent</a> présentait un <em>accrochage</em> sur un <em>hook</em> d'interruption. Dans cet article, je vais regarder du côté des hooks de commandes de <strong>périphériques</strong>.</p>
<p>Ces <strong>hooks</strong> sont initialisés différements des 10 premiers, selon le code qui suit :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">c3</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inst_lpen</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inst_disk</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inst_modem</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld …</span></code></pre></div><p>L'<a href="https://www.triceraprog.fr/vg5000m-les-hooks.html">article précédent</a> présentait un <em>accrochage</em> sur un <em>hook</em> d'interruption. Dans cet article, je vais regarder du côté des hooks de commandes de <strong>périphériques</strong>.</p>
<p>Ces <strong>hooks</strong> sont initialisés différements des 10 premiers, selon le code qui suit :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">c3</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inst_lpen</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inst_disk</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inst_modem</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">no_device</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="kc">$</span><span class="mi">47</span><span class="nv">f2</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="kc">$</span><span class="mi">47</span><span class="nv">f5</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="kc">$</span><span class="mi">47</span><span class="nv">f8</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
</code></pre></div>
<p>L'instruction placée au début de chaque vecteur de redirection est un <code>JP</code> et l'adresse par défaut est celle d'une routine indiquant que le <strong>périphérique</strong> n'est <strong>pas géré</strong>.</p>
<p>Il s'agit d'une <strong>extension de commandes</strong> mise à disposition par le VG5000µ, permettant de traiter les commandes <code>LPEN</code>, <code>MODEM</code> et <code>DISK</code>. Il est d'ailleurs amusant de voir que dans le <strong>manuel d'utilisation</strong>, ces trois commandes sont mentionnées dans le rappel des instructions reconnues, mais qu'elles ne sont pas décrites.</p>
<p>Dans la table des <strong>instructions</strong>, le décodage de ces tokens envoie <strong>directement</strong> sur chacun des trois <strong>vecteurs</strong>, sans traitement particulier. C'est donc du code de décodage de paramètres qu'il faut écrire si l'on veut traiter ces instructions.</p>
<h3>Mise en place de la routine</h3>
<p>Le code est <strong>similaire</strong> à celui de la mise en place de la routine sur l'interruption. Seule l'adresse du <em>hook</em> change.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">dskhk</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">47</span><span class="nv">f4</span><span class="w"> </span><span class="c1">; Adresse du hook</span><span class="w"></span>
<span class="w"> </span><span class="k">org</span><span class="w"> </span><span class="kc">$</span><span class="mi">7</span><span class="nv">A00</span><span class="w"> </span><span class="c1">; Spécification de l'adresse mémoire d'implémentation</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">AF</span><span class="w"> </span><span class="c1">; Sauvegarde des registres sur la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,</span><span class="kc">$</span><span class="nv">C3</span><span class="w"> </span><span class="c1">; Mise en place de la routine sur le HOOK</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">dskhk</span><span class="p">),</span><span class="nv">A</span><span class="w"> </span><span class="c1">; Il y a normalement déjà un 'JP' ici, mais on s'en assure</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">HL</span><span class="p">,</span><span class="nv">cmd_routine</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">dskhk</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span><span class="nv">HL</span><span class="w"> </span><span class="c1">; l'adresse de la routine</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Restauration des registres depuis la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">AF</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="c1">; Retour au programme appelant</span><span class="w"></span>
<span class="nl">cmd_routine:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>Passons donc rapidement sur la partie la plus intéressante : le traitement de l'instruction.</p>
<h3>Traitement de l'instruction</h3>
<p>Lorsque l'<strong>interpréteur</strong> appelle la <strong>routine</strong> associée à une <strong>instruction</strong>, plusieurs choses sont mises en place. Les principales sont :</p>
<ul>
<li><code>HL</code> pointe vers la chaîne en train d'être interprétée, sur le prochain caractère à lire,</li>
<li>Le flag "Z" est à 1 si l'on est à la fin de la chaîne,</li>
<li>L'adresse de retour sur la pile est positionnée pour traiter l'instruction suivante.</li>
</ul>
<p>Le contrat en sortie est :</p>
<ul>
<li><code>HL</code> doit pointer sur le caractère après le dernier consommé par l'instruction,</li>
<li>S'il s'agit d'une fonction, son résultat doit être dans l'accumulateur flottant (numérique, ou pointeur de chaîne).</li>
</ul>
<p>Pour s'amuser avec le paramètre, je vais écrire le <strong>décodage</strong> d'un paramètre de <strong>type chaîne</strong>, qui sera ensuite <strong>affiché à l'écran</strong>, suivi par un message.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">type_eq_str</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">2851</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">read_expr</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">2861</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">out_str</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">36</span><span class="nv">aa</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">out_str1</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">36</span><span class="nv">ad</span><span class="w"></span>
<span class="nl">cmd_routine:</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Sauvegarde du pointeur d'exécution</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">read_expr</span><span class="w"> </span><span class="c1">; Lecture de l'expression suivant la commande</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="p">(</span><span class="nb">sp</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; Remplacement de la valeur du pointeur d'exécution</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">type_eq_str</span><span class="w"> </span><span class="c1">; Vérification du type du paramètre</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_str1</span><span class="w"> </span><span class="c1">; Affichage de la chaîne</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="nv">answer</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">out_str</span><span class="w"> </span><span class="c1">; Affichage du message de la commande</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Récupération du pointeur d'exécution</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">answer:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">" <-- PARAM"</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">00</span><span class="w"></span>
</code></pre></div>
<p>La structure est assez simple, car les routines nécessaires sont toutes disponibles dans la ROM.</p>
<p>Tout d'abord, on <strong>sauve</strong> le pointeur vers la <strong>chaîne interprétée</strong>. <code>HL</code> est actuellement sur le début du paramètre de <code>DISK</code> (en tout cas, ce qui suit).</p>
<p>L'appel à <code>read_expr</code> se charge de <strong>lire une expression valide</strong> (ou échouer avec une erreur). À la sortie de la routine, <code>HL</code> est positionné à la fin de ce qui a été consommé par l'expression.</p>
<p>Ce pointeur est celui qu'il faudra <strong>conserver</strong> pour l’interpréteur. Du coup, on échange la valeur précédente de <code>HL</code> actuellement sur la pile avec la nouvelle valeur. Hop, le tour est joué.</p>
<p>L'appel à <code>type_eq_str</code> vérifie si le <strong>type</strong> de l'<strong>expression</strong> est bien une <strong>chaîne</strong>, et affiche un message d'erreur sinon (et dans ce cas l'interprétation est arrêtée immédiatement).</p>
<p>Si c'est bien une chaîne, l'appel à <code>out_str1</code> <strong>affiche</strong> le résultat de l'expression de type chaîne de caractères qui vient d'être évaluée.</p>
<p>L'appel à <code>out_str</code> qui suit affiche la chaîne terminée par un <code>0</code> définie dans le programme.</p>
<p><strong>Et voilà</strong>, voir le résultat à la fin de l'article.</p>
<h3>BASIC</h3>
<p>Voici un programme <strong>BASIC</strong> pour charger et lancer la routine.</p>
<div class="highlight"><pre><span></span><code><span class="mf">10</span><span class="w"> </span><span class="n">CLEAR</span><span class="w"> </span><span class="mf">50</span><span class="p">,</span><span class="err">&</span><span class="s">"79FF"</span><span class="w"></span>
<span class="mf">20</span><span class="w"> </span><span class="n">S</span><span class="o">=</span><span class="err">&</span><span class="s">"7A00"</span><span class="w"></span>
<span class="mf">30</span><span class="w"> </span><span class="kr">READ</span><span class="w"> </span><span class="n">A$</span><span class="w"></span>
<span class="mf">40</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="n">A$</span><span class="o">=</span><span class="s">"FIN"</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">END</span><span class="w"></span>
<span class="mf">50</span><span class="w"> </span><span class="n">A$</span><span class="o">=</span><span class="s">"&"</span><span class="o">+</span><span class="nb">CHR$</span><span class="p">(</span><span class="mf">34</span><span class="p">)</span><span class="o">+</span><span class="n">A$</span><span class="o">+</span><span class="nb">CHR$</span><span class="p">(</span><span class="mf">34</span><span class="p">):</span><span class="n">A</span><span class="o">=</span><span class="nb">VAL</span><span class="p">(</span><span class="n">A$</span><span class="p">)</span><span class="w"></span>
<span class="mf">60</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="n">S</span><span class="p">,</span><span class="n">A</span><span class="w"></span>
<span class="mf">70</span><span class="w"> </span><span class="n">S</span><span class="o">=</span><span class="n">S</span><span class="o">+</span><span class="mf">1</span><span class="w"></span>
<span class="mf">80</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="mf">30</span><span class="w"></span>
<span class="mf">300</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="n">F5</span><span class="p">,</span><span class="n">E5</span><span class="p">,</span><span class="mf">3</span><span class="n">E</span><span class="p">,</span><span class="n">C3</span><span class="p">,</span><span class="mf">32</span><span class="p">,</span><span class="n">F4</span><span class="p">,</span><span class="mf">47</span><span class="p">,</span><span class="mf">21</span><span class="p">,</span><span class="mf">10</span><span class="p">,</span><span class="mf">7</span><span class="n">A</span><span class="p">,</span><span class="mf">22</span><span class="p">,</span><span class="n">F5</span><span class="p">,</span><span class="mf">47</span><span class="p">,</span><span class="n">E1</span><span class="p">,</span><span class="n">F1</span><span class="p">,</span><span class="n">C9</span><span class="p">,</span><span class="n">E5</span><span class="p">,</span><span class="n">CD</span><span class="p">,</span><span class="mf">61</span><span class="p">,</span><span class="mf">28</span><span class="w"></span>
<span class="mf">310</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="n">E3</span><span class="p">,</span><span class="n">CD</span><span class="p">,</span><span class="mf">51</span><span class="p">,</span><span class="mf">28</span><span class="p">,</span><span class="n">CD</span><span class="p">,</span><span class="n">AD</span><span class="p">,</span><span class="mf">36</span><span class="p">,</span><span class="mf">21</span><span class="p">,</span><span class="mf">23</span><span class="p">,</span><span class="mf">7</span><span class="n">A</span><span class="p">,</span><span class="n">CD</span><span class="p">,</span><span class="n">AA</span><span class="p">,</span><span class="mf">36</span><span class="p">,</span><span class="n">E1</span><span class="p">,</span><span class="n">C9</span><span class="p">,</span><span class="mf">20</span><span class="p">,</span><span class="mf">3</span><span class="n">C</span><span class="p">,</span><span class="mf">2</span><span class="n">D</span><span class="p">,</span><span class="mf">2</span><span class="n">D</span><span class="p">,</span><span class="mf">20</span><span class="w"></span>
<span class="mf">320</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="mf">50</span><span class="p">,</span><span class="mf">41</span><span class="p">,</span><span class="mf">52</span><span class="p">,</span><span class="mf">41</span><span class="p">,</span><span class="mf">4</span><span class="n">D</span><span class="p">,</span><span class="mf">0</span><span class="w"></span>
<span class="mf">1000</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="n">FIN</span><span class="w"></span>
<span class="kr">RUN</span><span class="w"></span>
<span class="n">CALL</span><span class="w"> </span><span class="err">&</span><span class="s">"7A00"</span><span class="w"></span>
</code></pre></div>
<p>Pour éviter d'avoir à tout rentrer au clavier, voici le <a href="https://www.triceraprog.fr/files/201909/hk_disk.k7">fichier .k7</a>. À charger avec <code>CLOAD</code>, suivi d'un <code>RUN</code> et du <code>CALL &"7A00"</code>.</p>
<h2>Le résultat</h2>
<p><img alt="La commande DISK" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201909/VG5000-Hook-Disk.png"></p>VG5000µ, les hooks2019-08-31T00:00:00+02:002019-08-31T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2019-08-31:/vg5000m-les-hooks.html<p>Pour cet article, nous allons <strong>laisser de côté</strong> la partie BASIC-80 pour regarder du côté d'un fonctionnement spécifique au VG5000µ. Pas que le principe soit original, il est présent dans de nombreuses machines, mais que les capacités sont diverses suivant les différentes machines.</p>
<p>Les « <strong>hooks</strong> » (la traduction de « crochet » me …</p><p>Pour cet article, nous allons <strong>laisser de côté</strong> la partie BASIC-80 pour regarder du côté d'un fonctionnement spécifique au VG5000µ. Pas que le principe soit original, il est présent dans de nombreuses machines, mais que les capacités sont diverses suivant les différentes machines.</p>
<p>Les « <strong>hooks</strong> » (la traduction de « crochet » me semble un peu hasardeuse, une « accroche » me semble un peu meilleur) est un moyen qu'offre le système pour <strong>intervenir</strong> lors de <strong>certaines opérations</strong> en augmentant le fonctionnement de la ROM. En y mettant son grain de sel en quelque sorte.</p>
<p>Plus prosaïquement, les « hooks » sont des <strong>appels</strong> à des <strong>adresses précises</strong>, en RAM, à des routines qui peuvent être modifiées. Il peut s'agir aussi sur d'autres machines de récupérer dans une variable système une adresse d'indirection. Sur le VG5000µ, toutes les adresses de « hooks » sont fixes.</p>
<p>Les « hooks » sont parfois aussi appelés <strong>vecteurs d'indirection</strong>. Ou bien tout simplement <em>vecteurs</em>.</p>
<h3>L'initialisation des « hooks »</h3>
<p><strong>L'initialisation</strong> des « hooks » arrive très tôt dans l'initialisation de la machine, juste après la détection de la mémoire disponible.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">c9</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">inthk</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="kc">$</span><span class="mi">1</span><span class="nv">e</span><span class="w"></span>
<span class="nl">hk_ini_lop:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">djnz</span><span class="w"> </span><span class="nv">hk_ini_lop</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">c3</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inst_lpen</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inst_disk</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inst_modem</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">no_device</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="kc">$</span><span class="mi">47</span><span class="nv">f2</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="kc">$</span><span class="mi">47</span><span class="nv">f5</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="kc">$</span><span class="mi">47</span><span class="nv">f8</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">resetlang</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="kc">$</span><span class="mi">47</span><span class="nv">ef</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">nmihk</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
</code></pre></div>
<p><code>A</code> prend la valeur <code>C9</code>, qui est le code pour l'instruction <code>RET</code> sur Z80. <code>HL</code> est initialisé à <code>inthk</code>, qui est le premier hook d'une plage consécutive. Et <code>B</code> prend <code>$1e</code>, la taille de cette plage.</p>
<p>La <strong>première étape</strong> et de remplir cette plage avec des <code>RET</code>. Cette plage contient les <strong>10 premiers hooks, que sont les suivants</strong>.</p>
<ul>
<li>$47D0, <code>inthk</code> : interruption masquable</li>
<li>$47D3, <code>calhk</code> : vecteur <code>CALL</code></li>
<li>$47D6, <code>sonhk</code> : vecteur de générateur de son</li>
<li>$47D9, <code>plyhk</code> : début de commande PLAY</li>
<li>$47DC, <code>rsthk</code> : vecteur pour l'instruction RST utilisateur</li>
<li>$47DF, <code>prthk</code> : début de commande PRINT</li>
<li>$47E2, <code>outhk</code> : début d'impression de caractère</li>
<li>$47E5, <code>crdhk</code> : début de retour chariot</li>
<li>$47E8, <code>inlhk</code> : début de lecture d'une ligne</li>
<li>$47EB, <code>inphk</code> : début de commande INPUT</li>
</ul>
<p>Chaque hook fait <strong>3 octets de long</strong>, nous verrons pourquoi plus loin. Et pour le moment tous remplis de <code>RET</code>. Un <code>CALL</code> à ces adresses ne fait donc rien d'autre que de <strong>revenir</strong> à l'appelant immédiatement.</p>
<p><code>A</code> prend ensuite la valeur <code>C3</code>, qui, suivi d'une adresse sur deux octets, est un <code>JP</code> absolu à cette adresse. Les hooks <code>lpnhk</code>, <code>dskhk</code> et <code>modhk</code> sont remplis avec ce <code>jp no_device</code>, qui est un <strong>branchement</strong> vers l'<strong>erreur</strong> indiquant que le <strong>périphérique</strong> n'est pas géré.</p>
<p>Plus étonnant est la valeur que prend le hook <code>nmihk</code>, qui est appelé en cas d'<strong>interruption non masquable</strong>. Cette interruption est appelée lors de l'appui sur la touche <code>Delta</code> du VG5000µ. La routine <code>resetlang</code> met le système en anglais au niveau des messages et du clavier puis ressort de l'interruption. Et c'est tout.</p>
<p>À vrai dire, cela ne dure <strong>qu'un instant</strong>. Juste après, le VG5000µ initialise sa partie graphique puis remplace le hook par une nouvelle valeur :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">test_reset</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="kc">$</span><span class="mi">47</span><span class="nv">ef</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
</code></pre></div>
<p>Cette nouvelle routine <code>test_reset</code> vérifie si la touche <code>Ctrl</code> est appuyée. Si ce n'est pas le cas, la routine sort immédiatement. Sinon, un reset <em>à chaud</em> a lieu.</p>
<h3>Mise en place d'une routine</h3>
<p>« <strong>Accrocher</strong> » une routine est assez facile, et demande juste quelques précautions. Afin de prendre un premier exemple, je vais accrocher une routine sur l'<strong>interruption</strong> qui provoque l'affichage sur le VG5000µ.</p>
<p>Rapidement, dans le VG5000µ, le <strong>processeur graphique</strong> est la cause d'une <strong>interruption</strong> <code>INT</code> à chaque rafraîchissement. C'est la seule raison, de base, qui provoque cette interruption.</p>
<p>Lors de l'interruption, le <code>PC</code> est branché en <code>$0038</code> et la <strong>première instruction</strong> y est <code>call inthk</code>. On a donc une possibilité d'agir lors de l'interruption, avant que le rafraîchissement potentiel de l'écran n'ait lieu (il n'a pas lieu à chaque fois).</p>
<p>Le code nécessaire pour une routine de « hook » est en deux parties. La première se charge de <strong>modifier le branchement</strong> du « hook » vers notre routine. La seconde est la <strong>routine elle-même</strong>.</p>
<p>Commençons par la <strong>première partie</strong>. À noter que pour être propre, il faudrait effectuer un chaînage en faisant appeler à notre propre routine une routine éventuellement déjà installée. Je ne m'en occuperai pas ici.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">inthk</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">47</span><span class="nv">D0</span><span class="w"> </span><span class="c1">; Adresse du hook</span><span class="w"></span>
<span class="w"> </span><span class="k">org</span><span class="w"> </span><span class="kc">$</span><span class="mi">7</span><span class="nv">A00</span><span class="w"> </span><span class="c1">; Spécification de l'adresse mémoire d'implémentation</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">AF</span><span class="w"> </span><span class="c1">; Sauvegarde des registres sur la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,</span><span class="kc">$</span><span class="nv">C3</span><span class="w"> </span><span class="c1">; Mise en place de la routine sur le HOOK</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inthk</span><span class="p">),</span><span class="nv">A</span><span class="w"> </span><span class="c1">; le 'JP'</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">HL</span><span class="p">,</span><span class="nv">int_routine</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">inthk</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span><span class="nv">HL</span><span class="w"> </span><span class="c1">; et l'adresse</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Restauration des registres depuis la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">AF</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="c1">; Retour au programme appelant</span><span class="w"></span>
<span class="nl">int_routine:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>Le programme est directement commenté. Si depuis le BASIC, ce programme est injecté et appelé, alors... il ne se passera pas grand chose de visible, mais en réalité, le <code>RET</code> de <code>int_routine</code> sera appelé à chaque interruption.</p>
<h2>Une routine plus intéressante</h2>
<p>Pour rendre les choses plus intéressantes, voici une routine à installer qui <strong>affiche</strong> à l'écran une petite barre qui tourne.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">screen</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">4000</span><span class="w"></span>
<span class="nl">int_routine:</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">AF</span><span class="w"> </span><span class="c1">; Sauvegarde de AF</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,(</span><span class="nv">IX</span><span class="o">+</span><span class="kc">$</span><span class="mi">00</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">dec</span><span class="w"> </span><span class="nv">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">nz</span><span class="p">,</span><span class="w"> </span><span class="nv">no_display</span><span class="w"> </span><span class="c1">; Respect du timer de rafraîchissement</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Sauvegarde de HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,(</span><span class="nv">count</span><span class="p">)</span><span class="w"> </span><span class="c1">; Compteur du caractère à afficher</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">A</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">4</span><span class="w"> </span><span class="c1">; S'il est à la dernière position, on boucle</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">nz</span><span class="p">,</span><span class="w"> </span><span class="nv">display</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">0</span><span class="w"></span>
<span class="nl">display:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">count</span><span class="p">),</span><span class="nv">A</span><span class="w"> </span><span class="c1">; Mise à jour du compteur</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">HL</span><span class="p">,</span><span class="nv">cursor</span><span class="w"> </span><span class="c1">; Récupération du caractère à afficher</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">A</span><span class="p">,</span><span class="nv">L</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">L</span><span class="p">,</span><span class="nv">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">A</span><span class="p">,(</span><span class="nv">HL</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">HL</span><span class="p">,</span><span class="nv">screen</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">32</span><span class="w"> </span><span class="c1">; Affichage</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">HL</span><span class="p">),</span><span class="w"> </span><span class="nv">A</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">ix</span><span class="o">+</span><span class="kc">$</span><span class="mi">01</span><span class="p">),</span><span class="kc">$</span><span class="mi">01</span><span class="w"> </span><span class="c1">; Force le ré-affichage</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">HL</span><span class="w"> </span><span class="c1">; Restauration des registres depuis la pile</span><span class="w"></span>
<span class="nl">no_display:</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">AF</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">count:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">0</span><span class="w"></span>
<span class="nl">cursor:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">2</span><span class="nv">F</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">60</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">5</span><span class="nv">C</span><span class="p">,</span><span class="w"> </span><span class="kc">$</span><span class="mi">7</span><span class="nv">C</span><span class="w"> </span><span class="c1">; Les 4 caractères qui forment l'animation</span><span class="w"></span>
</code></pre></div>
<p>Il faut veiller dans cette routine à bien <strong>préserver</strong> les <strong>registres</strong> utilisés, nous sommes ici en pleine interruption, nous n'avons aucune connaissance du contexte.</p>
<p>La lecture de la <strong>variable système</strong> à travers le registre <code>IX</code> permet de savoir si le système va considérer un rafraîchissement de l'affichage. La commande <code>DISPLAY</code> du BASIC influe directement sur une valeur de compteur qui, lorsqu'il arrive à zéro, provoque éventuellement un affichage avant de revenir à sa valeur spécifiée.</p>
<p>L'affichage n'est cependant <strong>pas systématique</strong>. Le bit <code>0</code> de la variable système qui suit le compteur doit être à <code>1</code> pour que l'affichage est vraiment lieu. Et l'on trouve parsemé dans la ROM des <code>ld (ix+$01),$01</code> qui signifient qu'un rafraîchissement de l'écran est demandé. Ce que je fais à la fin de la routine.</p>
<p>La partie après <code>PUSH HL</code> est un bête <strong>compteur cyclique</strong> de 0 à 3, qui est ensuite utilisé pour pointer dans un tableau de 4 caractères provoquant l'animation.</p>
<p>L'<strong>adresse écran</strong> est calculé en dur et on y place directement le caractère. Puis le contexte est restauré.</p>
<p>Une dernière note pour comprendre l'affichage du VG5000µ dans sa ROM BASIC. La ROM maintient en <code>$4000</code> une image logique du contenu de l'écran, et un ensemble de variables systèmes, pointées en tout temps par <code>IX</code>. Lorsqu'un rafraîchissement à lieu, tout ce contenu est envoyé vers le processeur graphique pour une grande (et lente !) mise à jour.</p>
<p>Il se peut donc que des modifications aient lieu en mémoire graphique côté processeur qui ne soient pas répercutées tout de suite vers le processeur graphique.</p>
<p>Mais tout ceci est une autre histoire qui n'est pas le sujet ici.</p>
<h3>Le test</h3>
<p>Voici un programme BASIC qui va monter la routine en mémoire. Suivi d'un <code>RUN</code> pour l'exécuter puis du <code>CALL</code> pour lancer le hook.</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="kr">CLEAR</span><span class="w"> </span><span class="il">50</span><span class="p">,</span><span class="o">&</span><span class="s2">"79FF"</span>
<span class="nl">20</span><span class="w"> </span><span class="vg">S</span><span class="o">=&</span><span class="s2">"7A00"</span>
<span class="nl">30</span><span class="w"> </span><span class="kr">READ</span><span class="w"> </span><span class="vg">A$</span>
<span class="nl">40</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">A$</span><span class="o">=</span><span class="s2">"FIN"</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">END</span>
<span class="nl">50</span><span class="w"> </span><span class="vg">A$</span><span class="o">=</span><span class="s2">"&"</span><span class="o">+</span><span class="kr">CHR$</span><span class="p">(</span><span class="il">34</span><span class="p">)</span><span class="o">+</span><span class="vg">A$</span><span class="o">+</span><span class="kr">CHR$</span><span class="p">(</span><span class="il">34</span><span class="p">)</span><span class="o">:</span><span class="vg">A</span><span class="o">=</span><span class="kr">VAL</span><span class="p">(</span><span class="vg">A$</span><span class="p">)</span>
<span class="nl">60</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="vg">S</span><span class="p">,</span><span class="vg">A</span>
<span class="nl">70</span><span class="w"> </span><span class="vg">S</span><span class="o">=</span><span class="vg">S</span><span class="o">+</span><span class="il">1</span>
<span class="nl">80</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">30</span>
<span class="nl">300</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="vg">F5</span><span class="p">,</span><span class="vg">C5</span><span class="p">,</span><span class="vg">D5</span><span class="p">,</span><span class="vg">E5</span><span class="p">,</span><span class="il">3</span><span class="vg">E</span><span class="p">,</span><span class="vg">C3</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="vg">D0</span><span class="p">,</span><span class="il">47</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">14</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">22</span><span class="p">,</span><span class="vg">D1</span><span class="p">,</span><span class="il">47</span><span class="p">,</span><span class="vg">E1</span><span class="p">,</span><span class="vg">D1</span><span class="p">,</span><span class="vg">C1</span><span class="p">,</span><span class="vg">F1</span><span class="p">,</span><span class="vg">C9</span>
<span class="nl">310</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="vg">F5</span><span class="p">,</span><span class="vg">DD</span><span class="p">,</span><span class="il">7</span><span class="vg">E</span><span class="p">,</span><span class="il">0</span><span class="p">,</span><span class="il">3</span><span class="vg">D</span><span class="p">,</span><span class="vg">C2</span><span class="p">,</span><span class="il">3</span><span class="vg">A</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="vg">E5</span><span class="p">,</span><span class="il">3</span><span class="vg">A</span><span class="p">,</span><span class="il">3</span><span class="vg">C</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">3</span><span class="vg">C</span><span class="p">,</span><span class="vg">FE</span><span class="p">,</span><span class="il">4</span><span class="p">,</span><span class="vg">C2</span><span class="p">,</span><span class="il">28</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">3</span><span class="vg">E</span><span class="p">,</span><span class="il">0</span>
<span class="nl">320</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">32</span><span class="p">,</span><span class="il">3</span><span class="vg">C</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">3</span><span class="vg">D</span><span class="p">,</span><span class="il">7</span><span class="vg">A</span><span class="p">,</span><span class="il">85</span><span class="p">,</span><span class="il">6</span><span class="vg">F</span><span class="p">,</span><span class="il">7</span><span class="vg">E</span><span class="p">,</span><span class="il">21</span><span class="p">,</span><span class="il">20</span><span class="p">,</span><span class="il">40</span><span class="p">,</span><span class="il">77</span><span class="p">,</span><span class="vg">DD</span><span class="p">,</span><span class="vg">CB</span><span class="p">,</span><span class="il">1</span><span class="p">,</span><span class="vg">C6</span><span class="p">,</span><span class="vg">E1</span><span class="p">,</span><span class="vg">F1</span><span class="p">,</span><span class="vg">C9</span>
<span class="nl">330</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">0</span><span class="p">,</span><span class="il">2</span><span class="vg">F</span><span class="p">,</span><span class="il">60</span><span class="p">,</span><span class="il">5</span><span class="vg">C</span><span class="p">,</span><span class="il">7</span><span class="vg">C</span>
<span class="nl">1000</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="vg">FIN</span>
<span class="kr">RUN</span>
<span class="kr">CALL</span><span class="w"> </span><span class="o">&</span><span class="s2">"7A00"</span>
</code></pre></div>
<p>Pour éviter d'avoir à tout rentrer au clavier, voici le <a href="https://www.triceraprog.fr/files/201909/hk_int.k7">fichier .k7</a>. À charger avec <code>CLOAD</code>, suivi d'un <code>RUN</code> et du <code>CALL &"7A00"</code>.</p>
<h3>La suite</h3>
<p>Nous avons vu un exemple de mise en place de routine sur un « hook » du VG5000µ. Dans les articles suivant, j'irai examiner les autres « hook » et dans quels contextes ils sont appelés.</p>VG5000µ, les chaînes de caractères2019-08-22T00:00:00+02:002019-08-22T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2019-08-22:/vg5000m-les-chaines-de-caracteres.html<p>Dans l'<a href="https://www.triceraprog.fr/vg5000m-les-variables-en-memoire.html">article précédent</a>, on avait vu la <strong>création d'une variable</strong> dans la zone principale de la mémoire. Cette variable a par défaut un contenu nul, et ne s'occupe pas de savoir si ce contenu est un nombre ou une chaîne de caractères. Les quatre octets de contenus qui suivent …</p><p>Dans l'<a href="https://www.triceraprog.fr/vg5000m-les-variables-en-memoire.html">article précédent</a>, on avait vu la <strong>création d'une variable</strong> dans la zone principale de la mémoire. Cette variable a par défaut un contenu nul, et ne s'occupe pas de savoir si ce contenu est un nombre ou une chaîne de caractères. Les quatre octets de contenus qui suivent les deux octets du nom sont donc tous les quatre à <code>$00</code>.</p>
<p>Pour qu'une valeur soit associée à une variable, il faut une <strong>instruction d'assignation</strong>, directement via <code>LET</code> (éventuellement de manière implicite), plus <strong>indirectement</strong> avec une instruction <code>FOR</code>, ou encore <strong>plus indirectement</strong> par un couple <code>READ</code>/<code>DATA</code>.</p>
<p>Dans tous les cas, la valeur à assigner à la variable est le résultat de l'<strong>évaluation d'une expression</strong>, c'est-à-dire le résultat d'un calcul numérique ou d'une opération à partir de chaînes.</p>
<p>Afin de comprendre comment sont créées et stockées les chaînes de caractères, c'est donc du côté de l'évaluation d'expression qu'il faut commencer.</p>
<h2>Évaluation d'expression</h2>
<p>L'évaluation d'une expression commence en <code>$2861</code> et nous n'allons pas nous y attarder. Nous suivons la piste immédiatement vers la routine de lecture d'une valeur depuis le buffer d'entrée. Cette routine se situe en <code>$28d8</code> et commence comme suit :</p>
<div class="highlight"><pre><span></span><code><span class="nl">parse_value:</span><span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">valtyp</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">chget</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">missing_op</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">str_to_num</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="s">'&'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_hex_dec</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">a_to_z_2</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">str_to_var</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="s">'+'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">parse_value</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="s">'.'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_to_num</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="s">'-'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_to_min</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="s">'"'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_to_str</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">b7</span><span class="w"> </span><span class="c1">; 'NOT'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_to_not</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">b4</span><span class="w"> </span><span class="c1">; 'FN'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_to_fn</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">c3</span><span class="w"> </span><span class="c1">; 'SGN'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">str_to_func</span><span class="w"></span>
</code></pre></div>
<p>Voici toute une <strong>série de tests</strong> pour déterminer ce que contient l'opérande pointée actuellement par <code>HL</code>.</p>
<p>On remarque au tout début que la valeur par défaut de l'expression en cours est mis à <code>0</code> (c'est-à-dire : valeur numérique).</p>
<p>Puis le premier caractère est lu et la suite de tests ressemble à ceci :</p>
<ul>
<li>Est-ce qu'on est à la <strong>fin de la ligne</strong> ? Alors il manque quelque chose...</li>
<li>Est-ce que c'est un <strong>chiffre</strong> ? Alors on commence à convertir l'entrée en nombre</li>
<li>Est-ce que ça commence par <strong><code>&</code></strong> ? Alors on commence à décoder un nombre hexa</li>
<li>Est-ce que c'est une <strong>lettre</strong> ? Alors on va lire une variable</li>
<li>Est-ce que c'est un <strong>'+'</strong> ? On l'ignore et on boucle un caractère plus loin</li>
<li>Est-ce que c'est un <strong>'.'</strong> ? Alors on commence à convertir l'entrée en nombre</li>
<li>Est-ce que c'est un <strong>'-'</strong> ? Alors on démarre une sous-expression qui sera inversée</li>
<li>Est-ce que c'est un <strong>'"'</strong> ? Alors on décode une chaîne !</li>
<li>Etc... (les trois derniers cas sont pour <code>NOT</code>, une fonction utilisateur, ou une fonction prédéfinie, puis on continue avec le traitement des parenthèses)</li>
</ul>
<p>D'après cette liste, on part donc vers <code>str_to_str</code>.</p>
<h2>Les chaînes à la chaîne</h2>
<p>Arrivée dans <code>str_to_str</code>, on a <code>HL</code>qui pointe vers une chaîne qui commence avec des guillemets. La première étape va être de <strong>chercher</strong> la <strong>fin de la chaîne</strong> et de <strong>compter</strong> le nombre de caractères.</p>
<div class="highlight"><pre><span></span><code><span class="nl">str_to_str:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="s">'"'</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">d</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="nl">direct_str:</span><span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="kc">$</span><span class="nv">ff</span><span class="w"></span>
<span class="nl">loop_str:</span><span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">c</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">create_str</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">d</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">create_str</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nz</span><span class="p">,</span><span class="nv">loop_str</span><span class="w"></span>
<span class="nl">create_str:</span><span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="s">'"'</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">skipch</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="p">(</span><span class="nb">sp</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">c</span><span class="w"></span>
</code></pre></div>
<p>Cette routine commence par placer le caractère guillemets dans les registres <code>B</code> et <code>D</code>. Les caractères présents dans <code>B</code> et <code>D</code> sont des <strong>terminateurs potentiels</strong>. Cette routine est en effet appelée par un autre chemin directement en <code>direct_str</code> avec d'autres terminateurs possibles.</p>
<p><strong>Note</strong> : ces autres terminateurs possibles sont <code>:</code> et ',' dans le cas où la chaîne est lue par une instruction <code>READ</code> depuis une séquence de <code>DATA</code>.</p>
<p>La suite du préambule de la routine se fait en poussant sur la pile le pointeur sur la ligne en exécution et en initialisant <code>C</code> avec <code>-1</code>. <code>C</code> est le <strong>compteur de caractères</strong>. Au passage, on peut en déduire que les chaînes de caractères auront donc comme longueur maximale 255.</p>
<p>Puis débute la boucle <code>loop_str</code>, qui commence par avancer le pointeur <code>HL</code> sur le caractère suivant, récupère la valeur de ce caractère dans <code>A</code> et incrémente le nombre de caractères.</p>
<p>Le premier test vérifie si <code>A</code> <strong>est nul</strong>. Si c'est le cas, on a atteint la <strong>fin de la chaîne</strong> et il est temps de la créer. De même que si le caractère est égal à l'un des deux terminateurs. Dans le cas contraire, la boucle est bouclée et le caractère suivant traité.</p>
<p><strong>Note</strong> : mais et s'il y a plus de 255 caractères avant de trouver un terminateur ? Ça ne se passe pas très bien... Il n'y a pas de tests et vous pouvez vérifier (c'est un peu long) qu'il peut se passer des choses étranges.</p>
<p>Avant de créer la chaîne, il faut mettre les choses en place. Si le dernier caractère sont des guillemets, une routine va les consommer et ignorer tout ce qui est inintéressant, pour recaler <code>HL</code> sur la prochaine valeur ou instruction.</p>
<p>Ce pointeur est échangé avec le haut de la pile, qui contenait le début de la chaîne. Ce début de chaîne est avancé de 1 pour ignorer les premier guillemets (le chemin <code>READ</code> s'arrange pour mettre <code>HL</code> au bon endroit en sachant qu'il sera incrémenté ici).</p>
<p>Puis le pointeur de début de chaîne est transféré dans <code>DE</code> et le nombre de caractères lus dans <code>A</code>.</p>
<h2>Création de la chaîne temporaire</h2>
<p>À présent que l'on sait <strong>où est la chaîne</strong> (pointée par <code>DE</code>) et <strong>combien de caractères</strong> elle contient, l'étape suivant consiste à <strong>l'extraire</strong> dans un endroit où l'évaluation de l'expression ou l'assignation pourra la trouver.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">crt_tmp_str</span><span class="w"></span>
<span class="nl">cpy_to_pool:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">dsctmp</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">temppt</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">faclo</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">01</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">valtyp</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">cpy_detohl_4</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">de_compare</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">temppt</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="nv">nz</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="kc">$</span><span class="mi">001</span><span class="nv">e</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">error_out</span><span class="w"></span>
</code></pre></div>
<p>Tout commence par un appel à <code>crt_str_dsc</code> qui créé un <strong>descripteur temporaire</strong> de chaîne à l'adresse <code>dsctmp</code> (<code>$499b</code>). Dans ce buffer qui sert aux opérations sur les chaînes, la routine placera en premier octet la taille de la chaîne, puis rien de spécial, puis la valeur de 'DE' sur les deux derniers octets.</p>
<p>Les <strong>descripteurs</strong> de chaînes font donc <strong>4 octets</strong>, dont le deuxième est inutilisé.</p>
<p>Puis, <code>DE</code> prend la valeur de <code>dsctmp</code>, le buffer temporaire qui vient d'être initialisé, et <code>HL</code> la valeur contenue dans la variable système <code>temppt</code>. Ce pointeur est initialisé par le BASIC vers le buffer <code>tempst</code>, qui est un buffer de 120 octets réservé.</p>
<p>Ce pointeur est placé dans l'<strong>accumulateur flottant</strong>, qui maintient en fait toute <strong>valeur courante</strong> d'une <strong>expression</strong>, qu'elle soit numérique (lorsque <code>(valtyp)</code> vaut <code>0</code>) ou chaîne (lorsque <code>(valtyp)</code> vaut <code>1</code>).</p>
<p>Et d'ailleurs, <code>(valtyp)</code> passe à <code>1</code> pour indiquer la nature du contenu de l'accumulateur flottant.</p>
<p>L'appel suivant est une routine qui <strong>copie</strong> 4 octets pointés par <code>DE</code> vers ce qui est pointé par <code>HL</code>. Autrement dit, le <strong>descripteur de chaîne</strong> qui vient d'être créé est <strong>copié</strong> vers le buffer temporaire pointé par <code>HL</code>.</p>
<p>Comme <code>dsctmp</code> est placé astucieusement après le buffer <code>tempst</code>, si jamais, après copie, <code>HL</code> est égal <code>DE</code>, alors c'est qu'on a atteint la fin de l'espace de travail, <strong>une erreur est latente</strong>, traitée un peu plus loin. Comme 120 (la taille du buffer) est divisible par 4 (la taille des descripteurs) on est assuré de tomber juste, et que <code>HL</code> ne dépasse jamais <code>DE</code>.</p>
<p>En attendant de traiter l'erreur, il s'agit de mettre les choses en ordre. La variable système <code>(temppt)</code> est mise à jour avec la nouvelle valeur de <code>HL</code>, puis on récupère le pointeur sur la ligne depuis la pile, et le caractère pointé par <code>HL</code> est placé dans <code>A</code>, tout est prêt pour continuer le décodage.</p>
<p>Enfin, on sort de la routine si la dernière comparaison n'était pas nulle (il reste de la place dans le buffer temporaire) ou bien on saute vers une erreur indiquant à l'utilisateur que l'opération sur les chaînes de caractères était trop complexe.</p>
<p><strong>Note</strong> : il existe 30 emplacements de descripteurs de chaîne dans le buffer temporaire avant que la routine ne laisse tomber avec un message d'erreur. En sachant qu'une expression comme <code>PRINT "ABC" + "CDE" + "DEF"</code> en consomme 2, ça laisse de la marge...</p>
<h2>Association de la variable</h2>
<p>Pour l'<strong>association</strong> de la <strong>variable</strong> avec sa <strong>valeur</strong>, voyons le cas de l'instruction <code>LET</code> (qui est de toute façon appelée par <code>FOR</code> et <code>READ</code>).</p>
<p>Passons rapidement sur le début de l'instruction <code>LET</code> qui récupère l'adresse de la variable à gauche du signe égal selon la méthode décrite dans l'<a href="filename}/Machines/20190818-VG5000-LesVariables.md">article précédent</a>, appel l'évaluation de ce qui est à droite du signe égal, et vérifie que les types sont <strong>cohérents</strong> des deux côtés (soit numérique, soit chaîne de caractères).</p>
<p>Une fois tout ceci en place, l'association de la chaîne elle-même a lieu :</p>
<div class="highlight"><pre><span></span><code><span class="nl">let_string:</span><span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">faclo</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">e</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">d</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">txttab</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">de_compare</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">crtstrentry</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">strend</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">de_compare</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">pop_string</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">dsctmp</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">de_compare</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">pop_string</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">3</span><span class="nv">e</span><span class="w"></span>
<span class="nl">crtstrentry:</span><span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">bc_from_tmp</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">save_str</span><span class="w"></span>
<span class="nl">pop_string:</span><span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">bc_from_tmp</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">cpy_detohl_4</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>En début de routine, <code>HL</code> pointe vers la <strong>variable à gauche</strong> du signe <code>=</code>, on <strong>sauve cette adresse</strong> sur la pile pour plus tard.</p>
<p>Puis on récupère dans <code>HL</code> la valeur de la dernière <strong>expression évaluée</strong>, qui est dans l'accumulateur flottant. On pousse aussi cette valeur sur la pile et on va chercher deux octets plus loin le pointeur vers la chaîne de caractère elle-même, qui est placée dans <code>DE</code>.</p>
<p>À présent, il s'agit de savoir où sont situés ces <strong>octets de chaînes</strong>. Le premier cas est une comparaison avec <code>(txttab)</code>. Si les caractères sont avant, c'est qu'ils sont dans les variables systèmes, et donc dans un <strong>endroit volatile</strong>, il va donc falloir les <strong>copier ailleurs</strong> et c'est ce que va faire le saut en <code>crtstrentry</code>.</p>
<p><strong>Note</strong> : si vous vous amusez avec les pointeurs de zones mémoire du BASIC pour déplacer le contenu du code, gardez en tête que pour le BASIC, une chaîne située <em>avant</em> le code est volatile.</p>
<p>Le second test vérifie si la chaîne se situe avant <code>(strend)</code>. Si c'est le cas, c'est que la <strong>chaîne</strong> se trouve dans le <strong>programme</strong>.</p>
<p><strong>Note</strong> : en toute rigueur, la comparaison aurait du être faite avec <code>(vartab)</code>, car il n'y a pas de contenu de chaînes entre <code>(vartab)</code> et <code>(strend)</code>. En regardant d'autres dérivés du BASIC-80, je pense qu'il s'agit d'une adaptation un peu hâtive, car d'autres BASIC-80 semblent placer leurs chaînes différemment. Même si le pointeur de comparaison n'est pas exactement le <em>bon</em>, le test fonctionne néanmoins, et ce n'est pas plus lent.</p>
<p>Si la <strong>chaîne</strong> se trouve <strong>dans le programme</strong>, on va pouvoir <strong>conserver</strong> ce pointeur <strong>sans dupliquer</strong> les octets ailleurs. En effet, un programme n'est pas volatile et le moindre changement dans le listing efface toutes les variables. On est donc assuré que les chaînes de caractères présentes dans le programme lorsque celui-ci tourne restent en place.</p>
<p><strong>Note</strong> : cela donne quelques contraintes si vous vous amusez à modifier le listing en cours de route depuis le programme qui tourne...</p>
<p>Le <strong>troisième test</strong>, enfin, vérifie si le contenu de la chaîne ne serait pas par hasard dans un autre buffer temporaire, celui où l'on met le descripteur temporaire (et qui se situe juste avant <code>dsctmp</code>)</p>
<p><strong>Note</strong> : ce troisième test est <strong>étrange</strong>. Ce buffer est situé dans les variables système et donc est déjà avant le code BASIC. Je ne vois donc pas comment on peut arriver ici. Je pense que c'est un reliquat d’adaptation du BASIC-80 où le buffer temporaire se situe après le code BASIC.</p>
<p>Le <code>defb $3e</code> est un instruction morte permettant d'éviter l'exécution du <code>POP DE</code> qui suit. En effet, ce <code>POP DE</code> pour récupérer le pointeur sur le descripteur de chaîne a déjà été fait lorsqu'on arrive par là.</p>
<p><code>crtstrentry</code> replace le pointeur HL sur les informations de la chaîne temporaire la plus récente (la plus en haut du buffer temporaire), puis cette adresse et échangée avec celle tenue dans <code>DE</code> qui est aussi le pointeur vers ce même descripteur.</p>
<p><strong>Note</strong> : ici, je <strong>ne sais pas</strong> dans quel cas <code>HL</code> et <code>DE</code> peuvent être différent. L'idée est d'enlever le descripteur de chaîne du buffer temporaire en ajustant le pointeur sur ce buffer <code>(temppt)</code>, le buffer temporaire étant manipulé comme une pile, la routine <code>bc_from_tmp</code> est en quelque sorte le <code>pop</code> de cette pile, dont la valeur part dans <code>BC</code>, mais avec une sécurité. Si le pointeur <code>HL</code> n'est pas celui qui était attendu <code>DE</code> alors le <code>pop</code> n'a pas lieu, c'est juste une récupération de la valeur en haut de la pile.</p>
<p>Avec les informations récupérées, un appel à <code>save_str</code> est effectué, et nous verrons ça juste après.</p>
<p>Dans tous les cas, la description de chaîne la plus récente du <strong>buffer temporaire</strong> est à nouveau <strong>récupérée</strong>, l'adresse de la variable popée de la pile dans <code>HL</code> et le descripteur temporaire <strong>copié</strong> vers la valeur de cette <strong>variable</strong>.</p>
<p>Après une remise en ordre de la pile, on rend la main, la <strong>variable</strong> est maintenant <strong>associée</strong> à la valeur de la chaîne.</p>
<h2>Et la création ?</h2>
<p>Dans le cas où la chaîne de caractères doit être sauvée quelque part, alors un appel à <code>save_str</code> est fait.</p>
<p><code>save_str</code> se situe en <code>$3646</code> et est comme suit :</p>
<div class="highlight"><pre><span></span><code><span class="nl">save_str:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">alloc_str_mem</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">crt_str_dsc</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">copy_str</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>En entrée, <code>HL</code> pointe vers un <strong>descripteur de variable</strong> de type chaîne. Le premier des 4 octets contient donc le nombre de caractères, qui est récupéré dans <code>A</code>. Puis <code>HL</code> est positionné sur le <strong>premier octet</strong> de l'<strong>adresse du contenu</strong> et cette adresse est poussée sur la pile.</p>
<p>L'appel à <code>alloc_str_mem</code> <strong>vérifie</strong> ensuite s'il reste <strong>assez de place</strong> dans la mémoire dédiée aux chaînes pour ajouter <code>A</code> octets.</p>
<p>Si la routine de vérification ressort, c'est qu'il y a de la place (une erreur aurait été immédiatement émise sinon) et une chaîne de <code>A</code> caractères a été <strong>allouée</strong>, <code>(fretop)</code> ajusté, et <code>DE</code> pointe vers cette nouvelle allocation.</p>
<p>On récupère alors <code>HL</code> pour obtenir dans <code>BC</code> l'adresse actuelle du contenu de la chaîne.</p>
<p>Un appel à <code>crt_str_dsc</code> crée une nouveau descripteur dans le buffer temporaire avec <code>DE</code> comme pointeur de contenu.</p>
<p>Puis <code>copy_str</code> est appelé après avoir sauvé le pointeur vers le nouveau descripteur dans la pile et mis la taille de la chaîne dans <code>L</code>.</p>
<p>Je ne copie pas le code de <code>copy_str</code> ici. Il est extrêmement simple et copie <code>L</code> caractères de la zone pointée par <code>BC</code> vers la zone pointée par <code>DE</code>. Autrement dit, de la <strong>chaîne source</strong> vers l'<strong>emplacement nouvellement alloué</strong>.</p>
<p>Au retour, <code>DE</code> prend la valeur du nouveau descripteur de chaîne, qui est actuellement dans le buffer temporaire et sera récupéré par la fin de la routine de l'instruction <code>LET</code>.</p>
<p>Ouf!</p>
<h3>Ramasse miettes</h3>
<p>En fait... ce n'est pas tout à fait complet. Lors de la tentative d'allocation de chaîne <code>alloc_str_mem</code>, s'il n'y a plus de place dans la mémoire dédiée, un <strong>ramasse miettes</strong> est lancé (garbage collection). Cette routine va compacter la mémoire des chaînes de caractères en comblant les trous des données qui ne sont plus valides, d'anciennes valeurs de chaînes qui ne sont plus pointées par aucune variable.</p>
<p>C'est un <strong>gros morceau</strong> qui doit parcourir les variables mais aussi les tableaux, je laisse ça de côté (pour le moment ?).</p>
<p>À la fin de cette routine, l'allocation est tentée à nouveau. Si lors de cette nouvelle tentative, il n'y a toujours pas assez de mémoire, alors l'<strong>erreur</strong> est <strong>vraiment lancée</strong>.</p>
<h2>Un peu de BASIC</h2>
<p>C'est un peu la tradition de ces articles, voyons maintenant un programme en BASIC qui affiche la valeur des variables. Puisque toutes les variables sont effacées au démarrage d'un programme, il est nécessaire d'en initialiser dans le programme.</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="vg">DEFFNPK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="o">+</span><span class="il">1</span><span class="p">)</span><span class="o">*</span><span class="il">256</span><span class="o">+</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span>
<span class="nl">20</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"1"</span><span class="o">:</span><span class="vg">A$</span><span class="o">=</span><span class="s2">"ABC"</span>
<span class="nl">30</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">1000</span>
<span class="nl">40</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"2"</span><span class="o">:</span><span class="vg">B$</span><span class="o">=</span><span class="s2">"DEF"</span>
<span class="nl">50</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">1000</span>
<span class="nl">60</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"3"</span><span class="o">:</span><span class="vg">A$</span><span class="o">=</span><span class="s2">""</span>
<span class="nl">70</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">1000</span>
<span class="nl">100</span><span class="w"> </span><span class="kr">END</span>
<span class="nl">1000</span><span class="w"> </span><span class="vg">VT</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="o">&</span><span class="s2">"49D8"</span><span class="p">)</span>
<span class="nl">1010</span><span class="w"> </span><span class="vg">AT</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="o">&</span><span class="s2">"49DA"</span><span class="p">)</span>
<span class="nl">1020</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">PT</span><span class="o">=</span><span class="vg">VT</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="vg">AT</span><span class="il">-1</span><span class="w"> </span><span class="k">STEP</span><span class="w"> </span><span class="il">6</span>
<span class="nl">1030</span><span class="w"> </span><span class="vg">T1</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">PT</span><span class="p">)</span>
<span class="nl">1040</span><span class="w"> </span><span class="vg">T2</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">PT</span><span class="o">+</span><span class="il">1</span><span class="p">)</span>
<span class="nl">1050</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="p">(</span><span class="vg">T2</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">128</span><span class="p">)</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="il">1100</span>
<span class="nl">1060</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="kr">CHR$</span><span class="p">(</span><span class="vg">T1</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">127</span><span class="p">);</span>
<span class="nl">1070</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="kr">CHR$</span><span class="p">(</span><span class="vg">t2</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">127</span><span class="p">);</span>
<span class="nl">1080</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="s2">"$="</span><span class="o">+</span><span class="kr">CHR$</span><span class="p">(</span><span class="il">34</span><span class="p">);</span>
<span class="nl">1090</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">2000:</span><span class="kr">PRINT</span><span class="w"> </span><span class="kr">CHR$</span><span class="p">(</span><span class="il">34</span><span class="p">)</span>
<span class="nl">1100</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">PT</span>
<span class="nl">1200</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">2000</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="vg">PT</span><span class="o">+</span><span class="il">4</span><span class="p">)</span>
<span class="nl">2000</span><span class="w"> </span><span class="vg">C</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">PT</span><span class="o">+</span><span class="il">2</span><span class="p">)</span>
<span class="nl">2010</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">C</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">2020</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="vg">PT</span><span class="o">+</span><span class="il">4</span><span class="p">)</span>
<span class="nl">2030</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="il">1</span><span class="w"> </span><span class="vg">to</span><span class="w"> </span><span class="vg">C</span>
<span class="nl">2040</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="kr">CHR$</span><span class="p">(</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">V</span><span class="o">+</span><span class="vg">I</span><span class="il">-1</span><span class="p">));</span>
<span class="nl">2050</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span>
<span class="nl">2060</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<p>Va afficher</p>
<div class="highlight"><pre><span></span><code><span class="mf">1</span><span class="w"></span>
<span class="n">A$</span><span class="o">=</span><span class="s">"ABC"</span><span class="w"></span>
<span class="mf">2</span><span class="w"></span>
<span class="n">A$</span><span class="o">=</span><span class="s">"ABC"</span><span class="w"></span>
<span class="n">B$</span><span class="o">=</span><span class="s">"DEF"</span><span class="w"></span>
<span class="mf">3</span><span class="w"></span>
<span class="n">B$</span><span class="o">=</span><span class="s">"DEF"</span><span class="w"></span>
</code></pre></div>
<p>La partie du programme entre 10 et 100 s'occupe de <strong>manipuler des variables</strong> et d'appeler l'affichage de leur contenu.</p>
<p>La partie entre 1000 et 1200 regarde la <strong>liste des variables</strong> comme dans l'article précédent, mais ne sélectionne que celles de types <strong>chaînes de caractères</strong>.</p>
<p>La partie à partir de 2000 va chercher le nombre de caractères et le pointeur vers les données pour <strong>afficher le tout</strong>, caractère par caractère.</p>VG5000µ, les variables en mémoire2019-08-22T00:00:00+02:002019-08-22T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2019-08-22:/vg5000m-les-variables-en-memoire.html<p>Pour terminer cette série sur la gestion de la mémoire par le BASIC sur VG5000µ, j'aborde la manière dont sont stockées les variables en mémoire.</p>
<h3>Les variables systèmes</h3>
<p>On l'a vu dans l'article sur la <strong>cartographie</strong> de la mémoire, il y a six variables systèmes intéressantes sur ce sujet :</p>
<ul>
<li><strong>(vartab …</strong></li></ul><p>Pour terminer cette série sur la gestion de la mémoire par le BASIC sur VG5000µ, j'aborde la manière dont sont stockées les variables en mémoire.</p>
<h3>Les variables systèmes</h3>
<p>On l'a vu dans l'article sur la <strong>cartographie</strong> de la mémoire, il y a six variables systèmes intéressantes sur ce sujet :</p>
<ul>
<li><strong>(vartab)</strong> est la première adresse d'une variable. C'est ici que sont stockés les noms des variables numériques (avec leur valeur) ou chaînes (avec un pointeur vers leur contenu),</li>
<li><strong>(arytab)</strong> est la première adresse de stockage du contenu des tableaux dimensionnés par <code>DIM</code> (ou bien les tableaux crées par défaut par le BASIC avec un <code>DIM</code> implicite), avec leur nom, leur taille, et leur contenu (ou pointeurs),</li>
<li><strong>(strend)</strong> est la première adresse de la zone libre de stockage, tout ce qu'il y a à partir de cette adresse et « au-dessus » jusqu'à la pile (pointée par le registre <code>SP</code>) est la mémoire BASIC « libre » (ce qui n'est pas tout à fait vrai puisque la mémoire pour les chaînes de caractères est séparée).</li>
<li><strong>(stktop)</strong> est le haut de la pile, l'adresse un octet au-dessus est la première adresse de la zone réservée pour les chaînes de caractères.</li>
<li><strong>(fretop)</strong> est le pointeur de la zone libre pour les chaînes de caractères. L'adresse juste au-dessus contient le début des données de chaînes de caractères,</li>
<li><strong>(memsiz)</strong> est l'adresse la plus haute adressable par le BASIC, et aussi le haut de l'espace réservée aux chaînes de caractères (inclus).</li>
</ul>
<p>Une première constatation est qu'il y a <strong>deux mémoires réservées</strong> au BASIC, <strong>disjointes</strong>. La première est celle qui contiendra tous les <strong>noms</strong> des variables et les <strong>contenus numériques</strong>. La place restante de cette zone est donnée par la fonction <code>FRE(0)</code> (la valeur du paramètre importe peu, seul son type importe).</p>
<p>La seconde est celle qui contiendra tout le <strong>contenu</strong> des <strong>chaînes de caractères</strong>, dans des variables ou dans des tableaux. L'espace restant dans cette zone est donné par la fonction <code>FRE(" ")</code> (la encore, seul le type du paramètre compte). L'espace est fixe, de 50 octets au démarrage de la machine, et déterminé par le premier paramètre de la commande <code>CLEAR</code>. Faire un <code>CLEAR 0</code> est tout à fait possible, mais alors vous ne pourrez plus stocker de chaîne de caractères.</p>
<h3>Création d'une variable</h3>
<h4>Aparté sur la commande <code>LET</code></h4>
<p>Dans le BASIC tel qu'il a été créé à l'université de <strong>Dartmouth</strong>, chaque ligne doit contenir une commande et une seule. La définition et l'assignation d'une variable se font avec la commande <code>LET</code>. Cette <strong>obligation</strong> de commande a un avantage sur l'analyse du programme BASIC par un compilateur ou un interpréteur : s'il n'y a pas de commande, c'est une <strong>erreur de syntaxe</strong>, et il n'y a pas d'exception.</p>
<p>Le BASIC de <strong>Microsoft</strong> a relaxé cette obligation en rendant la commande <code>LET</code> optionnelle, et cette exception a été conservée par de nombreux BASIC par la suite. Mais pas partout, sur un <strong>ZX81</strong> par exemple, chaque nouvelle ligne demande d'entrer une instruction via la touche du clavier correspondante, et le <code>LET</code> est obligatoire.</p>
<p>Lorsque l'instruction <code>LET</code> est optionnelle, le décodage du BASIC lors de la tokénisation est plus complexe : il faut vérifier que ce qui est trouvé sur la ligne ne correspond à aucune instruction et dans ce cas-ci, <strong>faire comme si</strong> une instruction <code>LET</code> était présente. C'est du code en plus pris dans la ROM.</p>
<h4>Avant tout, que cherche-t-on ?</h4>
<p>Lorsque BASIC <strong>rencontre</strong> une variable, il lui faut toujours en premier lieu <strong>vérifier son existence</strong>. En effet, la première assignation de valeur à une variable vaut création avec une valeur par défaut.</p>
<p>Et pour savoir si cette variable existe, il faut en <strong>connaître le nom</strong>. C'est la première partie de la routine qui se trouve en <code>$38da</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">getvar:</span><span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">dimflg</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="nl">get_id:</span><span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">a_to_z</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">stx_err_prt</span><span class="w"></span>
<span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">valtyp</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">chget</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">idnum_trail</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">a_to_z_2</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">id_end</span><span class="w"></span>
<span class="nl">idnum_trail:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="nl">idtrail_skp:</span><span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">chget</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">idtrail_skp</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">a_to_z_2</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">idtrail_skp</span><span class="w"></span>
<span class="nl">id_end:</span><span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">24</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nz</span><span class="p">,</span><span class="nv">num_vrble</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">valtyp</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">rrca</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">chget</span><span class="w"></span>
<span class="nl">num_vrble:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">subflg</span><span class="p">)</span><span class="w"></span>
</code></pre></div>
<p>En premier lieu, (dimflg) est mis à zéro. Cette variable sert lorsque l'on manipule un tableau. Comme on n'a pas encore cette information, la routine part sur du <strong>non tableau</strong>.</p>
<p>Note : il se peut que <code>A</code> soit différent de <code>0</code>, lorsque l'on arrive par l'instruction <code>DIM</code>, qui <strong>saute</strong> par-dessus le <code>XOR A,A</code>, mais laissons ça de côté, le fonctionnement de <code>DIM</code> est de l'acrobatie en assembleur Z80...</p>
<p><code>HL</code> pointe, comme souvent lors de décodage d'une ligne, sur l'emplacement de la ligne en cours d'exécution. Si on est arrivé ici, c'est que l'on s'attend à <strong>trouver un nom de variable</strong> à cet endroit. Le premier caractère est donc lu dans <code>C</code>, puis il est <strong>vérifié</strong> que ce caractère est <strong>une lettre</strong>. En effet, chaque identifiant doit commencer par une lettre.</p>
<p>Si ce n'est pas le cas, un saut vers le traitement d'une <strong>erreur de syntaxe</strong> est fait immédiatement.</p>
<p>Donc le cas contraire, tout comme la routine s'initialise dans un mode <strong>non tableau</strong>, elle part du principe que la <strong>variable est numérique</strong>. On place donc <code>0</code> dans (valtyp), dans lequel est tenu à jour en tout temps le type de l'expression en cours. <code>0</code> dans (valtyp) signifie <strong>numérique</strong>.</p>
<p>Par <code>RST CHGET</code>, un potentiel <strong>second caractère</strong> pour le nom de la variable est lu. Cette routine <code>chget</code> renvoie le caractère lu dans <code>A</code> et l'accompagne de quelques informations. Si le flag <code>Carry</code> est à <code>1</code>, cela signifie que le caractère est un chiffre. La routine en profite pour traiter ce cas en sautant plus loin, un chiffre en deuxième caractère est valide.</p>
<p>Si ce n'était pas un chiffre, on vérifie que c'est une lettre. Note au passage : la première fonction <code>A_TO_Z</code> lit un caractère depuis ce que pointe <code>HL</code>, <code>A_TO_Z_2</code> fait la même vérification mais depuis le caractère déjà présent dans <code>A</code>.</p>
<p>Si ce <strong>second caractère</strong> n'est pas une lettre, alors on doit avoir le nom de l'identifiant, on passe à la suite en <code>id_end</code>.</p>
<p>Le <code>LD B,A</code> sauve le second caractère de la variable s'il existe. <code>B</code> avait été initialisé à <code>0</code> peut avant.</p>
<p>Entre <code>idtrail_skp</code> et <code>id_end</code> (exclu), une boucle <strong>saute tous les caractères</strong> qui sont soit des chiffres, soit des lettres. En effet, il est tout à fait <strong>valide</strong> d'avoir des <strong>noms</strong> de variables <strong>plus long</strong> que deux caractères. Même si les caractères surnuméraires sont ignorés.</p>
<p>Arrivé en <code>id_end</code>, on vérifie le dernier caractère lu. Est-ce un <code>$</code>, si non, on saute plus loin en <code>num_vrble</code>, la variable est bien numérique. Dans le cas contraire, <code>1</code> est mis dans (valtyp) pour désigner un type chaîne de caractères.</p>
<p>Puis le second caractère de la variable, qui avait été sauvé dans <code>B</code> est augmenté de <code>$80</code> (<code>A</code> égal à <code>1</code> après <code>RCCA</code> vaut <code>$80</code>). C'est comme cela que les <strong>variables</strong> de types <strong>chaînes de caractères</strong> sont <strong>identifiées</strong> : le second octet de leur nom a le bit 7 à <code>1</code>.</p>
<p>Lorsque l'on arrive dans <code>num_vrble</code>, on a dans <code>C</code> le premier caractère de la variable, dans <code>B</code> le second caractère de la variable, porteur de l'information de type chaîne, et (valtyp) qui contient aussi le type de la variable.</p>
<h4>Aparté sur les noms de variables longs</h4>
<p>Dans le BASIC original, la simplicité voulu par le langage avait amené les auteurs à ne permette des noms de variables qu'à <strong>une seule lettre</strong>, éventuellement suivie par un chiffre. Plus tard, les variables de type chaînes ont été ajoutées, et le nombre de caractères étendus.</p>
<p>Dans le monde de la micro-informatique 8 bits, il n'y a <strong>pas beaucoup de place</strong> en RAM, et cette limite est soit conservée, soit relaxée par l'intermédiaire du <strong>système permissif</strong> des <strong>noms longs</strong>, dont seuls les premiers caractères sont significatifs.</p>
<p>Sur VG5000µ, ce sont les deux premiers caractères qui comptent. D'ailleurs, tous les noms de variable en interne ont deux caractères, le second caractère étant éventuellement égal à <code>$00</code>.</p>
<p>Je trouve cette idée de caractères significatifs <strong>désastreuse</strong>. Si elle part de l'idée que l'on peut utiliser des noms de variables plus expressifs, elle n'en n'offre pas les moyens, car absolument <strong>aucun test</strong> n'est fait sur ces caractères supplémentaires. D'expérience, il est facile, dans un programme un peu long, de ne plus faire attention au fait que deux noms longs possèdent les deux mêmes premiers caractères.</p>
<p>Et si l'on y fait attention, il faut alors trouver un autre nom pour éviter la collision, et ce nom perd souvent en signification claire, et par la même perd l'intérêt des noms longs.</p>
<h4>Pour qui est-ce ?</h4>
<p>Il existe quelques <strong>contraintes</strong> sur les variables, et c'est dans l'aiguillage suivant qu'elles sont traitées.</p>
<div class="highlight"><pre><span></span><code><span class="nl">num_vrble:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">subflg</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">dec</span><span class="w"> </span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">aryvar</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">p</span><span class="p">,</span><span class="nv">simplevar</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="s">' ('</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">subscript</span><span class="w"></span>
<span class="nl">simplevar:</span><span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
</code></pre></div>
<p>La variable système <code>(subflg)</code> sert à plusieurs choses. Ici, elle donne une <strong>indication</strong> sur une contrainte au niveau de la variable attendue. Si <code>(subflg)</code> est à <code>1</code>, c'est que l'on <strong>s'attend à un tableau</strong>, le branchement est alors vers la recherche d'une variable tableau.</p>
<p>Si <code>(subflg)</code> est supérieur à <code>1</code>, alors c'est que les <strong>tableaux sont interdits</strong>, on saute donc vers <code>simplevar</code>. Les deux cas d'interdictions sont la variable d'index d'un <code>FOR</code> et la variable paramètre d'une fonction <code>DEFFN</code>.</p>
<p>S'il n'y a <strong>pas de contrainte</strong> sur cette variable, mais que l'on trouve une parenthèse ouvrante à la suite de la variable, alors c'est qu'il y a un index, et l'on va vers cette routine.</p>
<p>Et dans le cas contraire, il s'agit d'une variable <strong>simple</strong>. Ouf!</p>
<h4>Dans le cas simple</h4>
<p>Comme cet article va être assez long comme ça, on traitera les tableaux une autre fois. À présent que l'on sait que l'on a affaire à une <strong>variable simple</strong> (pas un tableau), que l'on a son nom et son type, il est grand temps de vérifier si elle existe !</p>
<p>La première partie de cette recherche consiste à initialiser le domaine de recherche et... de vérifier si par hasard on ne serait pas en train d'<strong>évaluer une fonction</strong> <code>DEFFN</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">simplevar:</span><span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">subflg</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">d</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">e</span><span class="p">,</span><span class="nv">c</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">prmnam</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">de_compare</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">prmval</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">pop_hl_ret</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">arytab</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">vartab</span><span class="p">)</span><span class="w"></span>
</code></pre></div>
<p>Tout d'abord, <code>(subflg)</code> est remis à <code>0</code>. Le contexte a bien été traité et ne doit plus l'être. Puis le <code>PUSH HL</code> sert à sauver le pointeur vers la ligne tokenisée en court.</p>
<p>Le <strong>nom de la variable</strong>, présente dans <code>BC</code> est placée dans <code>DE</code> pour utiliser la <strong>comparaison</strong> entre <code>DE</code> et <code>HL</code>.</p>
<p><code>HL</code> prend la valeur de la variable système <code>(prmnam)</code> (parameter name), qui contient, s'il y a lieu, le nom de la variable qui sert de paramètre à une fonction <code>DEFFN</code> en train d'être évaluée.</p>
<p>Si la variable que l'on cherche est <strong>celle du paramètre</strong> d'une <strong>fonction</strong> que l'on est en train d'<strong>évaluer</strong>, il faut la traiter spécialement, car le nom de cette variable ne doit pas affecter une variable du même nom hors de la fonction.</p>
<p>Le <strong>traitement spécial</strong> consiste à, si <code>DE</code> et <code>HL</code> sont égaux, <strong>sortir immédiatement</strong> de la routine de recherche, en faisant pointer <code>DE</code> sur la variable système <code>prmval</code>. Ce buffer de 4 octets contient la <strong>valeur actuelle</strong> du paramètre de la fonction, et <code>DE</code> est le pointeur que renvoie la routine de recherche de variable pour identifier la valeur cherchée.</p>
<p>Dans le cas d'une recherche générique, <code>DE</code> est initialisé avec <code>(arytab)</code> et <code>HL</code> avec <code>(vartab)</code>, les deux bornes de la mémoire contenant les variables simples.</p>
<h4>Cette fois on cherche !</h4>
<p>Ça y est, après tous ces préparatifs, on arrive au point de la <strong>recherche de la variable</strong> dans la mémoire ! Et pour cela, la routine va dérouler une boucle qui va cherche parmi tous les variables existantes une dont le nom correspond.</p>
<div class="highlight"><pre><span></span><code><span class="nl">next_var:</span><span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">de_compare</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">no_var_yet</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">c</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">nz</span><span class="p">,</span><span class="nv">var_diff</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="nl">var_diff:</span><span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">var_found</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">next_var</span><span class="w"></span>
</code></pre></div>
<p>En début de <strong>boucle</strong>, une <strong>comparaison</strong> entre <code>HL</code> et <code>DE</code> est faite. Si les deux sont égaux, c'est qu'on a <strong>fini la recherche</strong>. En effet, tout au long de la boucle, <code>HL</code>, parti de <code>(vartab)</code>, va être incrémenté. <code>DE</code> représente la limite haute, qui ne bouge pas.</p>
<p>Si la recherche est terminée sans avoir trouvé la variable, alors on saute en <code>no_var_yet</code>, il va falloir <strong>la créer</strong>. En effet, tout accès à une variable en BASIC induit sa création si elle n'existe pas encore.</p>
<p>En se souvenant que <code>BC</code> contient le nom de la variable inversé, le premier caractère, dans <code>C</code> est soustrait de celui pointé par <code>HL</code>. S'ils sont <strong>différents</strong>, c'est qu'on n'a <strong>pas trouvé</strong> la variable pour le moment, on saute plus loin.</p>
<p>Si le <strong>premier caractère</strong> correspond, on fait le même test avec le <strong>second caractère</strong>. Si ces <strong>deux tests passent</strong>, alors on a <strong>trouvé le nom</strong>, on saute en <code>var_found</code>.</p>
<p>Sinon, on saute les quatre octets suivants, qui contiennent la valeur de la variable, et on boucle.</p>
<p><strong>Note</strong> : comme le nom de la variable en interne est modifié en fonction de son type, cette recherche montre bien que les variables <code>A</code> et <code>A$</code> sont deux variables différentes. De même que les fonctions, que l'on n'a fait qu'effleurer. Dans la cas d'une fonction définition par <code>DEF FN A(...)</code>, c'est le premier des deux octets qui porte un bit 7 à <code>1</code>, et qui défini donc une troisième espace de nom.</p>
<p>Le cas où les deux octets aurait un bit 7 à <code>1</code> pourrait définir une fonction sur des chaînes de caractères. Mais ce cas n'est pas permis par le BASIC sur VG5000µ.</p>
<h3>Création de la variable</h3>
<p>De la section précédente, on peut sortir soit en ayant trouvé la variable, soit en ne l'ayant pas trouvé. Si la <strong>variable n'est pas trouvé</strong>, il faut la <strong>créer</strong> et l'initialiser puis enchaîner sur la section où la variable est trouvée... si elle a pu être créée bien entendu.</p>
<p>Comme c'est assez long, voyons ça en plusieurs partie. Tout d'abord, la création elle-même, avec un petit <strong>plot twist</strong>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">no_var_yet:</span><span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="p">(</span><span class="nb">sp</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">from_eval</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">de_compare</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">ret_null</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="p">(</span><span class="nb">sp</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">bc</span><span class="p">,</span><span class="kc">$</span><span class="mi">0006</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">strend</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">mem_move_ckk</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">strend</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,</span><span class="nv">c</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">arytab</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
</code></pre></div>
<p>Tout d'abord, on récupère le pointeur sur la ligne évaluée qui était depuis la pile et on l'échange avec la valeur actuellement en haut de la pile. C'est un pas de danse assez <strong>classique en Z80</strong> qui permet d'aller <strong>récupérer</strong> la valeur en <strong>deuxième position</strong> sur la pile.</p>
<p>Puis on sauve la valeur de <code>DE</code> (qui contient <code>(arytab)</code>) avant d'y mettre une valeur spécifique : l'adresse d'une instruction de retour après un <code>CALL</code> particulier. Cette adresse est le chemin que prend l'appel à la <strong>récupération</strong> d'une variable lors de l'<strong>évaluation</strong> d'une <strong>expression</strong>.</p>
<p>Si on vient de là, alors c'est un cas spécifique, et on saute à la section <code>ret_null</code> qui est un <strong>raccourci</strong> qui renvoie directement la valeur nulle à l'appelant. Et tout ceci <strong>sans créer de variable</strong> ! Après tout, pourquoi créer une variable qui a sa valeur par défaut ? Nous verrons <code>ret_null</code> plus loin.</p>
<p>Dans le cas où l'on ne vient par d'une évaluation, alors l'adresse de retour est remise à sa place sur la pile et on y repousse le pointeur sur la ligne exécutée.</p>
<p>Il s'agit maintenant de <strong>vérifier</strong> s'il y a de la <strong>place en mémoire</strong> pour créer la variable. Une variable a besoin de 6 octets en mémoire, et c'est donc avec 6 qu'est initialisé <code>BC</code> (après avoir été sauvé sur la pile, car <code>BC</code> contenait une information importante : le nom de la variable).</p>
<p>Ici, il faut suivre... <code>HL</code> est initialisé avec <code>(strend)</code>, c'est-à-dire la première adresse libre en mémoire principale et on lui ajoute 6 via <code>BC</code>. Au passage, sur la pile est poussé <code>(strend)</code>, qui est récupéré dans <code>BC</code>, puis l'adresse <code>(strend) + 6</code> est poussée sur la pile.</p>
<p>On résume, par ordre croissant, on a :</p>
<ul>
<li>Dans <code>DE</code> se trouve <code>(arytab)</code></li>
<li>Dans <code>BC</code> se trouve <code>(strend)</code></li>
<li>Dans <code>HL</code> se trouve <code>(strend) + 6</code></li>
</ul>
<p>Sur la pile on a <code>(strend) + 6</code> en première position de <code>POP</code>.</p>
<p><strong>Tout est prêt</strong> pour appeler <code>mem_move_chk</code> qui va déplacer la zone comprise entre <code>DE</code> et <code>BC</code>, c'est-à-dire toute la mémoire des tableaux, vers une zone dont l'adresse de fin sera <code>HL</code>. Autrement dit, les <strong>tableaux</strong> sont <strong>poussés</strong> de 6 octets pour faire de la place pour la nouvelle variable.</p>
<p>Cette routine de <strong>déplacement</strong> commence aussi par une vérification que la <strong>place nécessaire</strong> est <strong>disponible</strong>. Dans le cas contraire, une <strong>erreur</strong> est levée et le processus est arrêté.</p>
<p>Après le déplacement de la mémoire pour faire de la place, les variables <code>(strend)</code> et <code>(arytab)</code> sont ajustées à leur nouvelles valeur.</p>
<div class="highlight"><pre><span></span><code><span class="nl">clear_mem:</span><span class="w"> </span><span class="nf">dec</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="kc">$</span><span class="mi">00</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">de_compare</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nz</span><span class="p">,</span><span class="nv">clear_mem</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="nv">e</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="nv">d</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
</code></pre></div>
<p>À présent qu'un <strong>emplacement est libre</strong> pour la variable, on parcourt son emplacement pour y placer des <code>$00</code> avec cette boucle.</p>
<p>Puis on récupère le nom de la variable dans <code>DE</code> et on enregistre ce nom dans les deux premiers octets des 6 octets tout neufs.</p>
<p>La <strong>variable</strong> est à présent <strong>créée</strong>. Il ne reste plus qu'à <strong>retourner un pointeur</strong> avec l'emplacement de sa valeur à l'appelant.</p>
<div class="highlight"><pre><span></span><code><span class="nl">var_found:</span><span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p><code>DE</code> prend la valeur de <code>HL</code> qui pointe juste après le nom de la variable, donc sur les 4 octets de sa valeur.</p>
<p>Puis <code>HL</code> récupère sa valeur de pointeur d'exécution et la routine se termine.</p>
<h3>Retour de variable non définie</h3>
<p>On l'a vu juste avant, si, lors de l'évaluation d'une expression, une variable n'est pas trouvée, le BASIC ne va pas créer cette variable et se contentera de renvoyer la valeur nulle pour le type demandé.</p>
<p>Nous pouvons le confirmer avec cette <strong>petite expérience</strong> :</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="vg">DEFFNPK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="o">+</span><span class="il">1</span><span class="p">)</span><span class="o">*</span><span class="il">256</span><span class="o">+</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span>
<span class="nl">20</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="vg">FNPK</span><span class="p">(</span><span class="o">&</span><span class="s2">"49D8"</span><span class="p">),</span><span class="w"> </span><span class="vg">FNPK</span><span class="p">(</span><span class="o">&</span><span class="s2">"49DC"</span><span class="p">)</span>
<span class="nl">30</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="vg">A</span>
<span class="nl">40</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="vg">A$</span>
<span class="nl">50</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="vg">NPK</span><span class="p">(</span><span class="o">&</span><span class="s2">"49D8"</span><span class="p">),</span><span class="w"> </span><span class="vg">NPK</span><span class="p">(</span><span class="o">&</span><span class="s2">"49DC"</span><span class="p">)</span>
</code></pre></div>
<p>Donne :</p>
<p><img alt="Aucune variable créée" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201908/VG5000-BASIC-VariableNulleAuto.png"></p>
<p>Le nombre à droite est <code>(vartab)</code> et ne bouge pas, puisque le listing ne bouge pas. La valeur de droite est la valeur de <code>(strend)</code> et reste constante entre le premier et le second affichage. Aucune variable n'a été créée par les accès à <code>A</code> et <code>A$</code>.</p>
<p>La <strong>différence de 12 octets</strong> correspond à la variable créé par la <strong>fonction</strong> elle-même et à la variable paramètre de cette fonction. En effet, et c'est assez étrange, alors que la recherche de variable dans une fonction est court-circuitée, comme expliqué plus haut, le BASIC créé tout de même une variable vide, qui ne sera pas utilisée.</p>
<p>Du coup, si vous voulez <strong>éviter de perdre 6 octets</strong>, prenez comme non de paramètre de vos fonction une variable utilisée ailleurs. Elle ne sera pas modifiée.</p>
<p><strong>Changeons un peu l'expérience</strong> et cette fois, donnons les valeurs nulles (0 pour un nombre, "" pour une chaîne) spécifiquement aux deux variables (le <code>LET</code> est optionnel, mais je le laisse pour être clair sur la signification de la création de la variable).</p>
<p><img alt="Aucune variable créée" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201908/VG5000-BASIC-VariableNulleCreee.png"></p>
<p>La valeur de <code>(vartab)</code> est différente par rapport au premier test car le programme est un peu plus long. Néanmoins, sa valeur ne change pas avant et après la création des variables, ce qui donne une référence. Il y a toujours 12 octets pour les variables créées par la fonction.</p>
<p>Par contre, <code>(strend)</code> <strong>augmente de 12 octets</strong> avant et après les assignations. Les <strong>variables</strong> ont bien été <strong>créées</strong>.</p>
<h4>Et cette valeur nulle ?</h4>
<div class="highlight"><pre><span></span><code><span class="nl">ret_null:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">fac</span><span class="p">),</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">null_str</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">faclo</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>Lors du branchement vers <code>ret_null</code>, <code>A</code> avait été mis à <code>0</code>, c'est donc cette valeur que l'on met dans l'<strong>accumulateur flottant</strong>, qui contient la valeur de l'expression évaluée. Lorsqu'on lit une chaîne, ce sont dans les deux premiers octets de ce même accumulateur que se trouve un pointeur vers la chaîne évaluée. On y place un pointeur vers une chaîne nulle (<code>null_str</code> contient un <code>$00</code>).</p>
<p><code>POP HL</code> récupère le pointeur sur la ligne en exécution. Pas besoin de mettre en place <code>DE</code>, car cette <strong>branche</strong> de la routine n'est appelée qu'en cas d'<strong>évaluation d'expression</strong>, et comme l'adresse de retour a été <strong>enlevée</strong> de la pile pour comparaison et n'y a pas été remis, le retour ne se fait pas à l'appelant direct (qui irait chercher la valeur de la variable retournée) mais à son appelant précédent (l'évaluateur).</p>
<p>Est-ce que cette <strong>optimisation acrobatique</strong> était bien <strong>nécessaire</strong> ? Je n'ai pas la réponse.</p>
<h3>Reset de la mémoire</h3>
<p>Une dernière chose avant de clore cet article. Le BASIC du VG5000µ efface toutes les variables lorsqu'un programme est lancé avec <code>RUN</code>. On est assuré que la mémoire est « effacée » (le contenu est toujours là, seul les pointeurs sont réinitialisés). Les valeurs spécifiées par un <code>CLEAR</code> sont par contre conservées.</p>
<p>De même, les variables sont effacées au moindre changement dans le listing.</p>
<h3>Un peu de BASIC</h3>
<p>Et pour terminer, un petit listing BASIC qui va afficher les variables présentes en mémoire... et donc du programme en cours d'exécution.</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="vg">DEFFNPK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="o">+</span><span class="il">1</span><span class="p">)</span><span class="o">*</span><span class="il">256</span><span class="o">+</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span>
<span class="nl">20</span><span class="w"> </span><span class="vg">VT</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="o">&</span><span class="s2">"49D8"</span><span class="p">)</span>
<span class="nl">30</span><span class="w"> </span><span class="vg">AT</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="o">&</span><span class="s2">"49DA"</span><span class="p">)</span>
<span class="nl">40</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="p">(</span><span class="vg">AT</span><span class="o">-</span><span class="vg">VT</span><span class="p">)</span><span class="o">/</span><span class="il">6</span><span class="p">;</span><span class="s2">" VARIABLE(S)"</span>
<span class="nl">50</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">PT</span><span class="o">=</span><span class="vg">VT</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="vg">AT</span><span class="il">-1</span><span class="w"> </span><span class="k">STEP</span><span class="w"> </span><span class="il">6</span>
<span class="nl">60</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="kr">CHR$</span><span class="p">(</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">PT</span><span class="p">)</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">127</span><span class="p">);</span>
<span class="nl">70</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="kr">CHR$</span><span class="p">(</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">PT</span><span class="o">+</span><span class="il">1</span><span class="p">)</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">127</span><span class="p">)</span>
<span class="nl">80</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">PT</span>
</code></pre></div>
<p>Affiche</p>
<div class="highlight"><pre><span></span><code> 4 VARIABLE(S)
PK
P
VT
AT
</code></pre></div>
<p>Et <code>PT</code> ? Comme la variable est créés après la récupération de <code>AT</code> (<code>(arytab)</code>), cette variable n'est pas vue par le programme, ce qui est tout à fait conforme à ce qui était attendu.</p>VG5000µ, le BASIC cherche ses lignes2019-08-16T00:00:00+02:002019-08-16T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2019-08-16:/vg5000m-le-basic-cherche-ses-lignes.html<p>Peut-être l'avez vous remarqué, <strong>certains programmes</strong> en BASIC sont construits avec leur <strong>programme principal</strong> avec des numéros de ligne « <strong>hauts</strong> » et ont leurs traitements <strong>souvent appelés</strong> dans les lignes « <strong>basses</strong> ».</p>
<p>Ainsi, dans l'<a href="https://www.triceraprog.fr/vg5000m-un-listing-en-basic.html">article précédent</a> sur le listing, le programme commence par un <code>GOTO 10000</code> et le décodage de ligne …</p><p>Peut-être l'avez vous remarqué, <strong>certains programmes</strong> en BASIC sont construits avec leur <strong>programme principal</strong> avec des numéros de ligne « <strong>hauts</strong> » et ont leurs traitements <strong>souvent appelés</strong> dans les lignes « <strong>basses</strong> ».</p>
<p>Ainsi, dans l'<a href="https://www.triceraprog.fr/vg5000m-un-listing-en-basic.html">article précédent</a> sur le listing, le programme commence par un <code>GOTO 10000</code> et le décodage de ligne est en <code>1300</code>. À vrai dire, avant que je ne stocke les adresses des <code>tokens</code> dans un tableau, le <strong>décodage</strong> systématique de leurs noms étaient dans des <strong>lignes encore plus basses</strong>.</p>
<p>La raison en est toute simple : lorsque le <code>BASIC</code> sur VG5000µ cherche un numéro de ligne, il le cherche <strong>systématiquement depuis le début du programme</strong>.</p>
<h3>C'est où que ça cherche ?</h3>
<p>La <strong>routine de recherche</strong> de ligne est en <code>$2347</code> et est appelée par <code>GOTO</code>, <code>RESTORE</code>, <code>RENUM</code> (beaucoup !), <code>LIST</code>, <code>AUTO</code> (à chaque nouvelle ligne), <code>NEW</code>, mais aussi lorsqu'il s'agit d'<strong>insérer une nouvelle ligne</strong> dans le listing au bon endroit, voire de <strong>remplacer</strong> une ancienne.</p>
<p>Pour appeler cette routine, il suffit d'assigner à <code>DE</code> le <strong>numéro</strong> de ligne <strong>recherché</strong>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">line_search:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">bc</span><span class="p">,</span><span class="kc">$</span><span class="mi">0000</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">txttab</span><span class="p">)</span><span class="w"></span>
<span class="nl">line_search_lp:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">prelin</span><span class="p">),</span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">h</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">l</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">dec</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="nv">z</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">de_compare</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,</span><span class="nv">b</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,</span><span class="nv">c</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ccf</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="nv">z</span><span class="w"></span>
<span class="w"> </span><span class="nf">ccf</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="nv">nc</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">line_search_lp</span><span class="w"></span>
</code></pre></div>
<p>Tout commence avec l'<strong>initialisation</strong> de <code>BC</code> avec $0000 et de <code>HL</code> avec le pointeur de <strong>début de listing</strong>. Puis débute la boucle de recherche.</p>
<p>En <strong>début de boucle</strong>, la ligne contenue dans <code>BC</code> est stocké dans une variable système <code>prelin</code>. Cette variable système contiendra en tout temps le dernier numéro de ligne qui a été trouvé dans le listing.</p>
<p>Jusqu'au <code>RET Z</code>, il s'agit de <strong>lire le contenu</strong> du pointeur de <strong>ligne suivante</strong> et vérifier s'il est égal à <code>$0000</code>. Si c'est le cas, ce <code>RET Z</code> provoque la fin de la routine. Le Carry Flag est à <code>0</code> grâce au <code>OR</code> qui précède, ce qui indiquera que la ligne n'a pas été trouvée.</p>
<p><strong>Pour rappel</strong>, une ligne de BASIC stockée en mémoire commence par un pointeur de chaînage vers la ligne suivante, ou <code>$0000</code> s'il s'agit de la dernière ligne.</p>
<p>On peut être <strong>surpris</strong> par ce <code>DEC HL</code> avant le retour de la routine, compensé par le premier <code>INC HL</code> juste après le <code>RET Z</code>. Comme le BASIC <strong>sauve</strong> un octet en fusionnant les 4 octets nuls terminant le chaînage des lignes avec le <code>$00</code> d'une fin de ligne, le pointeur, qui est un cran après ce <code>$00</code> de fin de ligne, réintègre le besoin des 4 octets nuls de fin de chaînage.</p>
<p>Mais si on ne sort pas de la routine, alors cet octet n'est pas fusionné, il faut remettre <code>HL</code> à sa place initiale.</p>
<p>Après le <code>RET Z</code>, le <strong>numéro de la ligne BASIC</strong>, qui est dans la ligne BASIC stockée formé par les deux octets suivants, est lu dans <code>HL</code>. Puis <strong>comparé</strong> à <code>DE</code> via <code>RST DE_COMPARE</code>.</p>
<p>En se souvenant que le numéro de ligne <strong>recherché</strong> est dans <code>DE</code>, après cette comparaison, il y a trois cas :</p>
<ul>
<li><code>HL</code> et <code>DE</code> <strong>sont égaux</strong> : on a donc <strong>trouvé</strong> la ligne. Le flag <code>Z</code> est mis à <code>1</code>, le Carry Flag est à <code>0</code></li>
<li><code>HL</code> <strong>est supérieur à</strong> <code>DE</code> : on a trouvé un numéro de ligne plus grand que celui recherché. La ligne recherchée <strong>n'existe donc pas</strong>. <code>Z</code> est à <code>0</code>, <code>Carry</code> est à <code>0</code> aussi.</li>
<li><code>HL</code> <strong>est inférieur à</strong> <code>DE</code> : il faut <strong>continuer</strong> à chercher. <code>Z</code> est à <code>0</code>, <code>Carry</code> est à <code>1</code>.</li>
</ul>
<p>Après la comparaison, <code>HL</code> est remis à l'adresse sauvée en début de boucle dans <code>BC</code>, pour revenir sur l'adresse de chaînage. Cette adresse est celle de la ligne que l'on vient de valider comme étant la suivante (voire la bonne !), l'adresse est donc lue à nouveau dans <code>HL</code>.</p>
<p>Les lignes suivantes traitent les <strong>différents cas</strong> de <strong>comparaison</strong> de ligne.</p>
<p>Tout d'abord le couple <code>CCF</code> / <code>RET Z</code> fait <strong>sortir</strong> la routine si la <strong>ligne a été trouvée</strong>. <code>CCF</code> qui inverse la valeur du Carry Flag, le met donc à <code>1</code> dans le cas où <code>Z</code> était à 1. En sortie de routine, le Carry Flag à <code>1</code> indique que la ligne a été trouvée. <code>HL</code> pointe sur le début de la zone de cette ligne.</p>
<p>Puis le couple <code>CCF</code> / <code>RET NC</code> remet le Carry Flag a son état d'après la comparaison entre <code>HL</code> et <code>DE</code> et le test. Dans le cas où <strong>la ligne n'est pas là</strong>, on <strong>sort</strong> de la <strong>routine</strong>, mais donc cette fois avec le Carry Flag à <code>0</code>, indiquant que la ligne n'a pas été trouvée. <code>HL</code> pointe alors vers l'adresse de la ligne suivante, et <code>BC</code> sur la ligne précédente. C'est intéressant dans le cas où l'on veut chaîner une <strong>nouvelle ligne</strong> entre les deux.</p>
<p>Si aucun de ces cas de sortie n'a eu lieu, le <code>JR</code> final repart pour un <strong>nouveau tour</strong> de boucle.</p>
<h3>Et NEXT et RETURN ?</h3>
<p><code>NEXT</code> et <code>RETURN</code> sont <strong>deux instructions</strong> qui provoquent une <strong>rupture de séquence</strong>. Autrement dit, suite à leur exécution, l'exécution continue ailleurs. En début de boucle si la boucle n'est pas terminée avec <code>NEXT</code>, ou après le <code>GOSUB</code> dans le cas du <code>RETURN</code>.</p>
<p>Le <strong>fonctionnement</strong> de ces instructions est <strong>différent</strong>. <code>FOR</code> et <code>GOSUB</code> vont placer le pointeur sur les instructions en train d'être interprétées dans un endroit sûr. <code>GOSUB</code> le place <strong>sur la pile</strong>, <code>FOR</code> aussi... si des <code>FOR</code> sont imbriqués, sinon, dans une <strong>variable système</strong> spécifique <code>(endfor)</code>.</p>
<p>Lorsqu'il faut reprendre l'exécution, le pointeur vers l'instruction en cours en remis à la valeur sauvegardée (avec son numéro de ligne correspondant, une autre variable système à garder cohérente, <code>(curlin)</code>) puis le décodage continue.</p>
<p>Par conséquent, les boucles <code>FOR</code>/<code>NEXT</code> et un <code>RETURN</code> <strong>ne sont pas affectés</strong> par la recherche de numéro de ligne.</p>
<h3>Ça optimise ?</h3>
<p>Choisir pour des destinations de saut de numéros de lignes les plus proches du début du programme est donc <strong>une optimisation</strong>. Mais soyons franc, c'est une <strong>toute petite</strong> optimisation. La recherche par parcours de liste chaînée est rapide en assembleur en comparaison de l’évaluation d'une expression un peu complexe avec des variables à manipuler par exemple.</p>
<p>J'ai pu lire que sur certains <strong>BASICs</strong> d'autres machines, une optimisation avait été faite par le simple fait de rechercher la ligne de destination <strong>par rapport</strong> à la ligne actuelle, privilégiant ainsi la localité des sauts. Est-ce que cela serait efficace sur la ROM VG5000µ ? Peut-être... à suivre.</p>VG5000µ, un listing en BASIC2019-08-12T00:00:00+02:002019-08-12T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2019-08-12:/vg5000m-un-listing-en-basic.html<p>Un programme en <strong>BASIC</strong> qui ferait un listing de lui-même. Reprogrammer l'instruction <code>LIST</code>. C'est un peu <strong>inutile</strong>, mais cela est un <strong>prétexte</strong> pour expliquer comment le programme est stocké en mémoire dans la machine.</p>
<p>Le <strong>VG5000µ</strong> utilise une version du <strong>BASIC-80</strong> de Microsoft. Ce BASIC se retrouve sur d'autres machines …</p><p>Un programme en <strong>BASIC</strong> qui ferait un listing de lui-même. Reprogrammer l'instruction <code>LIST</code>. C'est un peu <strong>inutile</strong>, mais cela est un <strong>prétexte</strong> pour expliquer comment le programme est stocké en mémoire dans la machine.</p>
<p>Le <strong>VG5000µ</strong> utilise une version du <strong>BASIC-80</strong> de Microsoft. Ce BASIC se retrouve sur d'autres machines dans des versions variées, mais dont on retrouve les grands principes.</p>
<h2>Structure générale d'un programme en mémoire</h2>
<p>Comme on l'a vu <a href="https://www.triceraprog.fr/vg5000m-une-cartographie-de-la-memoire-basic.html">précédemment</a>, le BASIC respecte un <strong>pointeur</strong> vers le début du programme qui se nomme (txttab), situé en <code>$488E</code>. Par défaut avec la ROM interne du VG5000µ, si aucune extension ne l'a modifié, (txttab) vaut <code>$49FC</code> au démarrage.</p>
<p>Chaque ligne est composée par les éléments suivants :</p>
<ul>
<li>Une <strong>adresse</strong> mémoire (sur 2 octets) vers l'emplacement de la <strong>ligne suivante</strong>, ou <code>$0000</code> pour marquer la fin de la liste de lignes,</li>
<li>Un <strong>numéro de ligne</strong> (sur 2 octets) qui correspond au numéro de ligne BASIC,</li>
<li>Un <strong>ensemble d'octets</strong> représentant le <strong>contenu</strong> de la ligne, qui se termine pas un <code>$00</code></li>
</ul>
<p>Juste après le zéro se trouve la nouvelle ligne, et la ROM maintient les lignes <strong>dans l'ordre</strong> de leur numéro de ligne BASIC.</p>
<p>L'ensemble des <strong>lignes stockées</strong> forme donc une « <strong>liste chaînée</strong> » selon le schéma suivant.</p>
<p><img alt="Liste chaînée des lignes du BASIC-80 sur VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201908/VG5000-BASIC-Lignes.png"></p>
<p><strong>Note</strong> : comme la dernière ligne est suivi par le pointeur vers l'adresse $0000, le BASIC en profite pour gagner un octet et ne met pas spécifiquement un $00 à la fin de cette ligne, puisqu'il y en a déjà 4.</p>
<p>Pour notre programme de <strong>décodage</strong> de ce qui se trouve dans la mémoire, il va donc falloir d'abord aller récupérer l'adresse de (txttab). On pourrait la mettre en dur, mais faisons ça <strong>proprement</strong>.</p>
<p>Le BASIC sur VG5000µ n'a comme instruction pour <strong>lire la mémoire</strong> que <code>PEEK</code>, qui ne lit qu'un octet. Qu'à cela ne tienne, voici une petite <strong>fonction</strong> qui, donnée une variable <code>P</code> pointant sur une zone mémoire, en retourne la valeur entière sur 16 bits qui y est stockée.</p>
<div class="highlight"><pre><span></span><code><span class="vg">DEFFNPK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="o">+</span><span class="il">1</span><span class="p">)</span><span class="o">*</span><span class="il">256</span><span class="o">+</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span>
</code></pre></div>
<p>De là, on peut donc partir de (txttab) puis de <strong>parcourir</strong> tout le <strong>chaînage</strong> jusqu'à trouver un pointeur nul (<code>$0000</code>).</p>
<p>Ce qui peut donner ça :</p>
<div class="highlight"><pre><span></span><code><span class="nl">10020</span><span class="w"> </span><span class="vg">DEFFNPK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="o">+</span><span class="il">1</span><span class="p">)</span><span class="o">*</span><span class="il">256</span><span class="o">+</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Définition</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">fonction</span><span class="w"> </span><span class="vg">pour</span><span class="w"> </span><span class="vg">lire</span><span class="w"> </span><span class="vg">un</span><span class="w"> </span><span class="vg">entier</span><span class="w"> </span><span class="vg">sur</span><span class="w"> </span><span class="il">2</span><span class="w"> </span><span class="vg">octets</span>
<span class="nl">10030</span><span class="w"> </span><span class="vg">PT</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="o">&</span><span class="s2">"488E"</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Récupération</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="p">(</span><span class="vg">txttab</span><span class="p">)</span>
<span class="nl">10040</span><span class="w"> </span><span class="vg">NX</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="vg">PT</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Lecture</span><span class="w"> </span><span class="vg">du</span><span class="w"> </span><span class="vg">pointeur</span><span class="w"> </span><span class="o">**</span><span class="vg">next</span><span class="o">**</span><span class="w"> </span><span class="p">(</span><span class="vg">ligne</span><span class="w"> </span><span class="vg">suivante</span><span class="p">)</span>
<span class="nl">10050</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">NX</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="il">10200</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Si</span><span class="w"> </span><span class="vg">ce</span><span class="w"> </span><span class="vg">pointeur</span><span class="w"> </span><span class="vg">est</span><span class="w"> </span><span class="vg">nul</span><span class="p">,</span><span class="w"> </span><span class="vg">on</span><span class="w"> </span><span class="vg">sort</span><span class="w"> </span><span class="vg">plus</span><span class="w"> </span><span class="vg">loin</span>
<span class="nl">10060</span><span class="w"> </span><span class="vg">PT</span><span class="o">=</span><span class="vg">PT</span><span class="o">+</span><span class="il">2</span><span class="o">:</span><span class="vg">LI</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="vg">PT</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Sinon</span><span class="p">,</span><span class="w"> </span><span class="vg">on</span><span class="w"> </span><span class="vg">avant</span><span class="w"> </span><span class="vg">le</span><span class="w"> </span><span class="vg">pointeur</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="il">2</span><span class="w"> </span><span class="vg">et</span><span class="w"> </span><span class="vg">on</span><span class="w"> </span><span class="vg">lit</span><span class="w"> </span><span class="vg">le</span><span class="w"> </span><span class="vg">numéro</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">ligne</span>
<span class="nl">10070</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="vg">LI</span><span class="p">;</span><span class="s2">" "</span><span class="p">;</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">On</span><span class="w"> </span><span class="vg">affiche</span><span class="w"> </span><span class="vg">le</span><span class="w"> </span><span class="vg">numéro</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">ligne</span>
<span class="nl">10080</span><span class="w"> </span><span class="vg">PT</span><span class="o">=</span><span class="vg">PT</span><span class="o">+</span><span class="il">2</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">1300</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">On</span><span class="w"> </span><span class="vg">avance</span><span class="w"> </span><span class="vg">le</span><span class="w"> </span><span class="vg">pointeur</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="il">2</span><span class="w"> </span><span class="vg">et</span><span class="w"> </span><span class="vg">on</span><span class="w"> </span><span class="vg">décode</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">ligne</span><span class="w"> </span><span class="p">(</span><span class="vg">voir</span><span class="w"> </span><span class="vg">plus</span><span class="w"> </span><span class="vg">loin</span><span class="p">)</span>
<span class="nl">10090</span><span class="w"> </span><span class="vg">PT</span><span class="o">=</span><span class="vg">NX</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Passage</span><span class="w"> </span><span class="vg">du</span><span class="w"> </span><span class="vg">pointeur</span><span class="w"> </span><span class="vg">vers</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">ligne</span><span class="w"> </span><span class="vg">suivante</span>
<span class="nl">10100</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">10040</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Et</span><span class="w"> </span><span class="vg">on</span><span class="w"> </span><span class="vg">boucle</span><span class="o">...</span>
</code></pre></div>
<p>Voici le <strong>corps principal</strong> du programme écrit. Avec ça, même en ne mettant qu'un <code>RETURN</code> en ligne 1300, vous pouvez voir les <strong>numéros de ligne</strong> du votre programme s'afficher.</p>
<h2>Décodage du contenu des lignes</h2>
<p>Les lignes sont formées d'une <strong>suite d'octets</strong> terminée par un $00, située entre le numéro de la ligne BASIC et l'adresse de début de la ligne suivante.</p>
<p>Le <strong>contenu</strong> d'une ligne peut contenir des <strong>caractères</strong>, mais aussi des <code>tokens</code>. Les <code>tokens</code> (jetons en français) représentent les <strong>instructions</strong>, <strong>fonctions</strong> et <strong>signes</strong> reconnus par le BASIC lorsque la ligne a été analysée à l'entrée par le clavier (sur cassette, les tokens sont enregistrés, et donc chargés, directement).</p>
<p>Un <code>token</code> est facilement reconnaissable, son <strong>bit de poids fort est à 1</strong>. Autrement dit, si le caractère présent est strictmeent supérieur à 127, il s'agit d'un <code>token</code>. D'ailleurs, lorsque l'on entre un programme au clavier, le BASIC fait bien attention de mettre tous les caractères entrés sur 7 bits.</p>
<p>C'est aussi dans ce traitement que le BASIC va chercher, à l'aide d'une <strong>liste de mots-clés</strong> et signes connus, à <strong>reconnaître</strong> et donc de <code>tokeniser</code> la ligne. Dès qu'une <strong>suite de caractères</strong> est un mot-clé ou un signe <strong>connu</strong>, la suite de caractères est <strong>remplacée</strong>, dans la chaîne encodée, par son <code>token</code>.</p>
<p>Si les caractères lus ne forment pas un mot connu, ceux-ci sont copiés tels quels dans la ligne encodée.</p>
<p><strong>Par exemple</strong>, si BASIC encode la ligne : <code>PRINT"HELLO"</code>, le résultat sera la suite <code>$94,'"', 'H','E,'L','L','O','"',$00</code>.</p>
<h3>Le décodage</h3>
<p>Pour <strong>reconstituer</strong> la ligne, il s'agit donc de prendre les caractères un par un et de vérifier. Est-ce un <strong>token</strong> ou pas ? Si ce n'est pas un <code>token</code>, on l'écrit directement à l'écran (ou presque, on va voir plus loin). Si c'est un <strong>token</strong>, on soustrait <code>128</code> à sa valeur pour avoir son index.</p>
<h4>Les tokens</h4>
<p>Reste à retrouver le <code>token</code> dans la table située en <code>$209E</code> et donc un extrait à partir du début ressemble à cela :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="nv">c5</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">e</span><span class="p">,</span><span class="kc">$</span><span class="mi">44</span><span class="p">,</span><span class="kc">$</span><span class="nv">c6</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">f</span><span class="p">,</span><span class="kc">$</span><span class="mi">52</span><span class="p">,</span><span class="kc">$</span><span class="nv">ce</span><span class="p">,</span><span class="kc">$</span><span class="mi">45</span><span class="p">,</span><span class="kc">$</span><span class="mi">58</span><span class="p">,</span><span class="kc">$</span><span class="mi">54</span><span class="w"> </span><span class="c1">; .ND.OR.EXT</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="nv">c4</span><span class="p">,</span><span class="kc">$</span><span class="mi">41</span><span class="p">,</span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="mi">41</span><span class="p">,</span><span class="kc">$</span><span class="nv">c9</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">e</span><span class="p">,</span><span class="kc">$</span><span class="mi">50</span><span class="p">,</span><span class="kc">$</span><span class="mi">55</span><span class="p">,</span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="nv">c4</span><span class="w"> </span><span class="c1">; .ATA.NPUT.</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">49</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">d</span><span class="p">,</span><span class="kc">$</span><span class="nv">d2</span><span class="p">,</span><span class="kc">$</span><span class="mi">45</span><span class="p">,</span><span class="kc">$</span><span class="mi">41</span><span class="p">,</span><span class="kc">$</span><span class="mi">44</span><span class="p">,</span><span class="kc">$</span><span class="nv">cc</span><span class="p">,</span><span class="kc">$</span><span class="mi">45</span><span class="p">,</span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="nv">c7</span><span class="w"> </span><span class="c1">; IM.EAD.ET.</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">4</span><span class="nv">f</span><span class="p">,</span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">f</span><span class="p">,</span><span class="kc">$</span><span class="nv">d2</span><span class="p">,</span><span class="kc">$</span><span class="mi">55</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">e</span><span class="p">,</span><span class="kc">$</span><span class="nv">c9</span><span class="p">,</span><span class="kc">$</span><span class="mi">46</span><span class="p">,</span><span class="kc">$</span><span class="nv">d2</span><span class="p">,</span><span class="kc">$</span><span class="mi">45</span><span class="w"> </span><span class="c1">; OTO.UN.F.E</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">53</span><span class="p">,</span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">f</span><span class="p">,</span><span class="kc">$</span><span class="mi">52</span><span class="p">,</span><span class="kc">$</span><span class="mi">45</span><span class="p">,</span><span class="kc">$</span><span class="nv">c7</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">f</span><span class="p">,</span><span class="kc">$</span><span class="mi">53</span><span class="p">,</span><span class="kc">$</span><span class="mi">55</span><span class="p">,</span><span class="kc">$</span><span class="mi">42</span><span class="w"> </span><span class="c1">; STORE.OSUB</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="nv">d2</span><span class="p">,</span><span class="kc">$</span><span class="mi">45</span><span class="p">,</span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="mi">55</span><span class="p">,</span><span class="kc">$</span><span class="mi">52</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">e</span><span class="p">,</span><span class="kc">$</span><span class="nv">d2</span><span class="p">,</span><span class="kc">$</span><span class="mi">45</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">d</span><span class="p">,</span><span class="kc">$</span><span class="nv">d3</span><span class="w"> </span><span class="c1">; .ETURN.EM.</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">f</span><span class="p">,</span><span class="kc">$</span><span class="mi">50</span><span class="p">,</span><span class="kc">$</span><span class="nv">cf</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">e</span><span class="p">,</span><span class="kc">$</span><span class="nv">cc</span><span class="p">,</span><span class="kc">$</span><span class="mi">50</span><span class="p">,</span><span class="kc">$</span><span class="mi">52</span><span class="p">,</span><span class="kc">$</span><span class="mi">49</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">e</span><span class="w"> </span><span class="c1">; TOP.N.PRIN</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="nv">c4</span><span class="p">,</span><span class="kc">$</span><span class="mi">45</span><span class="p">,</span><span class="kc">$</span><span class="mi">46</span><span class="p">,</span><span class="kc">$</span><span class="nv">d0</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">f</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">b</span><span class="p">,</span><span class="kc">$</span><span class="mi">45</span><span class="p">,</span><span class="kc">$</span><span class="nv">d0</span><span class="p">,</span><span class="kc">$</span><span class="mi">52</span><span class="w"> </span><span class="c1">; T.EF.OKE.R</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">49</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">e</span><span class="p">,</span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="nv">c3</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">f</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">e</span><span class="p">,</span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="nv">cc</span><span class="p">,</span><span class="kc">$</span><span class="mi">49</span><span class="p">,</span><span class="kc">$</span><span class="mi">53</span><span class="w"> </span><span class="c1">; INT.ONT.IS</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="nv">cc</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">c</span><span class="p">,</span><span class="kc">$</span><span class="mi">49</span><span class="p">,</span><span class="kc">$</span><span class="mi">53</span><span class="p">,</span><span class="kc">$</span><span class="mi">54</span><span class="p">,</span><span class="kc">$</span><span class="nv">c3</span><span class="p">,</span><span class="kc">$</span><span class="mi">4</span><span class="nv">c</span><span class="p">,</span><span class="kc">$</span><span class="mi">45</span><span class="p">,</span><span class="kc">$</span><span class="mi">41</span><span class="w"> </span><span class="c1">; T.LIST.LEA</span><span class="w"></span>
<span class="w"> </span><span class="nf">...</span><span class="w"></span>
</code></pre></div>
<p>Cette <strong>table</strong> est <strong>encodée</strong> de la manière suivante : les mots-clés sont les uns à la suite des autres. Chaque <strong>premier caractère</strong> a son bit de poids fort à 1, ce qui marque la séparation entre les mots.</p>
<p><strong>Faisons un essai</strong> : <code>$94</code> est le <code>token</code> pour <code>PRINT</code>, soit 148 en décimal. 148 - 128 = 20. Mais attention, le premier token est 128, donc il faut commencer à compter à partir de <code>0</code>. Cherchez le <strong>21ième</strong> mot clé et vous lirez <code>.RINT</code> (attention, le <code>.PRINT</code> précédent est le codage de <code>LPRINT</code>)</p>
<p>Pour faire plus <strong>simple</strong>, vous pouvez aussi compter <strong>21 points</strong>.</p>
<p>Dans mon <strong>premier essai</strong> de programme, j'avais traduit en BASIC ce que fait la ROM en assembleur : à chaque mot-clé, on <strong>parcours la table</strong> des mots-clés depuis le début. À chaque fois que l'on rencontre un caractère supérieur ou égal a 128, on décroit le numéro du <code>token</code> d'une unité. <strong>Arrivé à zéro</strong>, on a trouvé, on peut donc recopier à l'écran tous les caractères (sur 7 bits) jusqu'au prochain supérieur ou égal à 128, exclu.</p>
<p><strong>Ça fonctionne</strong>. Mais ce qui est très rapide en assembleur demande beaucoup de manipulations au BASIC. Parcourir un long tableau et faire des comparaisons et des tests est <strong>très lourd</strong>, même en usant de quelques astuces.</p>
<p>Lorsqu'il fallait décoder un <code>token</code> élevé, comme le signe <code>=</code> (<code>token</code> 65) ou la fonction <code>MID$</code> (<code>token</code> 95, le dernier), le décodage devenait très long, <strong>très très long</strong>.</p>
<p>J'ai donc finalement opté pour payer <strong>le coût</strong> de décodage une fois, au détriment d'une <strong>consommation mémoire</strong> plus importante.</p>
<div class="highlight"><pre><span></span><code><span class="nl">5000</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"INITIALISATION..."</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Affichage</span><span class="w"> </span><span class="vg">d</span><span class="c1">'un message, parce que c'est un peu long</span>
<span class="nl">5010</span><span class="w"> </span><span class="kd">DIM</span><span class="w"> </span><span class="vg">K</span><span class="p">(</span><span class="il">95</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Il</span><span class="w"> </span><span class="vg">y</span><span class="w"> </span><span class="il">96</span><span class="w"> </span><span class="vg">tokens</span><span class="p">,</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="vg">à</span><span class="w"> </span><span class="il">95</span>
<span class="nl">5020</span><span class="w"> </span><span class="vg">KT</span><span class="o">=&</span><span class="s2">"209E"</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Adresse</span><span class="w"> </span><span class="vg">du</span><span class="w"> </span><span class="vg">début</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">table</span>
<span class="nl">5030</span><span class="w"> </span><span class="vg">KW</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Initialisation</span><span class="w"> </span><span class="vg">avec</span><span class="w"> </span><span class="vg">le</span><span class="w"> </span><span class="vg">Keyword</span><span class="w"> </span><span class="il">0</span>
<span class="nl">5040</span><span class="w"> </span><span class="vg">K</span><span class="p">(</span><span class="vg">KW</span><span class="p">)</span><span class="o">=</span><span class="nl">KT:</span><span class="vg">KW</span><span class="o">=</span><span class="vg">KW</span><span class="o">+</span><span class="il">1</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Adresse</span><span class="w"> </span><span class="vg">du</span><span class="w"> </span><span class="vg">Keyword</span><span class="w"> </span><span class="vg">courant</span><span class="w"> </span><span class="vg">dans</span><span class="w"> </span><span class="vg">le</span><span class="w"> </span><span class="vg">tableau</span><span class="w"> </span><span class="vg">et</span><span class="w"> </span><span class="vg">augmentation</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">l</span><span class="c1">'index</span>
<span class="nl">5050</span><span class="w"> </span><span class="vg">KT</span><span class="o">=</span><span class="vg">KT</span><span class="o">+</span><span class="il">1</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Déplacement</span><span class="w"> </span><span class="vg">à</span><span class="w"> </span><span class="vg">l</span><span class="c1">'adresse suivante de la table</span>
<span class="nl">5060</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">KT</span><span class="p">)</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">128</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="il">5080</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Si</span><span class="w"> </span><span class="vg">bit</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">poids</span><span class="w"> </span><span class="vg">fort</span><span class="p">,</span><span class="w"> </span><span class="vg">on</span><span class="w"> </span><span class="vg">saute</span>
<span class="nl">5070</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">5050</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Sinon</span><span class="p">,</span><span class="w"> </span><span class="vg">on</span><span class="w"> </span><span class="vg">boucle</span><span class="w"> </span><span class="vg">sur</span><span class="w"> </span><span class="vg">l</span><span class="c1">'avancée dans la table</span>
<span class="nl">5080</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="p">(</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">KT</span><span class="p">)</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">127</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">RETURN</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Si</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">caractère</span><span class="w"> </span><span class="vg">est</span><span class="w"> </span><span class="err">$</span><span class="il">80</span><span class="p">,</span><span class="w"> </span><span class="vg">c</span><span class="c1">'est fini !</span>
<span class="nl">5090</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">5040</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Sinon</span><span class="p">,</span><span class="w"> </span><span class="vg">on</span><span class="w"> </span><span class="vg">boucle</span><span class="w"> </span><span class="vg">sur</span><span class="w"> </span><span class="vg">l</span><span class="c1">'enregistrement de l'adresse</span>
</code></pre></div>
<p><strong>Note</strong> : j'aurai pu écrire ça avec une boucle <code>FOR</code> car pour cette ROM là, je connais la taille de la table, qui ne bougera pas, mais je voulais garder ce bout de code <strong>flexible</strong>. Il fonctionnera pour une autre tableau d'un autre <code>BASIC-80</code>.</p>
<p>Décoder le <code>token</code> revient maintenant à aller chercher son adresse dans la table puis de recopier les caractères jusqu'au premier marquant le début du mot suivant.</p>
<div class="highlight"><pre><span></span><code><span class="mf">1500</span><span class="w"> </span><span class="c1">REM KT <- ADRESSE TOKEN(V)</span>
<span class="mf">1510</span><span class="w"> </span><span class="n">KW</span><span class="o">=</span><span class="n">V</span><span class="o">-</span><span class="mf">128</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="c1">REM Calcul du token à partir du caractère lu</span>
<span class="mf">1520</span><span class="w"> </span><span class="n">KT</span><span class="o">=</span><span class="n">K</span><span class="p">(</span><span class="n">KW</span><span class="p">)</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="c1">REM Lecture de l'adresse de son nom en mémoire</span>
<span class="mf">1530</span><span class="w"> </span><span class="c1">REM PRINT(KEYWORD(KT))</span>
<span class="mf">1540</span><span class="w"> </span><span class="n">R$</span><span class="o">=</span><span class="s">""</span><span class="p">:</span><span class="n">C</span><span class="o">=</span><span class="nb">PEEK</span><span class="p">(</span><span class="n">KT</span><span class="p">)</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="c1">REM Préparation de la chaîne et lecture du premier caractère</span>
<span class="mf">1550</span><span class="w"> </span><span class="n">R$</span><span class="o">=</span><span class="n">R$</span><span class="o">+</span><span class="nb">CHR$</span><span class="p">(</span><span class="n">C</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="mf">127</span><span class="p">)</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="c1">REM Construction de la chaîne</span>
<span class="mf">1560</span><span class="w"> </span><span class="n">KT</span><span class="o">=</span><span class="n">KT</span><span class="o">+</span><span class="mf">1</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="c1">REM Passage à l'adresse suivante</span>
<span class="mf">1570</span><span class="w"> </span><span class="n">C</span><span class="o">=</span><span class="nb">PEEK</span><span class="p">(</span><span class="n">KT</span><span class="p">)</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="c1">REM Lecture du caractère suivant</span>
<span class="mf">1580</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="n">C</span><span class="o">></span><span class="mf">127</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="mf">1600</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="c1">REM Si c'est le mot suivant, on saute plus loin</span>
<span class="mf">1590</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="mf">1550</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="c1">REM SInon, on boucle sur la construction</span>
<span class="mf">1600</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="n">R$</span><span class="p">;:</span><span class="kr">RETURN</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="c1">REM Le mot est trouvé, on l'affiche et on revient à l'appelant</span>
</code></pre></div>
<p><strong>Note</strong> : j'aurai pu afficher directement chaque caractère plutôt que de construire la chaîne au fur et à mesure. Je n'ai pas vérifié ce qui serait le plus rapide. Cette version là use pas mal la mémoire de chaînes de caractère, c'est certain.</p>
<h4>Les entiers compactés</h4>
<p>Je n'ai aucune idée de si c'est leur nom officiel, mais c'est comme celui que je les appelle. Certains <strong>nombres sont encodées</strong> par le BASIC dans une forme particulière : le caractère <code>$0E</code> suivi d'un nombre entier codé sur 16 bits (donc deux octets).</p>
<p>En particulier, les numéros de lignes sont codées de cette manière. Ainsi les instructions de saut comme <code>GOTO</code> ou <code>GOSUB</code> n'ont pas à décoder systématiquement le numéro en paramètre. Il a déjà été décodé et stocké dans la ligne.</p>
<p>Afficher un <strong>entier compacté</strong> n'est pas très compliqué grâce à la fonction de lecture d'un entier sur 16 bits.</p>
<div class="highlight"><pre><span></span><code><span class="nl">2999</span><span class="w"> </span><span class="c1">REM PRINT(NUM16(I+1))</span>
<span class="nl">3000</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="vg">I</span><span class="o">+</span><span class="il">1</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">I</span><span class="w"> </span><span class="vg">pointe</span><span class="w"> </span><span class="vg">vers</span><span class="w"> </span><span class="vg">le</span><span class="w"> </span><span class="vg">caractère</span><span class="w"> </span><span class="err">$</span><span class="il">0</span><span class="vg">E</span><span class="p">,</span><span class="w"> </span><span class="vg">l</span><span class="c1">'entier est juste après</span>
<span class="nl">3010</span><span class="w"> </span><span class="kr">PRINT</span><span class="p">(</span><span class="vg">FNPK</span><span class="p">(</span><span class="vg">I</span><span class="p">));</span><span class="s2">":"</span><span class="p">;</span>
<span class="nl">3020</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="vg">I</span><span class="o">+</span><span class="il">2</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">On</span><span class="w"> </span><span class="vg">saute</span><span class="w"> </span><span class="vg">les</span><span class="w"> </span><span class="vg">deux</span><span class="w"> </span><span class="vg">caractères</span><span class="w"> </span><span class="vg">lus</span>
<span class="nl">3030</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<p><strong>Note</strong> : lorsque le BASIC utilise un entier compacté, il induit aussi une <strong>fin d'instruction</strong>. Il n'encode donc pas le séparateur ':' éventuel. Je l'affiche donc ici systématiquement, ce qui n'est pas tout à fait correct car il apparaît parfois en fin de ligne. Ce n'est pas syntaxiquement faux non plus.</p>
<h4>Les différents cas</h4>
<p>Il y a donc <strong>trois cas</strong> pour un caractère à décoder, auquel j'<strong>ajoute une quatrième</strong> :</p>
<ul>
<li>Si le caractère est un <code>token</code>, on le décode,</li>
<li>Sinon, si le caractère est <code>$0E</code>, on décode un entier compacté,</li>
<li>Sinon, s'il est inférieur à 17 ou égal à 30 et 31, c'est un caractère de contrôle, on le remplace par le caractère espace,</li>
<li>Sinon, on le recopie tel quel.</li>
</ul>
<p>Ce qui donne :</p>
<div class="highlight"><pre><span></span><code><span class="nl">1300</span><span class="w"> </span><span class="c1">REM DECODE LA LIGNE ENTRE PT ET NX</span>
<span class="nl">1310</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="vg">PT</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="vg">NX</span><span class="il">-1</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Décodage</span><span class="w"> </span><span class="vg">sur</span><span class="w"> </span><span class="vg">tout</span><span class="w"> </span><span class="vg">le</span><span class="w"> </span><span class="vg">contenu</span><span class="w"> </span><span class="p">(</span><span class="vg">voir</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">note</span><span class="w"> </span><span class="vg">juste</span><span class="w"> </span><span class="vg">après</span><span class="p">)</span>
<span class="nl">1320</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">I</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Lecture</span><span class="w"> </span><span class="vg">du</span><span class="w"> </span><span class="vg">caractère</span>
<span class="nl">1330</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">V</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">128</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">1500:</span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">1370</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Est</span><span class="o">-</span><span class="vg">ce</span><span class="w"> </span><span class="vg">un</span><span class="w"> </span><span class="vg">token</span><span class="w"> </span><span class="o">?</span>
<span class="nl">1340</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="il">14</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">3000:</span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">1370</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Est</span><span class="o">-</span><span class="vg">ce</span><span class="w"> </span><span class="vg">un</span><span class="w"> </span><span class="vg">entier</span><span class="w"> </span><span class="vg">comptacté</span><span class="w"> </span><span class="o">?</span>
<span class="nl">1350</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">V</span><span class="o"><</span><span class="il">17</span><span class="w"> </span><span class="ow">OR</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="il">30</span><span class="w"> </span><span class="ow">OR</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="il">31</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="il">32</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Est</span><span class="o">-</span><span class="vg">ce</span><span class="w"> </span><span class="vg">un</span><span class="w"> </span><span class="vg">caractère</span><span class="w"> </span><span class="vg">non</span><span class="w"> </span><span class="vg">affichable</span><span class="w"> </span><span class="o">?</span>
<span class="nl">1360</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="kr">CHR$</span><span class="p">(</span><span class="vg">V</span><span class="p">);</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Affichage</span><span class="w"> </span><span class="vg">du</span><span class="w"> </span><span class="vg">caractère</span>
<span class="nl">1370</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Fin</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">boucle</span>
<span class="nl">1380</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Saut</span><span class="w"> </span><span class="vg">à</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">ligne</span><span class="w"> </span><span class="vg">quand</span><span class="w"> </span><span class="vg">c</span><span class="c1">'est terminé</span>
<span class="nl">1390</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<p><strong>Note</strong> : la ROM ne fait pas tout à fait comme ça, elle décode jusqu'à trouver le $00. Dans la pratique, les lignes sont stockées dans l'ordre du chaînage, par ordre de ligne croissant. Je décode donc entre le début du contenu de la chaîne tokenisé et la ligne suivante.</p>
<h2>Conclusion</h2>
<p>Voilà donc réunies toutes les pièces qui permettent de <strong>lister le programme en mémoire</strong>... et donc le programme lui-même.</p>
<p>L'<strong>instruction</strong> <code>LIST</code> <strong>incluse</strong> dans la ROM du VG5000µ est <strong>meilleure</strong> que cela : les commentaires sont affichés d'une couleur différente, il n'y a pas d'espace avant les numéros de lignes, pas de ':' qui traînent derrière les <code>GOTO</code> et <code>GOSUB</code> en fin de ligne.</p>
<p>Mais l'idée était surtout de montrer comment était encodé le listing en mémoire.</p>
<p>Et pour finir, le programme en entier.</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">10000</span>
<span class="nl">1300</span><span class="w"> </span><span class="c1">REM DECODE LA LIGNE ENTRE PT ET NX</span>
<span class="nl">1310</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="vg">PT</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="vg">NX</span><span class="il">-1</span>
<span class="nl">1320</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">I</span><span class="p">)</span>
<span class="nl">1330</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">V</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">128</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">1500:</span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">1370</span>
<span class="nl">1340</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="il">14</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">3000:</span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">1370</span>
<span class="nl">1350</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">V</span><span class="o"><</span><span class="il">17</span><span class="w"> </span><span class="ow">OR</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="il">30</span><span class="w"> </span><span class="ow">OR</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="il">31</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">V</span><span class="o">=</span><span class="il">32</span>
<span class="nl">1360</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="kr">CHR$</span><span class="p">(</span><span class="vg">V</span><span class="p">);</span>
<span class="nl">1370</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span>
<span class="nl">1380</span><span class="w"> </span><span class="kr">PRINT</span>
<span class="nl">1390</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">1500</span><span class="w"> </span><span class="c1">REM KT <- ADRESSE TOKEN(V)</span>
<span class="nl">1510</span><span class="w"> </span><span class="vg">KW</span><span class="o">=</span><span class="vg">V</span><span class="il">-128</span>
<span class="nl">1520</span><span class="w"> </span><span class="vg">KT</span><span class="o">=</span><span class="vg">K</span><span class="p">(</span><span class="vg">KW</span><span class="p">)</span>
<span class="nl">1530</span><span class="w"> </span><span class="c1">REM PRINT(KEYWORD(KT))</span>
<span class="nl">1540</span><span class="w"> </span><span class="vg">R$</span><span class="o">=</span><span class="s2">""</span><span class="o">:</span><span class="vg">C</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">KT</span><span class="p">)</span>
<span class="nl">1550</span><span class="w"> </span><span class="vg">R$</span><span class="o">=</span><span class="vg">R$</span><span class="o">+</span><span class="kr">CHR$</span><span class="p">(</span><span class="vg">C</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">127</span><span class="p">)</span>
<span class="nl">1560</span><span class="w"> </span><span class="vg">KT</span><span class="o">=</span><span class="vg">KT</span><span class="o">+</span><span class="il">1</span>
<span class="nl">1570</span><span class="w"> </span><span class="vg">C</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">KT</span><span class="p">)</span>
<span class="nl">1580</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">C</span><span class="o">></span><span class="il">127</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="il">1600</span>
<span class="nl">1590</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">1550</span>
<span class="nl">1600</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="vg">R$</span><span class="p">;</span><span class="o">:</span><span class="kr">RETURN</span>
<span class="nl">2999</span><span class="w"> </span><span class="c1">REM PRINT(NUM16(I+1))</span>
<span class="nl">3000</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="vg">I</span><span class="o">+</span><span class="il">1</span>
<span class="nl">3010</span><span class="w"> </span><span class="kr">PRINT</span><span class="p">(</span><span class="vg">FNPK</span><span class="p">(</span><span class="vg">I</span><span class="p">));</span><span class="s2">":"</span><span class="p">;</span>
<span class="nl">3020</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="vg">I</span><span class="o">+</span><span class="il">2</span>
<span class="nl">3030</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">5000</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"INITIALISATION..."</span>
<span class="nl">5010</span><span class="w"> </span><span class="kd">DIM</span><span class="w"> </span><span class="vg">K</span><span class="p">(</span><span class="il">94</span><span class="p">)</span>
<span class="nl">5020</span><span class="w"> </span><span class="vg">KT</span><span class="o">=&</span><span class="s2">"209E"</span>
<span class="nl">5030</span><span class="w"> </span><span class="vg">KW</span><span class="o">=</span><span class="il">0</span>
<span class="nl">5040</span><span class="w"> </span><span class="vg">K</span><span class="p">(</span><span class="vg">KW</span><span class="p">)</span><span class="o">=</span><span class="nl">KT:</span><span class="vg">KW</span><span class="o">=</span><span class="vg">KW</span><span class="o">+</span><span class="il">1</span>
<span class="nl">5050</span><span class="w"> </span><span class="vg">KT</span><span class="o">=</span><span class="vg">KT</span><span class="o">+</span><span class="il">1</span>
<span class="nl">5060</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">KT</span><span class="p">)</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">128</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="il">5080</span>
<span class="nl">5070</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">5050</span>
<span class="nl">5080</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="p">(</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">KT</span><span class="p">)</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">127</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">5090</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">5040</span>
<span class="nl">10000</span><span class="w"> </span><span class="c1">REM DEMARRAGE</span>
<span class="nl">10010</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">5000</span>
<span class="nl">10020</span><span class="w"> </span><span class="vg">DEFFNPK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="o">+</span><span class="il">1</span><span class="p">)</span><span class="o">*</span><span class="il">256</span><span class="o">+</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">P</span><span class="p">)</span>
<span class="nl">10030</span><span class="w"> </span><span class="vg">PT</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="o">&</span><span class="s2">"488E"</span><span class="p">)</span>
<span class="nl">10040</span><span class="w"> </span><span class="vg">NX</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="vg">PT</span><span class="p">)</span>
<span class="nl">10050</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">NX</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="il">10200</span>
<span class="nl">10060</span><span class="w"> </span><span class="vg">PT</span><span class="o">=</span><span class="vg">PT</span><span class="o">+</span><span class="il">2</span><span class="o">:</span><span class="vg">LI</span><span class="o">=</span><span class="vg">FNPK</span><span class="p">(</span><span class="vg">PT</span><span class="p">)</span>
<span class="nl">10070</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="vg">LI</span><span class="p">;</span><span class="s2">" "</span><span class="p">;</span>
<span class="nl">10080</span><span class="w"> </span><span class="vg">PT</span><span class="o">=</span><span class="vg">PT</span><span class="o">+</span><span class="il">2</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">1300</span>
<span class="nl">10090</span><span class="w"> </span><span class="vg">PT</span><span class="o">=</span><span class="vg">NX</span>
<span class="nl">10100</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">10040</span>
<span class="nl">10200</span><span class="w"> </span><span class="kr">PRINT</span><span class="s2">"FINI"</span>
<span class="nl">11000</span><span class="w"> </span><span class="kr">END</span>
</code></pre></div>VG5000µ, une Cartographie de la Mémoire BASIC2019-08-08T00:00:00+02:002019-08-08T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2019-08-08:/vg5000m-une-cartographie-de-la-memoire-basic.html<p>Quand j'ai commencé à étudier les documentations sur le VG5000µ, des incohérences sur certains points me sont apparues. Les différentes sources ne donnaient pas les même renseignements. Ou alors je lisais mal. Mais un truc ne collait pas.</p>
<p>En étudiant la ROM, là encore, ça ne collait pas avec les …</p><p>Quand j'ai commencé à étudier les documentations sur le VG5000µ, des incohérences sur certains points me sont apparues. Les différentes sources ne donnaient pas les même renseignements. Ou alors je lisais mal. Mais un truc ne collait pas.</p>
<p>En étudiant la ROM, là encore, ça ne collait pas avec les documentations. Mais le code ne ment pas, j'ai donc débuté une quête de la vérité : que se passe-t-il vraiment ?</p>
<p>... ce n'était peut-être pas si épique, mais bon...</p>
<p>L'anomalie qui m'intéresse aujourd'hui est celle de la gestion de la mémoire par le BASIC. Et voici les pièces qui ont provoqué mon étonnement.</p>
<h3>Première pièce</h3>
<p>La <strong>manuel de l'utilisateur</strong>, page 46, indique que le <strong>premier paramètre</strong> de la commande définit la totalité de l'espace occupé par les chaînes de caractères et doit être compris entre -32768 et 32767. Spécifier une mémoire à réserver négative est un peu étrange, et un test rapide montre que passer une valeur négative est rejetée par le BASIC.</p>
<p>Au passage, un coup d'œil dans la ROM montre que l'instruction <code>CLEAR</code> utilise une routine qui ne décode que des entiers positifs...</p>
<p>Plus étonnant, le <strong>second paramètre</strong>, qui indique la plus haute adresse possible atteignable par le BASIC doit être inférieur à 32767 pour le VG5000 standard, et 42864 pour l'extension mémoire 16ko. Et ne mentionne pas 32ko d'extension.</p>
<p>42864... Ça ne correspond pas à grand chose. Avec l'extension 16ko, je vois plutôt une adresse maximale à 49151. En effet, la ROM prend les 16 premiers ko, et les 16+16 ko de RAM sont à la suite. 48 * 1024 donne 49152 octets.</p>
<p>Bref, <strong>louche</strong>. De plus, un essai direct d'un <code>CLEAR 50, 42864</code> ne fonctionne pas, un paramètre invalide est indiqué. La routine pour décoder le second paramètre étant une routine qui prend des entiers entre <code>-32768</code> et <code>32767</code>.</p>
<p>Une cartographie de la mémoire <strong>page 86</strong> indique que la mémoire peut bien aller jusqu'à <strong>49151</strong>, qu'il faut spécifier cette valeur en complément à 2 sous une forme négative. C'est incohérent avec la description de <code>CLEAR</code> mais l'expérience montre que la <strong>cartographie a raison</strong>. Elle indique aussi des valeur de pointeur (stktop), (fretop), (arytab) et (txttab), mais n'indique pas où les trouver.</p>
<h3>Deuxième pièce</h3>
<p>La deuxième source est « <strong>Clefs pour VG5000</strong> », un livre des éditions du P.S.I.</p>
<p><strong>Page 15</strong>, la description de CLEAR est globalement reprise avec les mêmes renseignements erronés.</p>
<p><strong>Page 69</strong>, une cartographie de la mémoire est disponible et indique des noms de variables pointeurs sur des zones mémoire. Plus loin, ces variables systèmes sont décrites, avec leurs adresses. C'est par ici que j'avais commencé à regarder le fonctionnement.</p>
<p>Deux choses me <strong>paraissaient bizarre</strong>. Une flèche entre <code>arytab</code> et <code>fretop</code> sans nom. Soit. Mais aussi une variable <code>fretop</code> qui est sous la pile. C'est bizarre car le bas de la pile, c'est déjà le registre <code>SP</code>, quel intérêt de garder une information comme ça ? Pour protéger la pile ?</p>
<p>C'est cependant cohérent avec le manuel d'utilisateur et donne une information en plus sur le fait que (memsiz) n'est pas initialisé tout en haut de la RAM disponible mais trois octets avant. Il ajoute une variable de système : (vartab).</p>
<p>C'est à ce moment là que j'ai commencé à étudier la <strong>ROM</strong> et voir que (fretop) était initialisé avec la valeur (memsiz), et que lors d'allocation de chaînes de caractères, l'algorithme partait du principe que (fretop) était toujours supérieur à (stktop).</p>
<p>En fait, l'<strong>erreur d'allocation</strong> de chaîne était lancée, si je ne me trompais pas (mais le doute est permis quand on regarde des pages de code assembleur), lorsque (fretop) atteignant (stktop) <strong>par le haut</strong>. Autrement dit, tant que <code>(fretop)-(stktop)</code> était positif, il y avait de la place. Je détaille ça <strong>plus loin</strong>.</p>
<p>Et donc, <strong>ça ne collait pas</strong> avec le schéma.</p>
<p>Les descriptions des variables sont les suivantes :</p>
<ul>
<li><code>$488E</code> : adresse de début du <strong>programme</strong> Basic (txttab)</li>
<li><code>$4895</code> : Adresse du haut de la <strong>pile</strong> (stktop)</li>
<li><code>$49C3</code> : adresse du haut de la zone <strong>"chaînes"</strong> (fretop)</li>
<li><code>$49D8</code> : adresse de début de la zone <strong>"variables"</strong> (vartab)</li>
<li><code>$49DA</code> : adresse de début de la zone <strong>"chaînes"</strong> (arytab) (c'est incohérent d'après le schéma, mais on se doute bien que <strong>ary</strong> signifie <strong>array</strong> est qu'il s'agit en fait de la zone pour les <strong>tableaux</strong> <code>DIM</code>)</li>
<li><code>$49DC</code> : adresse de <strong>fin du stockage</strong> en cours, n'est pas nommé. Je ne le savais pas à ce moment-là, mais il s'agit de (strend) et c'est le nom qu'il manque entre (arytab) et (fretop)... il faut dire que la description n'est pas hyper parlante. Quel stockage ? En cours de quoi ?</li>
</ul>
<h3>Troisième pièce</h3>
<p>Le troisième document dans lequel je me suis plongé est celui du <strong>manuel technique</strong>, en anglais. <strong>Page 6</strong>, on trouve le tableau source des deux précédents documents., mais là encore, (fretop) est sous la pile...</p>
<p>Plus loin, <strong>page 9</strong>, la liste des variables systèmes, là encore probablement la source du livre P.S.I, est donné. On y trouve (txttab), (stktop), (fretop), (vartab), (arytab) avec sa description correcte mentionnant des tableaux, et (fretop), et (strend) indiquant « end of storage in use ».</p>
<p>La description de (strend) est <strong>un peu plus éclairante</strong> que sa traduction dans le livre P.S.I, même si ça manque de détail. Il n’apparaît pas dans la cartographie de la mémoire.</p>
<p>Il n'y a pas d'autres mentions du fonctionnement des allocations mémoire dans ce document, à part un peu à propos de manipulation de (txttab) et (vartab) quand on veut lancer un programme BASIC depuis un ROM d'extension.</p>
<h3>Quatrième pièce</h3>
<p>De tous les documents sur le VG5000µ, les plus <strong>dignes de confiances, ceux qui vont un peu plus dans le détail et dans lesquels je n'ai jamais trouvé d'erreur jusqu'à maintenant, ce sont les « </strong>Technical Bulletin** ». Sur celui du 14 juin 1984, à propos de « BASIC Text Relocation » il est mentionné que (strend) sera bougé, avec (vartab), (arytab) et (temp), si (txttab) est modifié et que le BASIC exécute une des routines qui replace tout ça.</p>
<p>Pas tellement plus d'information cependant.</p>
<p>Dans le bulletin du 11 septembre 1984, une mention de (stktop) et (memsiz) est fait, qui donne les bonnes adresses en fonction des différentes configuration de mémoire.</p>
<h3>Au final...</h3>
<p>Au final... et bien le mieux est d'aller voir dans la <strong>ROM</strong> ce qu'il se passe. Et pour chercher, je pars sur deux pistes : où donc est-ce que (fretop) est utilisé, et qu'est-ce que (strend) ?</p>
<p>(fretop) est utilisé a de nombreux endroits, mais quelques-uns de ses usages sont suffisant pour comprendre.</p>
<h4>L’initialisation</h4>
<p>La fonction suivante est appelé lorsqu'il s'agit de <strong>réinitialiser</strong> la mémoire. Au lancement de la machine, par un appel à <code>NEW</code>, mais aussi directement en <code>reset_vars</code> lors d'un <code>RUN</code> ou en <code>init_vars</code> lors d'un <code>CLEAR</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">reset_mem:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">txttab</span><span class="p">)</span><span class="w"> </span><span class="c1">; $2ed9</span><span class="w"></span>
<span class="nl">reset_mem_2:</span><span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">reset_mem_3</span><span class="w"></span>
<span class="nl">reset_mem_4:</span><span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">vartab</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="nl">reset_vars:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">txttab</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">dec</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="nl">init_vars:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">temp</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">memsiz</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">fretop</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">inst_restore</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">vartab</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">arytab</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">strend</span><span class="p">),</span><span class="nv">hl</span><span class="w"></span>
</code></pre></div>
<p>Tout commence avec comme point de repère (txttab), qui est le <strong>début du programme BASIC</strong> en cours.</p>
<p>Le <code>jp reset_mem_3</code> renvoie en <code>reset_mem_4</code> après avoir coupé le lien sur la première ligne de <code>BASIC</code>, effaçant par la même occasion l'accès au programme (qui est toujours là...).</p>
<p>Cette gymnastique avec <code>reset_mem_3</code>, qui n'est jamais appelé par ailleurs, vient à mon avis du <strong>patch</strong> de la ROM 1.1, qui fait tout pour rester stable dans les adresses de routines. Je vérifierai cette théorie plus tard.</p>
<p>(vartab) est placé deux octets plus loin que (txttab) (HL est aussi incrémenté par le code de <code>reset_mem_3</code>)</p>
<p>(temp) est placé un octet avec (txttab)</p>
<p>(fretop) est initialisé à la même adresse que (memsiz), ce qui montre bien que, au moins là, <strong>les cartographie mémoire sont fausses</strong>.</p>
<p>L'appel à <code>inst_restore</code> est l'exécution de l'instruction <code>RESTORE</code> du BASIC, qui va chercher et stocker la première ligne contenant des <code>DATA</code>.</p>
<p>Puis enfin, (arytab) et (strend) prennent la valeur de (vartab).</p>
<p>Un peu plus loin, les lignes suivantes remettent le registre <code>SP</code> à l'adresse en haut de sa zone, stockée dans (stktop).</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">stktop</span><span class="p">)</span><span class="w"> </span><span class="c1">; $2eff</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nb">sp</span><span class="p">,</span><span class="nv">hl</span><span class="w"></span>
</code></pre></div>
<p>La conclusion sur l’initialisation de la mémoire est que les <strong>adresses de références</strong> sont (txttab), (memsiz) et (stktop). Les autres pointeurs sont placés en fonction de ces adresses.</p>
<h4>Vérification mémoire de chaînes</h4>
<p>Lorsqu'une chaîne de caractère est sur le point d'être créé, la routine suivante est appelée en premier lieu :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">stktop</span><span class="p">)</span><span class="w"> </span><span class="c1">; $36bf</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">fretop</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">cpl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="kc">$</span><span class="nv">ff</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">de_compare</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">out_str_mem</span><span class="w"></span>
</code></pre></div>
<p>À l'entrée de cette routine, <code>A</code> contient le <strong>nombre de caractères</strong> de la chaîne à créer.</p>
<p>Après les trois premières lignes, on se retrouve avec (stktop) dans DE et (fretop) dans HL. Les trois lignes suivantes mettent dans <code>BC</code> l'inverse de <code>A</code> (en complément à 2).</p>
<p>Via le <code>add</code>, HL contient donc (fretop) moins le <strong>nombre de caractères</strong> dont on a besoin, (le inc qui suit est la correction de la soustraction en complément à 2).</p>
<p><code>rst de_compare</code> est une routine qui <strong>compare</strong> les valeurs de <code>HL</code> et <code>DE</code>, si <code>HL</code> est inférieur à DE, alors le flag <code>Carry</code> est levé, ce qui provoque le saut qui suit vers l'erreur indiquant qu'il n'y a pas assez de mémoire dans l'espace réservé aux chaînes de caractères.</p>
<p>Ce qui est important ici, c'est que ce calcul montre que (fretop) <strong>doit être supérieur</strong> à (stktop). Ce qui montre encore que les schémas sont faux.</p>
<h4>FRE(" ")</h4>
<p>Dernière vérification, histoire d'être vraiment certain, en analysant la fonction <code>FRE(" ")</code>. Cette commande avec une chaîne en paramètre n'est <strong>pas documentée</strong> dans le manuel d'instruction, mais on la trouve dans les « bulletins ».</p>
<p>Lorsque <code>FRE</code> a un paramètre numérique (peu importe lequel), la fonction renvoie la place restante en mémoire <code>BASIC</code>... pour tout ce qui n'est pas stockage des caractères de chaînes.</p>
<p>Avec un paramètre alphanumérique, <code>FRE</code> renvoie l'espace restant dans <strong>la mémoire réservée aux caractères</strong>.</p>
<p>Et en voici le code :</p>
<div class="highlight"><pre><span></span><code><span class="nl">inst_fre:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">strend</span><span class="p">)</span><span class="w"> </span><span class="c1">; $38b1</span><span class="w"></span>
<span class="w"> </span><span class="nf">ex</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="kc">$</span><span class="mi">0000</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nb">sp</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">valtyp</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">inst_fre_2</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">gstrcu</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">call36e3</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">de</span><span class="p">,(</span><span class="nv">stktop</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,(</span><span class="nv">fretop</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">inst_fre_2</span><span class="w"></span>
<span class="nl">inst_fre_2:</span><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">l</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">e</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">h</span><span class="w"></span>
<span class="w"> </span><span class="nf">sbc</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">d</span><span class="w"></span>
</code></pre></div>
<p>Tout commence en plaçant (strend) dans <code>DE</code> (via <code>HL</code>). Puis en mettant <code>SP</code> dans <code>HL</code> (via le <code>add</code>).</p>
<p>(valtyp) contient le <strong>type de l'expression</strong> qui vient d'être évaluée. À l'appel d'une fonction, il s'agit de la valeur du paramètre. Si cette valeur est numérique (= <code>0</code>), la suite se passe en <code>inst_fre_2</code> où le résultat de l'opération <code>HL - DE</code> est mis dans le couple de registres <code>A</code> et <code>C</code>.</p>
<p>Au passage, notons que la <strong>mémoire restante</strong> est calculée comme la différence entre le pointeur de pile courant et (strend), ce qui permet de situer correctement la fonction de (strend) comme marquant la fin (adresse haute) du stockage BASIC « listing + variables + tableaux ».</p>
<p>Si l'expression était alphanumérique, alors il y a deux appels (dont un auquel je n'ai pas encore donné de nom) qui permettent d'appeler le <strong>ramasse miettes</strong> (Garbage Collection) sur les chaînes de caractères. Passons.</p>
<p>Puis (stktop) est placé dans <code>DE</code> et (fretop) dans <code>HL</code>, avant d'effectuer le même calcul que précédemment <code>HL - DE</code>.</p>
<p>Ce qui montre à nouveau que (fretop) <strong>doit être supérieur à</strong> (stktop) et qui indique même que cet espace correspond à la zone de mémoire <strong>libre</strong> pour les chaînes. Autrement dit, (fretop) présente l'adresse la plus basse des chaînes de caractères, toutes stockées <strong>au-dessus</strong>. Le nom a probablement provoqué la confusion de sa description comme étant l'adresse <strong>du haut</strong>, mais c'est en fait une zone qui croît <strong>vers le bas</strong>.</p>
<h3>En donc ?</h3>
<p>(fretop) est un pointeur qui est valide entre (memsiz) et (stktop). L'espace entre (memsiz) et (stktop) est déterminé par le premier paramètre de <code>CLEAR</code> et (memsiz) est déterminé par le second paramètre de <code>CLEAR</code>.</p>
<p>Et grâce à ces informations, nous pouvons <strong>cartographier</strong>, la mémoire avec ses pointeurs de manière correcte et complète, du moins je l'espère.</p>
<p><img alt="Cartographie Mémoire VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201908/VG5000-CartographieMémoire.png"></p>Récréation 3D, Z80 du VG5000µ2019-06-01T00:00:00+02:002019-06-01T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2019-06-01:/recreation-3d-z80-du-vg5000m.html<p>Deux ans déjà que j'avais créé quelques <a href="https://www.triceraprog.fr/recreation-3d.html">modèles 3D</a>... Le temps passe vite. Et l'envie m'a repris.</p>
<p>Voici donc une petite recréation du Z80 présent dans le VG5000µ. Fait depuis des images et je ne suis donc pas complètement certains des mesures. J'irai vérifier la prochaine fois que j'en démonte …</p><p>Deux ans déjà que j'avais créé quelques <a href="https://www.triceraprog.fr/recreation-3d.html">modèles 3D</a>... Le temps passe vite. Et l'envie m'a repris.</p>
<p>Voici donc une petite recréation du Z80 présent dans le VG5000µ. Fait depuis des images et je ne suis donc pas complètement certains des mesures. J'irai vérifier la prochaine fois que j'en démonte un, si j'y pense.</p>
<p><img alt="Z80 présent dans le VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201906/VG5000-Z80-720.png"></p>
<p>Update: nouvelle version, corrigée avec des dimensions DIP plus correctes (mais le boitier du SGS est plat... ça fait donc un mélange)</p>
<p><img alt="Z80 présent dans le VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201906/VG5000-Z80-fixed-720.png"></p>Bonne Année 2019 !2018-12-31T00:00:00+01:002018-12-31T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2018-12-31:/bonne-annee-2019.html<p>Bonjour à tous. Cette année, j'espère pouvoir faire aboutir un projet autour du VG5000µ que j'ai commencé il y a longtemps et que j'avance petit pas par petit pas.</p>
<p>En attendant, je vous souhaite :</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="vg">B$</span><span class="o">=</span><span class="s2">"AAHGGGGGCB00A@GGGGCBCB00998898=<10"</span>
<span class="nl">20</span><span class="w"> </span><span class="vg">LB</span><span class="o">=</span><span class="kr">LEN</span><span class="p">(</span><span class="vg">B$</span><span class="p">)</span>
<span class="nl">30</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="il">1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="vg">LB</span><span class="w"> </span><span class="k">STEP …</span></code></pre></div><p>Bonjour à tous. Cette année, j'espère pouvoir faire aboutir un projet autour du VG5000µ que j'ai commencé il y a longtemps et que j'avance petit pas par petit pas.</p>
<p>En attendant, je vous souhaite :</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="vg">B$</span><span class="o">=</span><span class="s2">"AAHGGGGGCB00A@GGGGCBCB00998898=<10"</span>
<span class="nl">20</span><span class="w"> </span><span class="vg">LB</span><span class="o">=</span><span class="kr">LEN</span><span class="p">(</span><span class="vg">B$</span><span class="p">)</span>
<span class="nl">30</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">I</span><span class="o">=</span><span class="il">1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="vg">LB</span><span class="w"> </span><span class="k">STEP</span><span class="w"> </span><span class="il">2</span>
<span class="nl">40</span><span class="w"> </span><span class="vg">A</span><span class="o">=</span><span class="kr">ASC</span><span class="p">(</span><span class="kr">MID$</span><span class="p">(</span><span class="vg">B$</span><span class="p">,</span><span class="vg">I</span><span class="p">,</span><span class="il">1</span><span class="p">))</span><span class="o">+</span><span class="kr">ASC</span><span class="p">(</span><span class="kr">MID$</span><span class="p">(</span><span class="vg">B$</span><span class="p">,</span><span class="vg">I</span><span class="o">+</span><span class="il">1</span><span class="p">,</span><span class="il">1</span><span class="p">))</span><span class="il">-64</span>
<span class="nl">50</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="kr">CHR$</span><span class="p">(</span><span class="vg">A</span><span class="p">);</span>
<span class="nl">60</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">I</span>
</code></pre></div>
<p>À essayer sur votre ancienne machine sous BASIC préférée. Ça devrait être portable sur à peu près toutes les machines avec interpréteur BASIC et des capactités par trop limitées sur les chaînes de caractères (adieu ZX81...). Sur certains, il faudra ajouter <code>LET</code> aux lignes 10, 20 et 40 pour les assignations de variable.</p>VG5000µ, ajouter des instructions au BASIC ?2018-10-16T00:00:00+02:002018-10-16T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2018-10-16:/vg5000m-ajouter-des-instructions-au-basic.html<p>Il y a quelques temps, un message sur le <strong>forum</strong> <a href="https://forum.system-cfg.com/">Config.cfg</a> demandait s'il était simple ou même possible d'ajouter des <strong>instructions supplémentaires</strong> à la ROM d'un <strong>VG5000µ</strong>. C'est une question que je me posais aussi, avec dans l'idée d'ajouter des instructions graphiques utilisant l'implémentation des <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-afficher-le-point.html">articles précédents</a>].</p>
<p>J'ai donc …</p><p>Il y a quelques temps, un message sur le <strong>forum</strong> <a href="https://forum.system-cfg.com/">Config.cfg</a> demandait s'il était simple ou même possible d'ajouter des <strong>instructions supplémentaires</strong> à la ROM d'un <strong>VG5000µ</strong>. C'est une question que je me posais aussi, avec dans l'idée d'ajouter des instructions graphiques utilisant l'implémentation des <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-afficher-le-point.html">articles précédents</a>].</p>
<p>J'ai donc continué à étudier la <strong>ROM</strong> (que je commence à bien connaître maintenant) à la recherche d'une méthode. Et on va voir que ça n'est pas gagné.</p>
<h4>Le parseur</h4>
<p>Lorsque la touche <code>RET</code> du clavier est <strong>appuyée</strong>, il se passe <strong>plusieurs choses</strong>. Tout d'abord, un caractère <code>NUL</code> (valeur 0) est placé dans le buffer d'entrée à l'emplacement du dernier caractère qui n'est pas un espace (adresses <code>$3c4a</code> à <code>$3c56</code>). Le <code>RET</code> à la fin de cette fonction ramène hors de la boucle principal du traitement interactif.</p>
<p>Après un traitement de la <strong>protection</strong> de programmes qui n'est pas le sujet ici, une fonction <strong>cherche</strong> si la ligne commence par un <strong>numéro de ligne</strong> puis débute la transcription de la ligne vers une ligne « tokenisée ». La procédure de <strong>tokénisation</strong>, en bref, va repérer toutes les instructions, fonctions et opérations connues et les transformer en « tokens » : un octet dont le bit de poids fort est à 1.</p>
<p>Un token permet de prendre moins de place en mémoire et facilite le travail de l'évaluateur.</p>
<p>La procédure saute bien entendu les chaînes de caractères en repérant les paires de guillemets.</p>
<p>Je <strong>laisse de côté</strong> le traitement de caractères à significations particulières (le <code>?</code> qui remplace <code>PRINT</code> comme dans la plupart des <strong>BASIC</strong> par exemple) pour arriver à la <strong>recherche principale</strong>. Cette routine, qui commence globalement en <code>$23a5</code>, va <strong>comparer</strong> le contenu des caractères à décoder (en les passant en majuscules si nécessaire) avec une <strong>liste de noms</strong> et <strong>symboles</strong> dans une table située en <code>$23a7</code>.</p>
<p><strong>Premier problème</strong>, cette <strong>liste</strong> est <strong>figée</strong>. Si aucune correspondance n'est trouvée, la routine ne dispose pas de hook pour passer la main à une éventuelle liste gérée par l'utilisateur. Les <strong>caractères</strong> sans correspondance sont <strong>laissés tels quels</strong> dans le buffer. Si plus tard, à l'évaluation, ces caractères n'ont pas de sens (ne désignent pas une variable par exemple), une erreur sera émise.</p>
<p>On pourrait alors imaginer se servir d'un autre <code>hook</code>, celui qui est appelé à chaque affichage de caractère (<code>$47e2</code>) ou celui qui est appelé lors d'un retour à la ligne (<code>\$47e5</code>), afin d'ajouter un parsing à la main. Mais pour cela, il faudrait <strong>choisir</strong> des <strong>tokens libres</strong> pour les nouvelles instructions ; ce qui amène à étudier les tokens.</p>
<h4>Les tokens</h4>
<p>Voici la liste des tokens avec leurs valeurs.</p>
<table>
<thead>
<tr>
<th style="text-align: left;">Commandes</th>
<th style="text-align: left;">Support -</th>
<th style="text-align: left;">Opérateurs</th>
<th style="text-align: left;">Fonctions</th>
</tr>
</thead>
<tbody>
<tr>
<td style="text-align: left;">128:END</td>
<td style="text-align: left;">178:TAB(</td>
<td style="text-align: left;">185:+</td>
<td style="text-align: left;">195:SGN</td>
</tr>
<tr>
<td style="text-align: left;">129:FOR</td>
<td style="text-align: left;">179:TO</td>
<td style="text-align: left;">186:-</td>
<td style="text-align: left;">196:INT</td>
</tr>
<tr>
<td style="text-align: left;">130:NEXT</td>
<td style="text-align: left;">180:FN</td>
<td style="text-align: left;">187:*</td>
<td style="text-align: left;">197:ABS</td>
</tr>
<tr>
<td style="text-align: left;">131:DATA</td>
<td style="text-align: left;">181:SPC(</td>
<td style="text-align: left;">188:/</td>
<td style="text-align: left;">198:USR</td>
</tr>
<tr>
<td style="text-align: left;">132:INPUT</td>
<td style="text-align: left;">182:THEN</td>
<td style="text-align: left;">189:^</td>
<td style="text-align: left;">199:FRE</td>
</tr>
<tr>
<td style="text-align: left;">133:DIM</td>
<td style="text-align: left;">183:NOT</td>
<td style="text-align: left;">190:AND</td>
<td style="text-align: left;">200:LPOS</td>
</tr>
<tr>
<td style="text-align: left;">134:READ</td>
<td style="text-align: left;">184:STEP</td>
<td style="text-align: left;">191:OR</td>
<td style="text-align: left;">201:POS</td>
</tr>
<tr>
<td style="text-align: left;">135:LET</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">192:></td>
<td style="text-align: left;">202:SQR</td>
</tr>
<tr>
<td style="text-align: left;">136:GOTO</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">193:=</td>
<td style="text-align: left;">203:RND</td>
</tr>
<tr>
<td style="text-align: left;">137:RUN</td>
<td style="text-align: left;"></td>
<td style="text-align: left;">194:<</td>
<td style="text-align: left;">204:LOG</td>
</tr>
<tr>
<td style="text-align: left;">138:IF</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">205:EXP</td>
</tr>
<tr>
<td style="text-align: left;">139:RESTORE</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">206:COS</td>
</tr>
<tr>
<td style="text-align: left;">140:GOSUB</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">207:SIN</td>
</tr>
<tr>
<td style="text-align: left;">141:RETURN</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">208:TAN</td>
</tr>
<tr>
<td style="text-align: left;">142:REM</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">209:ATN</td>
</tr>
<tr>
<td style="text-align: left;">143:STOP</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">210:PEEK</td>
</tr>
<tr>
<td style="text-align: left;">144:ON</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">211:LEN</td>
</tr>
<tr>
<td style="text-align: left;">145:LPRINT</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">212:STR$</td>
</tr>
<tr>
<td style="text-align: left;">146:DEF</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">213:VAL</td>
</tr>
<tr>
<td style="text-align: left;">147:POKE</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">214:ASC</td>
</tr>
<tr>
<td style="text-align: left;">148:PRINT</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">215:STICKX</td>
</tr>
<tr>
<td style="text-align: left;">149:CONT</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">216:STICKY</td>
</tr>
<tr>
<td style="text-align: left;">150:LIST</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">217:ACTION</td>
</tr>
<tr>
<td style="text-align: left;">151:LLIST</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">218:KEY</td>
</tr>
<tr>
<td style="text-align: left;">152:CLEAR</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">219:LPEN</td>
</tr>
<tr>
<td style="text-align: left;">153:RENUM</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">220:CHR$</td>
</tr>
<tr>
<td style="text-align: left;">154:AUTO</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">221:LEFT$</td>
</tr>
<tr>
<td style="text-align: left;">155:LOAD</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">222:RIGHT$</td>
</tr>
<tr>
<td style="text-align: left;">156:SAVE</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;">223:MID$</td>
</tr>
<tr>
<td style="text-align: left;">157:CLOAD</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">158:CSAVE</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">159:CALL</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">160:INIT</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">161:SOUND</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">162:PLAY</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">163:TX</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">164:GR</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">165:SCREEN</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">166:DISPLAY</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">167:STORE</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">168:SCROLL</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">169:PAGE</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">170:DELIM</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">171:SETE</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">172:ET</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">173:EG</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">174:CURSOR</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">175:DISK</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">176:MODEM</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
<tr>
<td style="text-align: left;">177:NEW</td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
<td style="text-align: left;"></td>
</tr>
</tbody>
</table>
<p>On peut remarquer que cette <strong>liste</strong> comporte <strong>plusieurs parties</strong>. La première, de 128 à 177, contient des <strong>instructions</strong>, c'est-à-dire des <strong>commandes impératives</strong>, sans valeur de retour.</p>
<p>De 178 à 184, il s'agit de commandes de <strong>support</strong>, qui ne peuvent être trouvées qu'en <strong>conjonction</strong> de commandes maîtresses. Par exemple <code>THEN</code> avec <code>IF</code> ; <code>TO</code> et <code>STEP</code> avec <code>FOR</code>,...</p>
<p>De 185 à 194, il s'agit d'<strong>opérateurs logiques</strong> et <strong>arithmétiques</strong>. Enfin, à partir de 195 jusqu'à la fin, 223, il s'agit de <strong>fonctions</strong>, qui retournent des <strong>valeurs</strong>, et qui apparaîtrons donc, au côté des opérateurs, dans des <strong>expressions</strong>.</p>
<p>On commence à sentir que s'il fallait <strong>ajouter</strong> des mot-clés, en fonction de leur nature, il faudrait les <strong>placer</strong> dans le <strong>bon groupe</strong>. Sauf que cette <strong>liste</strong> est <strong>compacte</strong>. On peut imaginer ajouter des fonctions après <code>MID$</code>, mais ajouter une instruction ou un opérateur <strong>décalerait</strong> toute la table, ce qui poserait un problème de compatibilité au moins avec les programmes enregistrés (les programmes <strong>BASIC</strong> enregistrés le sont sous forme tokenisée).</p>
<h4>L'évaluateur</h4>
<p>C'est donc vers l'<strong>évaluateur</strong> qu'il faut se tourner et se demander comment sont <strong>traitées</strong> les lignes en <strong>BASIC</strong>. Et c'est là que vient se planter le dernier clou dans le cercueil de l'ajout de commandes utilisateurs... du moins sans modifier la <strong>ROM</strong>.</p>
<p>Tout d'abord, examinons le décodage des tokens en <code>$250f</code>.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">80</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">inst_let</span><span class="w"> </span><span class="c1">; Comme tous les tokens sont supérieurs ou égaux</span><span class="w"></span>
<span class="w"> </span><span class="c1">; à $80, si le caractère est inférieur, c'est le début du nom d'une variable.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; C'est un LET implicite.</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">32</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">stx_err_prt</span><span class="w"> </span><span class="c1">; Les 50 ($32) premiers tokens seulement sont des instructions</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Si le token est après, alors c'est une Erreur de syntaxe.</span><span class="w"></span>
</code></pre></div>
<p><strong>Voilà</strong>... la <strong>ROM</strong> contient <strong>en dur</strong> les <strong>bornes</strong> des instructions pouvant être décodées.</p>
<p>Et ce n'est <strong>pas fini</strong>. Lorsque l'on regarde l'évaluation d'expression en <code>$28d8</code> :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">valtyp</span><span class="p">),</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Type numérique par défaut</span><span class="w"></span>
<span class="w"> </span><span class="nf">rst</span><span class="w"> </span><span class="nv">chget</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">missing_op</span><span class="w"> </span><span class="c1">; Cas où le caractère est NUL</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">str_to_num</span><span class="w"> </span><span class="c1">; Cas où le caractère est un chiffre.</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">26</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_hex_dec</span><span class="w"> </span><span class="c1">; Saut si sur le point de parser un nombre en hexa (caractère '&')</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">a_to_z_2</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">str_to_var</span><span class="w"> </span><span class="c1">; Saut si la valeur est entre A et Z</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">b9</span><span class="w"> </span><span class="c1">; Token pour '+'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">parse_value</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">2</span><span class="nv">e</span><span class="w"> </span><span class="c1">; Caractère '.'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_to_num</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">ba</span><span class="w"> </span><span class="c1">; Token pour '-'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_to_min</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="mi">22</span><span class="w"> </span><span class="c1">; Caractère '"'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_to_str</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">b7</span><span class="w"> </span><span class="c1">; Token pour 'NOT'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_to_not</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">b4</span><span class="w"> </span><span class="c1">; Token pour 'FN'</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">str_to_fn</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="kc">$</span><span class="nv">c3</span><span class="w"> </span><span class="c1">; Token pour 'SGN', la première des fonctions</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">str_to_func</span><span class="w"></span>
</code></pre></div>
<p>Afin de déterminer le type de valeur à décoder, un certain nombre de tokens est là aussi <strong>en dur</strong>.</p>
<h4>Et donc ?</h4>
<p>Et donc <strong>tel quel</strong>, la <strong>ROM</strong> ne <strong>fourni pas</strong> de <strong>mécanisme</strong> d’extension pour écrire de <strong>nouvelles instructions</strong>. D'autre part, même s'il reste une trentaine de tokens libres à la fin de la liste, les constantes dans le parseur et dans l'évaluateur exigent que les tailles des groupes du tableau soient respectés.</p>
<p>Reste la <strong>possibilité</strong> de <strong>modifier</strong> la <strong>ROM</strong> pour ajouter les nouvelles instructions, ce qui n'est pas très compliqué, mais qui nécessitera de la <strong>conversion</strong> au chargement pour être <strong>compatible</strong> avec des programmes en <strong>BASIC</strong> enregistrés sur K7.</p>VG5000µ, Schémas de principe2018-09-01T00:00:00+02:002018-09-01T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2018-09-01:/vg5000m-schemas-de-principe.html<p>Le <strong>schéma de principe</strong> scanné depuis la documentation de maintenance trouvée sur le <a href="http://vg5000.free.fr/">site My VG5000</a> et reconstitué du <strong>VG5000µ</strong> m'a souvent aidé à comprendre le fonctionnement de cette machine. Et <strong>je remercie l'auteur</strong> de ce travail !</p>
<p>Pour contribuer à mon tour à la <strong>documentation VG5000µ</strong>, j'ai refait les schémas …</p><p>Le <strong>schéma de principe</strong> scanné depuis la documentation de maintenance trouvée sur le <a href="http://vg5000.free.fr/">site My VG5000</a> et reconstitué du <strong>VG5000µ</strong> m'a souvent aidé à comprendre le fonctionnement de cette machine. Et <strong>je remercie l'auteur</strong> de ce travail !</p>
<p>Pour contribuer à mon tour à la <strong>documentation VG5000µ</strong>, j'ai refait les schémas de la <strong>platine principale</strong> et de la <strong>platine k7</strong> au propre, afin d'en augmenter la <strong>lisibilité</strong>.</p>
<p>Quelques commentaires :</p>
<ul>
<li>
<p>j'ai ajouté en précision les broches non branchées du <strong>Z80</strong> et de l'<strong>EF9345</strong>. Cela montre rapidement les choix hardware du VG5000µ qui ne seront pas contournables sans modification du matériel.</p>
</li>
<li>
<p>j'ai utilisé les nomenclatures des <strong>datasheets</strong> des composants, ce qui change un peu la <strong>nomenclature</strong> originale.</p>
</li>
<li>
<p>j'ai gardé la disposition générale du schéma, pour ne pas perdre les habitués, mais il peut y avoir quelques changements dans le détail, lorsque je pensais pouvoir améliorer la lisibilité.</p>
</li>
<li>
<p>j'ai modifié les marquages des portes logiques vers une nomenclature en toute lettre ; par contre, je n'ai aucune idée de la nomenclature officiel pour un Buffer, j'ai mis BUF en attendant.</p>
</li>
<li>
<p>une porte <strong>AND</strong>, en sortie des deux portes NAND du 7812 n'avait pas de marquage sur le schéma originel, j'ai retrouvé le composant en suivant le schéma d'implantation (ce n'était pas trop compliqué, puisque qu'il n'y a qu'un seul composant qui fourni des portes AND, mais au moins, c'est vérifié)</p>
</li>
<li>
<p>le schéma original comporte la plupart des signaux en anglais, sauf quelques-uns (genre RVB), j'ai gardé RVB sur les sorties mais changé en RGB sur la nomenclature EF9345, comme sur la <strong>datasheet</strong>.</p>
</li>
<li>
<p>De même il y a un mélange <strong>COMMUT.RAPIDE</strong> mais <strong>SOUND</strong> sur la <strong>sortie vidéo</strong>, j'ai tout <strong>unifié</strong> en français.</p>
</li>
<li>
<p>Mais du coup, j'ai des signaux en anglais sur la sortie <strong>SON/K7</strong>...</p>
</li>
</ul>
<p>Merci à tous les commentateurs du <a href="https://forum.system-cfg.com/viewtopic.php?f=7&t=9245">fil de discussion</a> sur System.cfg qui m'ont permis d'améliorer les schémas.</p>
<h4>La platine principale</h4>
<p>Image cliquable pour une version en haute définition. (mise à jour 29 avril 2021)</p>
<p><a href="https://www.triceraprog.fr/images/202104/VG5000-Schema-v1.4.png"><img alt="Platine principale" src="https://www.triceraprog.fr/images/202104/VG5000-Schema-v1.4-750.png"></a></p>
<h4>La platine K7/Son</h4>
<p>Image cliquable pour une version en haute définition. (mise à jour 9 sept. 2018)</p>
<p><a href="https://www.triceraprog.fr/images/201809/VG5000-Schema-k7-v1.2.png"><img alt="Platine K7/Son" src="https://www.triceraprog.fr/images/201809/VG5000-Schema-k7-v1.2-750.png"></a></p>
<h4>Note</h4>
<p>Le schéma a été mis à jour dans <a href="https://www.triceraprog.fr/vg5000m-schemas-de-principe-mis-a-jour-en-v15.html">un nouvel article</a>.</p>VG5000µ, SetPoint en C2018-06-25T00:00:00+02:002018-06-25T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2018-06-25:/vg5000m-setpoint-en-c.html<p>Après cette implémentation en <strong>assembleur Z80</strong> d'une fonction <code>setpoint</code> qui affiche, de manière assez basique, un point à l'écran, je me pose la question d'utiliser un langage de <strong>plus haut niveau</strong>... mais pas trop.</p>
<p>J'ai une assez longue expérience du <strong>C</strong> et la question que je me pose est : qu'est-ce …</p><p>Après cette implémentation en <strong>assembleur Z80</strong> d'une fonction <code>setpoint</code> qui affiche, de manière assez basique, un point à l'écran, je me pose la question d'utiliser un langage de <strong>plus haut niveau</strong>... mais pas trop.</p>
<p>J'ai une assez longue expérience du <strong>C</strong> et la question que je me pose est : qu'est-ce que ça donne de <strong>programmer en C</strong> pour générer du code <strong>sur Z80</strong>.</p>
<p>Programmer en C a quelques avantages a priori : c'est nettement <strong>plus concis</strong> et lisible que de l'assembleur, j'y suis <strong>plus habitué</strong> et c'est <strong>portable</strong> sur de nombreuses plateformes. C'est le cas d'autres langages, mais le choix naturel pour moi puisque écrire du C m'est habituel. En tout cas bien plus habituel que d'écrire directement de l'assembleur Z80.</p>
<h4>Premier essai</h4>
<p>Voici le code C d'un premier essai :</p>
<div class="highlight"><pre><span></span><code><span class="cp">#include</span><span class="w"> </span><span class="cpf"><stdint.h></span><span class="c1"> // Afin d'utiliser les types standards</span><span class="cp"></span>
<span class="kt">void</span><span class="w"> </span><span class="nf">setpoint</span><span class="p">(</span><span class="kt">uint16_t</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">y</span><span class="p">)</span><span class="w"> </span><span class="c1">// Définition de la fonction</span>
<span class="w"> </span><span class="c1">// En entrée, les coordonnées</span>
<span class="w"> </span><span class="c1">// Pas de valeur de retour (void)</span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">zx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w"> </span><span class="c1">// Les mêmes calculs qu'en BASIC</span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">rx</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">x</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">zy</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">/</span><span class="w"> </span><span class="mi">3</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">ry</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">y</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="p">(</span><span class="n">zy</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">3</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">ch</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">1</span><span class="w"> </span><span class="o"><<</span><span class="w"> </span><span class="p">(</span><span class="n">ry</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">rx</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint16_t</span><span class="w"> </span><span class="n">address</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mh">0x4000</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">zy</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">80</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="n">zx</span><span class="w"> </span><span class="o">*</span><span class="w"> </span><span class="mi">2</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">const</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">at</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="p">((</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">address</span><span class="w"> </span><span class="o">+</span><span class="w"> </span><span class="mi">1</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">old</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">64</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">at</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mh">0x80</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mh">0x80</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">old</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="o">*</span><span class="p">((</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">address</span><span class="p">);</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="k">if</span><span class="w"> </span><span class="p">(</span><span class="n">old</span><span class="w"> </span><span class="o">&</span><span class="w"> </span><span class="mh">0x80</span><span class="w"> </span><span class="o">==</span><span class="w"> </span><span class="mh">0x80</span><span class="p">)</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="n">old</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">64</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">new</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">ch</span><span class="w"> </span><span class="o">|</span><span class="w"> </span><span class="n">old</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="o">*</span><span class="p">((</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">address</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="n">new</span><span class="p">;</span><span class="w"></span>
<span class="w"> </span><span class="o">*</span><span class="p">((</span><span class="kt">uint8_t</span><span class="o">*</span><span class="p">)</span><span class="n">address</span><span class="o">+</span><span class="mi">1</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mi">224</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
<span class="kt">int</span><span class="w"> </span><span class="nf">main</span><span class="p">()</span><span class="w"> </span><span class="c1">// Je dois ajouter une fonction `main` pour que le fichier CRT puisse en trouver une.</span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="mi">0</span><span class="p">;</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>J'ai globalement laissé les <strong>mêmes noms</strong> de variable que j'ai utilisé dans la version <strong>BASIC</strong>, et les <strong>mêmes calculs</strong>. C'est un <strong>portage simple</strong>, sans trop se poser de questions.</p>
<h4>Le compilateur SCCZ80</h4>
<p>Afin de <strong>transformer</strong> le code C <strong>en code machine</strong>, il me faut un <strong>compilateur</strong> qui sache sortir du langage machine <strong>Z80</strong>. J'en connais deux, mais il en existe d'autres. Le <strong>premier</strong> fait parti de l'environnement complet <a href="https://www.z88dk.org/"><strong>z88dk</strong></a> et se nomme <code>SCCZ80</code>. L'<strong>autre</strong> est <a href="https://sourceforge.net/projects/sdcc/"><strong>SDCC</strong></a>, pour Small Device C Compiler. Ce dernier peut générer du code pour de nombreux microprocesseurs. Il peut être aussi utilisé par l'environnement spécialisé z80 qu'est z88dk.</p>
<p>Je commence avec <strong>SCCZ80</strong> : <code>zcc +vg5k -O3 -m c_setpoint_basic.c</code></p>
<ul>
<li><strong>+vg5k</strong> indique que à la suite que ma plateforme cible est un VG5000µ,</li>
<li><strong>-O3</strong> indique que je veux optimiser le code au niveau maximum,</li>
<li><strong>-m</strong> indique que je veux générer un fichier avec des informations sur le résultat,</li>
<li>et enfin le <strong>nom du fichier</strong> en C.</li>
</ul>
<p>Pour que la compilation fonctionne, je dois ajouter une fonction <code>main()</code>. En effet, le <strong>CRT</strong> (C Run Time) va chercher cette fonction <code>main()</code> pour l'appeler au démarrage. Or pour utiliser la fonction <code>-m</code> du compilateur, je dois construire une application complète.</p>
<p>Je pourrais tenter de compiler seulement le fichier .c en fichier objet (.o), malheureusement, comme on va le voir juste après, <strong>SCCZ80</strong> appelle de nombreuses fonctions... Bref, c'est plus simple de faire une application complète.</p>
<p>Et cela me permet d'aller voir dans le fichier <code>.map</code> la taille de la fonction <code>setpoint</code> telle que compilée pour ce compilateur.</p>
<p><strong>251 octets</strong>.</p>
<p>La fonction écrite à la main fait 196 octets, fonctions de multiplication et division comprises. Ici, c'est 251 octets <strong>sans</strong> les appels aux <strong>nombreuses fonctions</strong> donc le compilateur se sert.</p>
<h4>Le compilateur SDCC</h4>
<p>Avant d'aller plus loin, un petit essai avec <strong>SDCC</strong> donne 125 octets, auquel il faut ajouter la fonction de division, 42 octets, pour un total de <strong>167 octets</strong>. C'est beaucoup mieux. C'est même mieux que le code écrit directement en assembleur, qui ne l'oublions pas utilise une table assez importante pour la division. Le <strong>code manuel</strong> du <code>setpoint</code> hors appels de fonction <strong>reste plus petit</strong>.</p>
<p>Par contre, SDCC fait grand usage des registres <code>IX</code> et <code>IY</code>. Et <code>IX</code> est strictement <strong>réservé</strong> par la <strong>ROM</strong> du VG5000µ, il faut l'utiliser avec des grandes précautions. Le fichier CRT du VG5000µ le préserve en sauvant sa valeur, et part du principe que l'on configure l'assembleur pour échanger l'utilisation de <code>IY</code> et <code>IX</code> (pour au final ne pas utiliser <code>IX</code>).</p>
<h4>Pourquoi c'est si gros ?</h4>
<p>Je reviens en premier lieu sur la compilation en <strong>SCCZ80</strong>. Ce compilateur part sur une <strong>stratégie</strong> : la compilation d'un programme entier doit donner un <strong>exécutable petit</strong>. Pour cela, les fonctions les plus courantes sont <strong>factorisées</strong> dans des <strong>routines assembleurs</strong> utilisées par le compilateur.</p>
<p>C'est le cas par exemple pour les fonctions de <strong>division</strong> et de <strong>multiplication</strong>. Mais c'est aussi le cas pour des services comme « aller chercher un entier sur la pile », utile dans le modèle C pour le passage de paramètres aux fonctions.</p>
<p>En regardant le <strong>code généré</strong>, on peut voir aussi beaucoup de <strong>manipulations de registres</strong> pour essayer de faire les calculs sur 8 bits que je demande. Ça à l'air un peu trop complexe. Et si on essayait de partir sur une base de calculs en 16 bits ?</p>
<p>Pour cela, je change les types <code>uint8_t</code> du code source ci-dessus en <code>uint16_t</code>.</p>
<p><strong>216 octets</strong> ! Voici qui est beaucoup mieux.</p>
<p>Le compilateur <strong>SCCZ80</strong> a donc l'air de mieux travailler avec des entiers de <strong>16 bits</strong>. Cela est en partie du à l'utilisation de ces routines annexes, qui sont écrites avec un modèle sur 16 bits en tête. En demandant des calculs sur 8 bits, j'oblige le compilateur à sans cesse passer de 8 à 16 bits et inversement.</p>
<p>Et <strong>SDCC</strong> ? ... 183 octets. Là, ce n'est pas bon. De son côté, SDCC était <strong>plus efficace</strong> à faire ses opérations sur du <strong>8 bits</strong>.</p>
<p>SDCC <strong>contrairement à</strong> SCCZ80, a pour objectif principal la <strong>vitesse d'exécution</strong>, quitte à avoir du code machine plus gros. Pour cela, certaines stratégies sont employées. Ici, tous <strong>les calculs</strong> sont faits <strong>sur place</strong>, avec des manipulations de registres, à l'<strong>exception</strong> notable de la <strong>division</strong>.</p>
<p>Du coup, travailler sur les registres 16 bits est un peu plus compliqué et cela a un impact direct sur la taille du code.</p>
<h4>Est-ce qu'on peut aider ?</h4>
<p>Première idée, puisque SDCC passe beaucoup de temps à récupérer les arguments de la fonctions, c'est de repasser ceux-ci sur 8 bits.</p>
<div class="highlight"><pre><span></span><code><span class="kt">void</span><span class="w"> </span><span class="n">setpoint</span><span class="p">(</span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">x</span><span class="p">,</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">y</span><span class="p">)</span><span class="w"></span>
</code></pre></div>
<p>Avec moins d'instruction d'accès à la pile, on fait gagner <strong>10 octets</strong> à <strong>SDCC</strong>. Pas négligeable. Côté <strong>SCCZ80</strong>, c'est un gain de <strong>3 octets</strong>. C'est toujours ça.</p>
<p>On peut aussi aider en utilisant une fonction de division par 3 spécialisé, comme en assembleur, sous cette forme :</p>
<div class="highlight"><pre><span></span><code><span class="k">static</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">div3_table</span><span class="p">[]</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">15</span><span class="p">,</span><span class="mi">15</span><span class="p">,</span><span class="mi">15</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">17</span><span class="p">,</span><span class="mi">17</span><span class="p">,</span><span class="mi">17</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="mi">18</span><span class="p">,</span><span class="mi">18</span><span class="p">,</span><span class="mi">18</span><span class="p">,</span><span class="mi">19</span><span class="p">,</span><span class="mi">19</span><span class="p">,</span><span class="mi">19</span><span class="p">,</span><span class="mi">20</span><span class="p">,</span><span class="mi">20</span><span class="p">,</span><span class="mi">20</span><span class="p">,</span><span class="mi">21</span><span class="p">,</span><span class="mi">21</span><span class="p">,</span><span class="mi">21</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">23</span><span class="p">,</span><span class="mi">23</span><span class="p">,</span><span class="mi">23</span><span class="w"></span>
<span class="p">};</span><span class="w"></span>
<span class="kt">uint8_t</span><span class="w"> </span><span class="nf">div3</span><span class="p">(</span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">dividend</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">div3_table</span><span class="p">[</span><span class="n">dividend</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>La fonction de <strong>division générique</strong> utilisée pour <strong>SCCZ80</strong> fait <strong>37 octets</strong> et pour <strong>SDCC</strong>, <strong>42 octets</strong>. La fonction par tableau utilisée ici est plus grosse, mais plus rapide.</p>
<p>Côté <strong>SCCZ80</strong>, on descend à <strong>212 octets</strong>... Soit 1 octet de gain. Pas top. Côté SDCC, ça remonte même à <strong>177 octets</strong> (ou 176 en jouant sur les tailles des types entiers).</p>
<h4>Conventions d'appel</h4>
<p>Lorsque l'on <strong>appelle</strong> une <strong>fonction</strong> avec des <strong>paramètres</strong>, il y a <strong>plusieurs moyens</strong> de les <strong>transmettre</strong>. On peut passer par une <strong>convention</strong> basée sur l'<strong>utilisation de registres</strong>, ou bien passer <strong>par la pile</strong> par exemple. Par défaut, les deux compilateurs passent pas la pile. Cependant, on peut indiquer à la fonction que l'on préfère passer par une autre convention lorsqu'il n'y a qu'un paramètre à la fonction.</p>
<p>Pour cela, on modifie la fonction <code>div3</code> comme ceci :</p>
<div class="highlight"><pre><span></span><code><span class="kt">uint8_t</span><span class="w"> </span><span class="nf">div3</span><span class="p">(</span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">dividend</span><span class="p">)</span><span class="w"> </span><span class="n">__z88dk_fastcall</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">div3_table</span><span class="p">[</span><span class="n">dividend</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Côté <strong>SDCC</strong>, la fonction <code>div3</code> fond grâce au <strong>passage</strong> du <strong>paramètre</strong> et de la <strong>valeur de retour</strong> par le registre <code>l</code>. Plus besoin de mécanisme pour aller chercher la valeur dans la pile, calcul qui était généré de manière assez complexe par SDCC. La fonction passe de <strong>27 octets</strong> à... <strong>10 octets</strong> !</p>
<p>Le code généré est tout de même étrange :</p>
<div class="highlight"><pre><span></span><code><span class="nl">_div3:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="w"> </span><span class="nv">l</span><span class="w"> </span><span class="c1">; Sauvegarde de L dans C</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">_div3_table</span><span class="o">+</span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,</span><span class="nv">c</span><span class="w"> </span><span class="c1">; Récupération de C dans L... mais pourquoi ?</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,</span><span class="mh">0x00</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="nl">l_div3_00101:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>Ce petit tour de passe-passe entre les registres <code>C</code> et <code>L</code> est inutile. <code>L</code> n'est pas utilisé entre temps, et <code>C</code> n'est pas utilisé ensuite. Voilà de quoi gagner 2 octets supplémentaire en réécrivant la fonction à la main.</p>
<p>Côté appelant, <code>setpoint</code> est aussi un peu simplifié et tombe à <strong>174 octets</strong>.</p>
<p>Utilisons SCCZ80 à présent avec la même convention d'appel. La fonction initiale n'était pas très grosse, avec ses 14 octets, elle passe à 14 octets. Mais là encore les compilateur génère des choses étranges :</p>
<div class="highlight"><pre><span></span><code><span class="nf">._div3</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; HL est poussé sur la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">_div3_table</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; HL est récupéré de la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; pour y être immédiatement remis</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,</span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,</span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">bc</span><span class="w"> </span><span class="c1">; pour que la valeur soit ignorée...</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>Ces <code>push</code> et <code>pop</code> du registre <code>HL</code> sont <strong>rigoureusement inutiles</strong>. Il n'y a pas de valeur à préserver. Et au final, le contenu de la pile est extrait dans un registre dont on ne s'est pas servi <code>BC</code> mais qui du coup est invalidé. Ce sont donc <strong>4 octets à économiser</strong> (et pas mal de cycles d'exécutions) en enlevant ces instructions. Cela nous amène à <strong>10 octets</strong>.</p>
<p>La différence, si on oubli ces instructions inutiles, entre les deux codes générés est le <code>LD H,0</code> (2 octets) pour s'assurer que la valeur de retour sur 16 bits est valide avec <strong>SCCZ80</strong>. <strong>SDCC</strong> ne prend pas cette précaution, à la charge de l'appelant de n'utiliser que la valeur du registre 8 bits <code>L</code>.</p>
<h4>Et l'inlining ?</h4>
<p>Une possibilité de compilation de l'appel d'une fonction est... de ne pas l'appeler, mais plutôt de faire comme si son code était « sur place ». C'est le mot-clé anglais « inline » qui nous permet de spécifier cela.</p>
<p>Côté positif, si le code de la fonction lui-même est petit par rapport au code nécessaire au passage des paramètres et code de retour, cela peut-être gagnant d'injecter le code sur place. Côté négatif, il est possible que ce code injecté un peu partout fasse augmenter la taille du code final.</p>
<div class="highlight"><pre><span></span><code><span class="kr">inline</span><span class="w"> </span><span class="kt">uint8_t</span><span class="w"> </span><span class="nf">div3</span><span class="p">(</span><span class="kt">uint8_t</span><span class="w"> </span><span class="n">dividend</span><span class="p">)</span><span class="w"></span>
<span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="k">return</span><span class="w"> </span><span class="n">div3_table</span><span class="p">[</span><span class="n">dividend</span><span class="p">];</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Avec <strong>SCCZ80</strong>, je ne suis pas allé bien loin. L'utilisation de « inline » fait émettre des erreurs au compilateur. Je ne me suis pas penché plus sur le problème.</p>
<p>Avec <strong>SDCC</strong> par contre, non seulement le mot clé est pris en compte, mais le code est réduit de pas mal : <strong>161 octets</strong>. Et forcément, pas de code généré pour <code>div3</code>. C'est <strong>plus petit</strong> que ce que j'avais écrit à la main (ce qui peut aussi s'expliquer par mes compétences limitées en assembleur Z80).</p>
<h4>Conclusion</h4>
<p>Le <strong>C</strong> est <strong>utilisable</strong> pour générer du code <strong>Z80</strong>. Les compilateurs utilisent des versions réduites du C actuel, mais néanmoins <strong>très corrects</strong>. Le bon côté est que le <strong>temps d'écriture du code</strong>, pour certaines opérations, est <strong>réduit</strong>, en tout cas pour moi.</p>
<p>Par contre, il est nécessaire de <strong>garder le code généré à l’œil</strong>. Celui-ci peut rapidement être gourmand ou générer des choses inutiles. Il faut alors se poser la question d'adapter se code et de le <strong>passer en assembleur</strong>, en perdant au passage la portabilité.</p>
<p>Cette <strong>portabilité</strong> reste limitée, puisqu'il est nécessaire d'aider fortement le compilateur.</p>VG5000µ, SetPoint en ASM, afficher le point2018-05-29T00:00:00+02:002018-05-29T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2018-05-29:/vg5000m-setpoint-en-asm-afficher-le-point.html<p>À présent que l'on sait diviser par 3, reprenons l'affichage d'un point à l'écran. Pour rappel.</p>
<p><strong>En entrée</strong>, nous avons : des coordonnées X et Y, comprises entre <code>0</code> et <code>79</code> pour X et <code>0</code> et <code>74</code> pour Y.</p>
<p><strong>En effet de bord</strong>, c'est-à-dire en modification de l'état de la machine …</p><p>À présent que l'on sait diviser par 3, reprenons l'affichage d'un point à l'écran. Pour rappel.</p>
<p><strong>En entrée</strong>, nous avons : des coordonnées X et Y, comprises entre <code>0</code> et <code>79</code> pour X et <code>0</code> et <code>74</code> pour Y.</p>
<p><strong>En effet de bord</strong>, c'est-à-dire en modification de l'état de la machine, nous voulons : le point correspondant à l'écran qui prend la couleur d'encre définie.</p>
<p>Pour cette version, la procédure ne prendra pas d'information de couleur, je me contenterai d'utiliser la couleur d'encre <code>0</code> (noir) sur fond <code>6</code> (bleu), qui est la combinaison à l'initialisation de la machine.</p>
<p>Les <strong>étapes</strong>, d'après les articles précédents, sont donc :</p>
<ul>
<li>À partir de X et Y, trouver les coordonnées du caractère à modifier à l'écran</li>
<li>À partir de X et Y, trouver les coordonnées à l’intérieur du caractère semi-graphique</li>
<li>À partir de coordonnées du caractère, calculer l'adresse mémoire écran correspondante</li>
<li>Récupérer les valeurs pour la paire d'adresse mémoire</li>
<li>Si le caractère présent n'était pas un caractère semi-graphique standard, considérer qu'il était complètement éteint (valeur <code>0</code> pour le caractère)</li>
<li>Modifier la valeur du caractère récupéré en fonction des coordonnées à l'intérieur du caractère semi-graphique</li>
<li>Modifier la mémoire écran avec les nouvelles valeurs</li>
</ul>
<p>Comme le code est plutôt long, je vais changer de méthode. Le code entier va suivre, commenté au maximum en ligne.</p>
<h4>Le Code</h4>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">; La procédure se nomme 'setpoint' et sera appelée avec `call setpoint`</span><span class="w"></span>
<span class="w"> </span><span class="c1">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Les coordonnées (x,y) du point à allumer sont mises dans, respectivement</span><span class="w"></span>
<span class="w"> </span><span class="c1">; L et H. Autrement dit, HL contient yx.</span><span class="w"></span>
<span class="w"> </span><span class="c1">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">; C'est le format utilisé par la ROM du VG5000µ pour ses coordonnées de</span><span class="w"></span>
<span class="w"> </span><span class="c1">; curseur. Autant le garder.</span><span class="w"></span>
<span class="w"> </span><span class="c1">;</span><span class="w"></span>
<span class="nl">setpoint:</span><span class="w"></span>
<span class="w"> </span><span class="c1">; La procédure sauve tous les registres exceptés IX et IY.</span><span class="w"></span>
<span class="w"> </span><span class="c1">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">; On pourrait aussi considérer que c'est à l'appelant de veiller à</span><span class="w"></span>
<span class="w"> </span><span class="c1">; garder ses registres intègres</span><span class="w"></span>
<span class="w"> </span><span class="c1">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Ce n'est pas le plus efficace, mais pour le moment, c'est le plus sûr.</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Pour le moment :</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - HL contient les coordonnées (y,x)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - Les autres registres sont libres</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">h</span><span class="w"> </span><span class="c1">; On travaille sur y</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">div3</span><span class="w"> </span><span class="c1">; Que l'on divise par 3</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">d</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Et donc D contient y/3</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">l</span><span class="w"> </span><span class="c1">; On travaille sur x</span><span class="w"></span>
<span class="w"> </span><span class="c1">; La ligne suivante ne fonctionne que si C (le Drapeau de retenue)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; est bien à zéro (ce qui est assuré avec le div_3 utilisé)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Sinon, il faudrait utiliser `srl a`, qui est codé sur deux octets plutôt que 1</span><span class="w"></span>
<span class="w"> </span><span class="nf">rra</span><span class="w"> </span><span class="c1">; On divise A par 2</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">e</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Et donc D contient x/2</span><span class="w"></span>
<span class="w"> </span><span class="c1">; À ce point :</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - DE contient les coordonnées (y/3,x/2)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - HL contient toujours les coordonnées (y,x)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - Les autres registres sont libres</span><span class="w"></span>
<span class="w"> </span><span class="c1">; B sera utilisé temporairement</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">l</span><span class="w"> </span><span class="c1">; On travaille à nouveau sur x</span><span class="w"></span>
<span class="w"> </span><span class="nf">and</span><span class="w"> </span><span class="kc">$</span><span class="mi">01</span><span class="w"> </span><span class="c1">; On ne garde de A que le bit de poids faible.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Et donc B contient x modulo 2 (le reste de la division entière par 2)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">h</span><span class="w"> </span><span class="c1">; On travaille à nouveau sur y</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">d</span><span class="w"> </span><span class="c1">; On soustrait D, qui contient y/3 (partie entière)</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">d</span><span class="w"> </span><span class="c1">; Une seconde fois</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">d</span><span class="w"> </span><span class="c1">; Puis une troisième fois.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Et donc A contient y modulo 3</span><span class="w"></span>
<span class="w"> </span><span class="c1">; On remarque ici qu'une fonction qui retournerait en même temps le quotient</span><span class="w"></span>
<span class="w"> </span><span class="c1">; ET le reste de la division entière pourrait faire gagner un peu de temps...</span><span class="w"></span>
<span class="w"> </span><span class="c1">; On travaille dessus immédiatement sur (y modulo 3)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Dorénavant, j'utiliserai le signe % pour modulo.</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">a</span><span class="w"> </span><span class="c1">; A contient à présent (y % 3) * 2</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">b</span><span class="w"> </span><span class="c1">; A contient à présent (y % 3) * 2 + (x % 2)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Petit point :</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - A contient la puissance de 2 nécessaire à trouver le bon caractère</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - DE contient toujours les coordonnées (y/3,x/2)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - BC ne contient plus rien d'intéressant</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - HL ne contient plus rien d'intéressant</span><span class="w"></span>
<span class="w"> </span><span class="c1">; À vrai dire, HL n'est plus utile depuis le ld a,h précédent</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="w"> </span><span class="c1">; Équivalent à cp $0 mais plus conci et rapide</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Le résultat de cette comparaison de A avec 0</span><span class="w"></span>
<span class="w"> </span><span class="c1">; va être conservé par les drapeaux jusqu'au</span><span class="w"></span>
<span class="w"> </span><span class="c1">; JR suivant, car les instructions LD n'altèrent</span><span class="w"></span>
<span class="w"> </span><span class="c1">; par les drapeaux.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Il faut à présent calculer la valeur 2 à la puissance A</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; On charge B avec la valeur A. B va servir de</span><span class="w"></span>
<span class="w"> </span><span class="c1">; compteur de boucle.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="mi">1</span><span class="w"> </span><span class="c1">; On initialise le résultat à 1</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">no_power</span><span class="w"> </span><span class="c1">; Si A était égal à zéro, on n'a rien besoin</span><span class="w"></span>
<span class="w"> </span><span class="c1">; de calculer, donc on passe à la suite.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; La boucle suivante décale vers la gauche le contenu de A de 1 position</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Autrement dit, A est multiplié par 2 à chaque tour de boucle.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; À la fin de la boucle, A contient donc 2 puissance B.</span><span class="w"></span>
<span class="nl">power_of_2:</span><span class="w"></span>
<span class="w"> </span><span class="nf">rla</span><span class="w"> </span><span class="c1">;</span><span class="w"></span>
<span class="w"> </span><span class="nf">djnz</span><span class="w"> </span><span class="nv">power_of_2</span><span class="w"></span>
<span class="nl">no_power:</span><span class="w"></span>
<span class="w"> </span><span class="c1">; A contient l'index du caractère semi-graphique à aller chercher.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; On sauve cette valeur pour plus tard dans la pile.</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Petit point :</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - HL est libre</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - BC est libre</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - DE contient les coordonnées (y/3, x/2)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - A est sauvé sur la pile, on pourra donc l'utiliser pour des calculs</span><span class="w"></span>
<span class="w"> </span><span class="c1">; L'objectif est à présent d'aller calculer l'adresse mémoire du caractère</span><span class="w"></span>
<span class="w"> </span><span class="c1">; à changer dans la plage mémoire dédiée en RAM.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; La fonction de multiplication que j'utilise ici, et qui n'aura pas</span><span class="w"></span>
<span class="w"> </span><span class="c1">; son article dédié, utilise HL et DE. DE est transféré dans BC.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">d</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">e</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Et donc à présent DE est libre</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Et BC contient (y/3, x/2)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Le premier calcul à faire est (y/3)*80</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,</span><span class="mi">80</span><span class="w"> </span><span class="c1">; H contient 80</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">e</span><span class="p">,</span><span class="nv">b</span><span class="w"> </span><span class="c1">; E contient y / 3</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">mult</span><span class="w"> </span><span class="c1">; Appel de la multiplication</span><span class="w"></span>
<span class="w"> </span><span class="c1">; À présent, HL contient (y/3)*80</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Le second calcul à faire est d'arrondir X à l'entier pair</span><span class="w"></span>
<span class="w"> </span><span class="c1">; inférieur le plus proche. Pour cela, (x/2)*2, en utilisant</span><span class="w"></span>
<span class="w"> </span><span class="c1">; une division entière donne le ŕésultat.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">c</span><span class="w"> </span><span class="c1">; A contient x/2 (division entière)</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">a</span><span class="w"> </span><span class="c1">; A contient (x/2)*2 (division entière)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Petit point :</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - HL contient le déplacement mémoire sur le début de la ligne</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - A contient le déplacement en colonnes sur la ligne</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - BC est libre</span><span class="w"></span>
<span class="w"> </span><span class="c1">; - DE est libre</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Il faut donc additionner HL et A pour avoir l'index mémoire.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Le Z80 ne peut pas faire ça directement. Il faut donc charger</span><span class="w"></span>
<span class="w"> </span><span class="c1">; A dans un registre 16 bits. BC par exemple.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">bc</span><span class="w"> </span><span class="c1">; HL contient à présent (x/2)*2 + (y/3)*80</span><span class="w"></span>
<span class="w"> </span><span class="c1">; On se ressert de BC pour indiquer la base de l'adresse mémoire vidéo</span><span class="w"></span>
<span class="w"> </span><span class="c1">; auquel on ajoute l'index, pour obtenir l'adresse mémoire dans HL.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">bc</span><span class="p">,</span><span class="kc">$</span><span class="mi">4000</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Il est temps d'aller chercher les informations déjà présentes</span><span class="w"></span>
<span class="w"> </span><span class="c1">; en mémoire. Pour cela, on a besoin des deux adresses HL et HL+1</span><span class="w"></span>
<span class="w"> </span><span class="c1">; (voir les articles sur l'agencement de la mémoire vidéo)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">h</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">l</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">bc</span><span class="w"> </span><span class="c1">; BC contient HL + 1</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">bc</span><span class="p">)</span><span class="w"> </span><span class="c1">; A contient donc la valeur d'attribut du caractère</span><span class="w"></span>
<span class="w"> </span><span class="nf">bit</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Ce qui nous intéresse est sont bit numéro 7</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">set_base_char</span><span class="w"> </span><span class="c1">; S'il est à 0, ce n'est pas un caractère semi-graphique</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Sinon, on a besoin de connaître la valeur actuelle de ce</span><span class="w"></span>
<span class="w"> </span><span class="c1">; caractère</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"> </span><span class="c1">; On récupère la valeur du caractère semi-graphique</span><span class="w"></span>
<span class="w"> </span><span class="c1">; actuellement à l'écran dans A</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Si le caractère fait partie de la place 64 à 127 (les caractères pleins)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; alors on continue plus loin.</span><span class="w"></span>
<span class="w"> </span><span class="nf">bit</span><span class="w"> </span><span class="mi">7</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">char_ok</span><span class="w"></span>
<span class="nl">set_base_char:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="mi">64</span><span class="w"> </span><span class="c1">; Dans le cas où le caractère à l'écran n'était pas</span><span class="w"></span>
<span class="w"> </span><span class="c1">; semi-grapique plein, on part sur une base du</span><span class="w"></span>
<span class="w"> </span><span class="c1">; caractère 64, qui est le caractère semi-graphique</span><span class="w"></span>
<span class="w"> </span><span class="c1">; 'tout éteint'</span><span class="w"></span>
<span class="nl">char_ok:</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"> </span><span class="c1">; Recupération dans D de l'index du caractère calculé.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Cette valeur vient du 'push af' effectué plus haut.</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">d</span><span class="w"> </span><span class="c1">; Une opération bit à bit 'OU' entre l'ancienne valeur</span><span class="w"></span>
<span class="w"> </span><span class="c1">; et le nouvel index donne le nouveau caractère.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">hl</span><span class="p">),</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Ce caractère est placé à l'écran</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="mi">224</span><span class="w"> </span><span class="c1">; Et dans cette implémentation, on fixe les attributs</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">bc</span><span class="p">),</span><span class="nv">a</span><span class="w"> </span><span class="c1">; selon les valeurs de couleurs à l'allumage du VG5000µ</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Une amélioration sera d'aller chercher dans les variables</span><span class="w"></span>
<span class="w"> </span><span class="c1">; systèmes quels sont les couleurs courantes.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; La routine se termine, on restitue la valeur de tous les registres</span><span class="w"></span>
<span class="w"> </span><span class="c1">; utilisés pour revenir à l'appelant.</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">div3:</span><span class="w"> </span><span class="c1">; Entrée: registre A, Sortie: valeur divisée par 3, dans A</span><span class="w"></span>
<span class="w"> </span><span class="nf">exx</span><span class="w"> </span><span class="c1">;</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">div3_table</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"></span>
<span class="w"> </span><span class="nf">exx</span><span class="w"> </span><span class="c1">;</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">div3_table:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="w"> </span><span class="c1">; 18</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">11</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">15</span><span class="p">,</span><span class="mi">15</span><span class="p">,</span><span class="mi">15</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">17</span><span class="p">,</span><span class="mi">17</span><span class="p">,</span><span class="mi">17</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">18</span><span class="p">,</span><span class="mi">18</span><span class="p">,</span><span class="mi">18</span><span class="p">,</span><span class="mi">19</span><span class="p">,</span><span class="mi">19</span><span class="p">,</span><span class="mi">19</span><span class="p">,</span><span class="mi">20</span><span class="p">,</span><span class="mi">20</span><span class="p">,</span><span class="mi">20</span><span class="p">,</span><span class="mi">21</span><span class="p">,</span><span class="mi">21</span><span class="p">,</span><span class="mi">21</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">23</span><span class="p">,</span><span class="mi">23</span><span class="p">,</span><span class="mi">23</span><span class="w"></span>
<span class="nl">mult:</span><span class="w"> </span><span class="c1">; Entrée, registre H et registre E</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Sortie, le registre HL comtient le résultat</span><span class="w"></span>
<span class="w"> </span><span class="c1">; de l'opération H * E</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Utilise HL, B, DE</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">d</span><span class="p">,</span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,</span><span class="nv">d</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="mi">8</span><span class="w"></span>
<span class="nl">mult_loop:</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nc</span><span class="p">,</span><span class="nv">mult_skip</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">de</span><span class="w"></span>
<span class="nl">mult_skip:</span><span class="w"></span>
<span class="w"> </span><span class="nf">djnz</span><span class="w"> </span><span class="nv">mult_loop</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<h4>Comment est-ce que ça s'utilise ?</h4>
<p>Cette routine s'utilise donc en mettant dans <code>HL</code> les coordonnées (y, x) du point à afficher. Elle pourrait être améliorée en déterminant si on veut afficher ou éteindre le point, spécifier les couleurs ou les récupérer des variables systèmes. Il y a probablement quelque optimisations qui trainent.</p>
<p>Toujours est-il que par rapport à la routine est basique, l'exécution sera beaucoup plus rapide. Il serait possible d'être encore plus réactif en s'adressant directement au processeur vidéo. Mais je préférais utiliser le buffer vidéo en RAM pour plus de simplicité. Une implémetnation utilisant le processeur vidéo peut se trouver dans la bibliothèque d'affichage de <a href="https://www.z88dk.org/">Z88DK</a> pour le VG5000µ. Z88DK est tout un système, basé sur un compilateur C, pour programmer les machines Z80.</p>
<p>Pour revenir à l'utilisation de cette routine, voici un exemple qui affiche plusieurs lignes horizontales à l'écran.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="k">org</span><span class="w"> </span><span class="kc">$</span><span class="mi">7000</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Sauvegarde des registres utilisés.</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">h</span><span class="p">,</span><span class="kc">$</span><span class="mi">0</span><span class="nv">A</span><span class="w"> </span><span class="c1">; La coordonnée y est 10 ($A en hexa)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="mi">25</span><span class="w"> </span><span class="c1">; On prépare une boucle de 25 itérations</span><span class="w"></span>
<span class="nl">loop_1:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">b</span><span class="w"> </span><span class="c1">; Sauvegarde temporaire de la boucle externe</span><span class="w"></span>
<span class="w"> </span><span class="c1">; afin de préparer une boucle interne</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">l</span><span class="p">,</span><span class="kc">$</span><span class="mi">10</span><span class="w"> </span><span class="c1">; La coordonnée x est 16 ($10 en hexa)</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="mi">40</span><span class="w"> </span><span class="c1">; On prépare une boucle de 40 itérations</span><span class="w"></span>
<span class="nl">loop_2:</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">setpoint</span><span class="w"> </span><span class="c1">; On affiche un point</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">l</span><span class="w"> </span><span class="c1">; On incrémente la coordonnée x de 1</span><span class="w"></span>
<span class="w"> </span><span class="nf">djnz</span><span class="w"> </span><span class="nv">loop_2</span><span class="w"> </span><span class="c1">; Et on boucle</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Ce qui affiche une ligne de 40 pixels de large</span><span class="w"></span>
<span class="w"> </span><span class="c1">; à la coordonnée y courante.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">c</span><span class="w"> </span><span class="c1">; Récupération de l'index de boucle externe</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">h</span><span class="w"> </span><span class="c1">; On incrémente la coordonnée y deux fois</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">h</span><span class="w"> </span><span class="c1">; on "saute" donc une ligne.</span><span class="w"></span>
<span class="w"> </span><span class="nf">djnz</span><span class="w"> </span><span class="nv">loop_1</span><span class="w"> </span><span class="c1">; Et on recommence ceci 25 fois.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; On affiche donc 25 lignes les unes sous les autres</span><span class="w"></span>
<span class="w"> </span><span class="c1">; séparées à chaque fois par une hauteur d'un pixel</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Restauration des registres utilisés et retour à l'appelant</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<h4>Résultat</h4>
<p><img alt="Affichage des résultats de tests dans MAME" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201805/VG5000-LignesHorizontalesASM.png"></p>VG5000µ, SetPoint en ASM, diviser par 3 sans diviser2018-04-15T00:00:00+02:002018-04-15T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2018-04-15:/vg5000m-setpoint-en-asm-diviser-par-3-sans-diviser.html<p>Les <strong>trois</strong> derniers <strong>articles</strong> sur la <strong>division</strong> on permit de s'attarder sur trois manière de diviser un nombre entier par 3.</p>
<ul>
<li><a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-diviser.html">la première méthode</a> effectuait une <strong>série de soustractions</strong>,</li>
<li><a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-diviser-plus-vite.html">la seconde méthode</a> utilisait la <em>méthode scolaire</em>,</li>
<li><a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-diviser-encore-plus-vite.html">la troisième méthode</a> se servait de <strong>divisions par 4</strong>.</li>
</ul>
<p>La méthode de cet article …</p><p>Les <strong>trois</strong> derniers <strong>articles</strong> sur la <strong>division</strong> on permit de s'attarder sur trois manière de diviser un nombre entier par 3.</p>
<ul>
<li><a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-diviser.html">la première méthode</a> effectuait une <strong>série de soustractions</strong>,</li>
<li><a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-diviser-plus-vite.html">la seconde méthode</a> utilisait la <em>méthode scolaire</em>,</li>
<li><a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-diviser-encore-plus-vite.html">la troisième méthode</a> se servait de <strong>divisions par 4</strong>.</li>
</ul>
<p>La méthode de cet article, qui sera le dernier avant de revenir à l'affichage d'un point, va diviser grâce à, globalement, <strong>une seule addition</strong>. Oui ! Une seule addition.</p>
<h4>L'idée</h4>
<p>Au <a href="({filename}/Machines/20180225-VG5000-SetPointASM-2.md)">tout début</a> de la série d'articles sur la division, j'ai mis en place un <strong>système de tests</strong> pour m'assurer que mes bouts d'assembleurs faisaient ce qu'il étaient censés faire. Et pour cela, je comparais une <strong>série de divisions</strong> avec un <strong>tableau de résultats</strong>.</p>
<p>Mais alors, pourquoi ne pas <strong>utiliser un tableau</strong> de résultats directement ? On <strong>stock</strong> quelque part le résultat de toutes les divisions par 3 des nombres entiers de 0 à 255, et on va <strong>piocher</strong> dedans. <strong>Facile</strong> à implémenter, ultra <strong>rapide</strong>.</p>
<p>La contrepartie, évidemment, c'est que cela va prendre <strong>un peu de place</strong>. Mais voyons ce que cela donne.</p>
<h4>Le code</h4>
<p>Nul besoin d'une longue <strong>explication</strong> pour cette méthode. L'entier à diviser (le dividende) est dans le registre <code>A</code>, on <strong>l'ajoute</strong> à l'<strong>adresse du tableau</strong> des résultats pré-calculés, on récupère la valeur dans <code>A</code> et voilà !</p>
<div class="highlight"><pre><span></span><code><span class="nl">div3_5:</span><span class="w"> </span><span class="c1">; Entrée: registre A, Sortie: valeur divisée par 3, dans A</span><span class="w"></span>
<span class="w"> </span><span class="nf">exx</span><span class="w"> </span><span class="c1">; Utilisation des registres secondaires</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">div3_table</span><span class="w"> </span><span class="c1">; Chargement dans HL de l'adresse de la table des résultats</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="mi">0</span><span class="w"> </span><span class="c1">; Mise à 0 de B</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Placement de la valeur de A dans C</span><span class="w"></span>
<span class="w"> </span><span class="c1">; On a donc à présent le registre BC qui contient le dividende</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">bc</span><span class="w"> </span><span class="c1">; Addition du dividende et de l'adresse du tableau</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"> </span><span class="c1">; Récupération du résultat</span><span class="w"></span>
<span class="w"> </span><span class="nf">exx</span><span class="w"> </span><span class="c1">; Échange des registres secondaires</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="c1">; Retour à l'appelant.</span><span class="w"></span>
<span class="nl">div3_table:</span><span class="w"> </span><span class="c1">; La table des résultats</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">1</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">4</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">5</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">6</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">8</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">9</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">11</span><span class="p">,</span><span class="mi">11</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">12</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="mi">12</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">13</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">14</span><span class="p">,</span><span class="mi">15</span><span class="p">,</span><span class="mi">15</span><span class="p">,</span><span class="mi">15</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">17</span><span class="p">,</span><span class="mi">17</span><span class="p">,</span><span class="mi">17</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">18</span><span class="p">,</span><span class="mi">18</span><span class="p">,</span><span class="mi">18</span><span class="p">,</span><span class="mi">19</span><span class="p">,</span><span class="mi">19</span><span class="p">,</span><span class="mi">19</span><span class="p">,</span><span class="mi">20</span><span class="p">,</span><span class="mi">20</span><span class="p">,</span><span class="mi">20</span><span class="p">,</span><span class="mi">21</span><span class="p">,</span><span class="mi">21</span><span class="p">,</span><span class="mi">21</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">22</span><span class="p">,</span><span class="mi">23</span><span class="p">,</span><span class="mi">23</span><span class="p">,</span><span class="mi">23</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">24</span><span class="p">,</span><span class="mi">24</span><span class="p">,</span><span class="mi">24</span><span class="p">,</span><span class="mi">25</span><span class="p">,</span><span class="mi">25</span><span class="p">,</span><span class="mi">25</span><span class="p">,</span><span class="mi">26</span><span class="p">,</span><span class="mi">26</span><span class="p">,</span><span class="mi">26</span><span class="p">,</span><span class="mi">27</span><span class="p">,</span><span class="mi">27</span><span class="p">,</span><span class="mi">27</span><span class="p">,</span><span class="mi">28</span><span class="p">,</span><span class="mi">28</span><span class="p">,</span><span class="mi">28</span><span class="p">,</span><span class="mi">29</span><span class="p">,</span><span class="mi">29</span><span class="p">,</span><span class="mi">29</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">30</span><span class="p">,</span><span class="mi">30</span><span class="p">,</span><span class="mi">30</span><span class="p">,</span><span class="mi">31</span><span class="p">,</span><span class="mi">31</span><span class="p">,</span><span class="mi">31</span><span class="p">,</span><span class="mi">32</span><span class="p">,</span><span class="mi">32</span><span class="p">,</span><span class="mi">32</span><span class="p">,</span><span class="mi">33</span><span class="p">,</span><span class="mi">33</span><span class="p">,</span><span class="mi">33</span><span class="p">,</span><span class="mi">34</span><span class="p">,</span><span class="mi">34</span><span class="p">,</span><span class="mi">34</span><span class="p">,</span><span class="mi">35</span><span class="p">,</span><span class="mi">35</span><span class="p">,</span><span class="mi">35</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">36</span><span class="p">,</span><span class="mi">36</span><span class="p">,</span><span class="mi">36</span><span class="p">,</span><span class="mi">37</span><span class="p">,</span><span class="mi">37</span><span class="p">,</span><span class="mi">37</span><span class="p">,</span><span class="mi">38</span><span class="p">,</span><span class="mi">38</span><span class="p">,</span><span class="mi">38</span><span class="p">,</span><span class="mi">39</span><span class="p">,</span><span class="mi">39</span><span class="p">,</span><span class="mi">39</span><span class="p">,</span><span class="mi">40</span><span class="p">,</span><span class="mi">40</span><span class="p">,</span><span class="mi">40</span><span class="p">,</span><span class="mi">41</span><span class="p">,</span><span class="mi">41</span><span class="p">,</span><span class="mi">41</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">42</span><span class="p">,</span><span class="mi">42</span><span class="p">,</span><span class="mi">42</span><span class="p">,</span><span class="mi">43</span><span class="p">,</span><span class="mi">43</span><span class="p">,</span><span class="mi">43</span><span class="p">,</span><span class="mi">44</span><span class="p">,</span><span class="mi">44</span><span class="p">,</span><span class="mi">44</span><span class="p">,</span><span class="mi">45</span><span class="p">,</span><span class="mi">45</span><span class="p">,</span><span class="mi">45</span><span class="p">,</span><span class="mi">46</span><span class="p">,</span><span class="mi">46</span><span class="p">,</span><span class="mi">46</span><span class="p">,</span><span class="mi">47</span><span class="p">,</span><span class="mi">47</span><span class="p">,</span><span class="mi">47</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">48</span><span class="p">,</span><span class="mi">48</span><span class="p">,</span><span class="mi">48</span><span class="p">,</span><span class="mi">49</span><span class="p">,</span><span class="mi">49</span><span class="p">,</span><span class="mi">49</span><span class="p">,</span><span class="mi">50</span><span class="p">,</span><span class="mi">50</span><span class="p">,</span><span class="mi">50</span><span class="p">,</span><span class="mi">51</span><span class="p">,</span><span class="mi">51</span><span class="p">,</span><span class="mi">51</span><span class="p">,</span><span class="mi">52</span><span class="p">,</span><span class="mi">52</span><span class="p">,</span><span class="mi">52</span><span class="p">,</span><span class="mi">53</span><span class="p">,</span><span class="mi">53</span><span class="p">,</span><span class="mi">53</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">54</span><span class="p">,</span><span class="mi">54</span><span class="p">,</span><span class="mi">54</span><span class="p">,</span><span class="mi">55</span><span class="p">,</span><span class="mi">55</span><span class="p">,</span><span class="mi">55</span><span class="p">,</span><span class="mi">56</span><span class="p">,</span><span class="mi">56</span><span class="p">,</span><span class="mi">56</span><span class="p">,</span><span class="mi">57</span><span class="p">,</span><span class="mi">57</span><span class="p">,</span><span class="mi">57</span><span class="p">,</span><span class="mi">58</span><span class="p">,</span><span class="mi">58</span><span class="p">,</span><span class="mi">58</span><span class="p">,</span><span class="mi">59</span><span class="p">,</span><span class="mi">59</span><span class="p">,</span><span class="mi">59</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">60</span><span class="p">,</span><span class="mi">60</span><span class="p">,</span><span class="mi">60</span><span class="p">,</span><span class="mi">61</span><span class="p">,</span><span class="mi">61</span><span class="p">,</span><span class="mi">61</span><span class="p">,</span><span class="mi">62</span><span class="p">,</span><span class="mi">62</span><span class="p">,</span><span class="mi">62</span><span class="p">,</span><span class="mi">63</span><span class="p">,</span><span class="mi">63</span><span class="p">,</span><span class="mi">63</span><span class="p">,</span><span class="mi">64</span><span class="p">,</span><span class="mi">64</span><span class="p">,</span><span class="mi">64</span><span class="p">,</span><span class="mi">65</span><span class="p">,</span><span class="mi">65</span><span class="p">,</span><span class="mi">65</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">66</span><span class="p">,</span><span class="mi">66</span><span class="p">,</span><span class="mi">66</span><span class="p">,</span><span class="mi">67</span><span class="p">,</span><span class="mi">67</span><span class="p">,</span><span class="mi">67</span><span class="p">,</span><span class="mi">68</span><span class="p">,</span><span class="mi">68</span><span class="p">,</span><span class="mi">68</span><span class="p">,</span><span class="mi">69</span><span class="p">,</span><span class="mi">69</span><span class="p">,</span><span class="mi">69</span><span class="p">,</span><span class="mi">70</span><span class="p">,</span><span class="mi">70</span><span class="p">,</span><span class="mi">70</span><span class="p">,</span><span class="mi">71</span><span class="p">,</span><span class="mi">71</span><span class="p">,</span><span class="mi">71</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">72</span><span class="p">,</span><span class="mi">72</span><span class="p">,</span><span class="mi">72</span><span class="p">,</span><span class="mi">73</span><span class="p">,</span><span class="mi">73</span><span class="p">,</span><span class="mi">73</span><span class="p">,</span><span class="mi">74</span><span class="p">,</span><span class="mi">74</span><span class="p">,</span><span class="mi">74</span><span class="p">,</span><span class="mi">75</span><span class="p">,</span><span class="mi">75</span><span class="p">,</span><span class="mi">75</span><span class="p">,</span><span class="mi">76</span><span class="p">,</span><span class="mi">76</span><span class="p">,</span><span class="mi">76</span><span class="p">,</span><span class="mi">77</span><span class="p">,</span><span class="mi">77</span><span class="p">,</span><span class="mi">77</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">78</span><span class="p">,</span><span class="mi">78</span><span class="p">,</span><span class="mi">78</span><span class="p">,</span><span class="mi">79</span><span class="p">,</span><span class="mi">79</span><span class="p">,</span><span class="mi">79</span><span class="p">,</span><span class="mi">80</span><span class="p">,</span><span class="mi">80</span><span class="p">,</span><span class="mi">80</span><span class="p">,</span><span class="mi">81</span><span class="p">,</span><span class="mi">81</span><span class="p">,</span><span class="mi">81</span><span class="p">,</span><span class="mi">82</span><span class="p">,</span><span class="mi">82</span><span class="p">,</span><span class="mi">82</span><span class="p">,</span><span class="mi">83</span><span class="p">,</span><span class="mi">83</span><span class="p">,</span><span class="mi">83</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">84</span><span class="p">,</span><span class="mi">84</span><span class="p">,</span><span class="mi">84</span><span class="p">,</span><span class="mi">85</span><span class="p">,</span><span class="mi">85</span><span class="p">,</span><span class="mi">85</span><span class="w"></span>
</code></pre></div>
<h4>Les instructions utilisées</h4>
<p>Il n'y a <strong>pas</strong> vraiment de <strong>nouvelles instructions</strong> utilisées ici, mais de <strong>nouvelles formes</strong> :</p>
<ul>
<li>Comme on ne peut pas additionner directement les registres <code>HL</code> et <code>A</code>, on doit passer la valeur de <code>A</code> dans un registre 16 bits. <code>BC</code> est souvent utilisé, mais ça aurait pu être <code>DE</code>.</li>
<li>Comme on ne peut pas charger le contenu de <code>A</code> directement dans <code>BC</code>, on le fait en <strong>deux étapes</strong>. Le registre 16 bits <code>BC</code> est constitué des deux registres 8 bits <code>B</code> et <code>C</code>. On place donc <code>0</code> dans <code>B</code> et <code>C</code> prend la valeur de <code>A</code>.</li>
<li><code>LD A,(HL)</code> récupère la <strong>valeur pointée</strong> par le registre <code>HL</code>, plutôt que la valeur de HL. C'est-à-dire que l'octet à l'adresse mémoire pointée par <code>HL</code> est <strong>récupérée</strong>, puis chargé dans <code>A</code>.</li>
</ul>
<h4>C'est mieux du coup ?</h4>
<p>D'un point de vu code de la <strong>fonction</strong> elle-même, c'est <strong>nettement mieux</strong>, 11 octets seulement. Par contre suivi d'un tableau de 255 octets. C'est donc à la fois le code le plus concis jusqu'à maintenant, mais aussi la fonction la plus grosse dans sa globalité.</p>
<p>Il est possible cependant de la <strong>réduire</strong> dans le cas présent. En effet, comme on ne divise que des <strong>numéros de lignes</strong>, on pourrait s'arrêter à 75 résultats. Cela donne <strong>86 octets</strong>, c'est toujours plus imposant que les autres versions, mais un peu mieux.</p>
<p><strong>Attention aussi</strong>, l'accès au tableau n'est pas protégé. S´il est plus petit que 255, rien n'empêche l'appelant de passer une valeur qui va déborder. Cela donnera des résultats faux.</p>
<p>D'un point de vu <strong>rapidité</strong> c'est <strong>constant</strong> et <strong>rapide</strong> : 15 cycles. Comme c'est une fonction très courte, il est possible aussi, au besoin, de <strong>l'inliner</strong>, c'est-à-dire de faire l'opération à l'endroit où elle est nécessaire plutôt que d'appeler une fonction. Cela <strong>économise</strong> le temps de mise en place et de retour de la fonction (les <code>EXX</code> et <code>RET</code>). On tombe alors à 10 cycles, que l'on peut potentiellement améliorer en utilisant les valeurs de registres au moment de l'appel.</p>
<p>Difficile de faire plus rapide comme méthode (à quelques astuces potentielles prêt).</p>
<h4>Que choisir ?</h4>
<p><strong>C'est la question</strong>. Nous voici avec <strong>quatre méthodes</strong> pour diviser par trois. <strong>Deux</strong> sont des <strong>méthodes générales</strong> de division, <strong>deux</strong> sont <strong>spécialisées</strong> dans la division par 3. La première question à se poser est donc de savoir de quoi on a besoin.</p>
<p>Ensuite, nous avons des fonctions qui ont des <strong>compromis</strong> en terme de <strong>taille</strong> et de <strong>rapidité</strong>.</p>
<p>C'est ici qu´utiliser une <strong>fonction</strong> s'avère <strong>utile</strong>. Pour le moment, le choix n'a <strong>pas d'importance</strong>. Les quatre fonctions demandent un dividende dans <code>A</code> et retournent le résultat dans <code>A</code>. Il est donc possible de <strong>remplacer</strong> l'une par l'autre et de continuer le développement de l'affichage du point.</p>
<p>Il sera temps, ensuite, de <strong>choisir</strong> quelle méthode sera la plus <strong>adéquate</strong>.</p>
<p><strong>Spoiler</strong> : probablement aucune de celles-ci en l'état, car nous allons avoir besoin du reste de la division par 3...</p>
<p>À la prochaine !</p>VG5000µ, SetPoint en ASM, diviser encore plus vite2018-04-08T00:00:00+02:002018-04-08T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2018-04-08:/vg5000m-setpoint-en-asm-diviser-encore-plus-vite.html<p>Dans le <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-diviser-plus-vite.html">dernier article</a>, j'avais cherché une version <strong>plus rapide</strong> pour effectuer une division par 3, toujours dans l'optique d'<strong>afficher un point à l'écran</strong> en transcrivant la <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-limplementation.html">routine écrite en BASIC</a> vers de l'assembleur Z80.</p>
<p>Le résultat était <strong>mitigé</strong> : une routine en moyenne plus rapide et <strong>plus stable</strong>, mais …</p><p>Dans le <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-diviser-plus-vite.html">dernier article</a>, j'avais cherché une version <strong>plus rapide</strong> pour effectuer une division par 3, toujours dans l'optique d'<strong>afficher un point à l'écran</strong> en transcrivant la <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-limplementation.html">routine écrite en BASIC</a> vers de l'assembleur Z80.</p>
<p>Le résultat était <strong>mitigé</strong> : une routine en moyenne plus rapide et <strong>plus stable</strong>, mais sur le domaine de définition considéré (le nombre de lignes graphiques du VG5000µ), pas entièrement gagnante.</p>
<p>Dans cet article, je vais donc étudier une <strong>autre manière</strong> de faire, un peu différente. Alors que les deux premières implémentations permettait de diviser par n'importe quel nombre de 1 à 255, cette nouvelle version est spécialisée dans la division par 3.</p>
<p>Est-ce que cette spécialisation permettra de gagner en performance et/ou en taille ?</p>
<h4>L'idée</h4>
<p>Le <strong>Z80</strong> n'a pas d'instruction pour <strong>diviser</strong> de manière générale, cela a déjà été mentionné dans les articles précédents. Par contre, il est tout à fait possible de diviser par des <strong>multiples de 2</strong>. Il suffit de <strong>décaler</strong> un nombre d'un bit vers la droite pour diviser un nombre entier codé en binaire par 2.</p>
<p><strong>Exemple :</strong></p>
<p>$00010010_{2}$ (18 en base 10) <strong>décalé vers la droite une fois</strong> donne $00001001_{2}$ (9 en base 10).</p>
<p>Il faut donc trouver une manière d'exprimer une <strong>division par trois</strong> à partir de divisions par des multiples de 2 aidées d’additions ou soustractions.</p>
<p><strong>Le calcul est le suivant</strong> : on remarque tout d'abord que n'importe quel nombre entier <code>n</code> peut s'écrire sous la forme $n = 4\times a + b$, avec $b$ inférieur à 4. Autrement dit, sous forme d'une division par 4 plus le reste de la division entière. Trouver $a$ et $b$ est donc simple. $a$ est le résultat de la division entière (<strong>quotient</strong>) de $n$ par $4$. $b$ le <strong>reste</strong> de la division entière de $n$ par $4$.</p>
<p>Sous cette forme, par <strong>exemple</strong>, $15$ s'écrit $4\times3 + 3$.</p>
<p>Puisque l'on veut diviser <code>n</code> par 3, écrivons le résultat à partir de cette forme $\frac{n}{3} = \frac{4\times a + b}{3}$</p>
<p>On n'est pas très avancé, il faudrait trouver dans l'expression un terme ou une forme que l'on connaît déjà qui nous permettrait de <strong>converger</strong> vers le résultat.</p>
<p>Sortons $a$ de la fraction : $\frac{n}{3} = \frac{3\times a}{3} + \frac{a + b}{3} = a + \frac{a + b}{3}$.</p>
<p>Ça c'est intéressant. Le <strong>résultat</strong> que l'on cherche est égal à $a$, donc le résultat de la division par $4$ additionné à... un <strong>quelque chose</strong>. Et ce quelque chose est un nombre que l'on voudrait diviser par 3 !</p>
<p>Appelons ce nombre $n_{1}$, cela donne : $\frac{n}{3} = a + \frac{n_{1}}{3}$.</p>
<p>Ce terme $\frac{n_{1}}{3}$ peut <strong>lui-même</strong> être écrit sous la forme : $\frac{n_{1}}{3} = \frac{4\times a_{1} + b_{1}}{3} = a_{1} + \frac{a_{1} + b_{1}}{3}$, c'est-à-dire sous la forme d'une division par 4 de $n_{1}$ et d'un quelque chose d'autre.</p>
<p>Et <strong>ainsi de suite</strong>... Jusqu'à ce que $n_{i}$ soit inférieur ou égal à 3. Dans ce cas, il y a deux cas : soit $n_{i} = 3$ et diviser ce nombre par $3$ donne $1$. Soit $n_{i} < 3$ et ce nombre divisé par $3$ donne $0$.</p>
<h4>Exemple</h4>
<p>Revoyons l'exemple avec 15 (pour plus de cohérence, j'ajoute l'indice 0 aux premiers termes) :</p>
<p>$\frac{15}{3} = \frac{4\times a_{0} + b_{0}}{3}$. On trouve les termes de la division par 4 : $a_{0} = 3$, $b_{0} = 3$</p>
<p>Donc $\frac{15}{3} = 3 + \frac{3 + 3}{3} = 3 + n_{1}$.</p>
<p>Il s'agit donc à présent de trouver $\frac{n_{1}}{3} = \frac{3 + 3}{3} = \frac{6}{3} = \frac{4 \times a_{1} + b_{1}}{3}$.</p>
<p>Comme $6 = 4 \times 1 + 2$, on a $a_{1} = 1$ et $b_{1} = 2$.</p>
<p>Donc $\frac{n_{1}}{3} = 1 + \frac{1 + 2}{3} = 1 + \frac{3}{3}$. On est dans le cas où le nombre restant est égal à $3$, on sait calculer facilement sa division par $3$, c'est $1$.</p>
<p>Si on remonte tout ça dans l'expression initiale on a donc :</p>
<p>$\frac{15}{3} = 3 + (1 + (1)) = 5$</p>
<h4>L'implémentation</h4>
<p>Pour cette implémentation, mis à part le dividende, nous avons besoin :</p>
<ul>
<li>d'un <strong>résultat intermédiaire</strong> dans lequel on va additionner les différents termes $a_{i}$ au fur et à mesure des itérations<ul>
<li>dans l'exemple précédent, on y mettra la première fois $3$, puis on y additionnera $1$</li>
<li>j'utiliserai pour ça le registre <code>E</code>.</li>
</ul>
</li>
<li>d'un <strong>dividende intermédiaire</strong> à diviser par 3 (les termes $n_{i}$)<ul>
<li>ce nombre est le dividende donné au début</li>
<li>puis il est remplacé par la somme du quotient et du reste de la division par 4 du dividende actuel</li>
<li>j'utiliserai pour ça le registre <code>A</code>.</li>
</ul>
</li>
</ul>
<p>J'aurai aussi besoin d'autres registres pour la division intermédiaire par 4.</p>
<p>Les opérations seront :</p>
<ul>
<li><strong>Tant que</strong> le dividende actuel (<code>A</code>) est supérieur à <code>3</code> :<ul>
<li>On <strong>sauve</strong> le dividende dans <code>B</code>.</li>
<li>On <strong>divise</strong> <code>A</code> par 4 (stocké temporairement dans <code>D</code>)</li>
<li>On <strong>ajoute</strong> <code>A</code> au résultat intermédiaire <code>E</code></li>
<li>On <strong>récupère</strong> le dividende depuis <code>B</code> vers <code>A</code></li>
<li>On prend le <strong>reste de la division</strong> de <code>A</code> par 4, qui reste dans <code>A</code></li>
<li>On <strong>ajoute</strong> <code>D</code> à <code>A</code>, ce qui donne le nouveau dividende <code>A</code></li>
</ul>
</li>
<li>Si <code>A</code> est <strong>égal à 3</strong>, on ajoute 1 au résultat <code>E</code>. Sinon rien.</li>
</ul>
<p>Cette valse des registres est nécessaire car quelques opérations opérations utilisées ne sont possible, sur <strong>Z80</strong>, que sur l'accumulateur, et pas sur les autres registres.</p>
<h4>Le code</h4>
<div class="highlight"><pre><span></span><code><span class="nl">div3_4:</span><span class="w"> </span><span class="c1">; Entrée: registre A, Sortie: valeur divisée par 3, dans A</span><span class="w"></span>
<span class="w"> </span><span class="nf">exx</span><span class="w"> </span><span class="c1">; Utilisation des registres secondaires</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="mi">3</span><span class="w"> </span><span class="c1">; Initialisation du diviseur</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Il est fixe dans cette implémentation</span><span class="w"></span>
<span class="w"> </span><span class="c1">; mais est nécessaire pour la comparaison</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">e</span><span class="p">,</span><span class="mi">0</span><span class="w"> </span><span class="c1">; Initialisation du résultat</span><span class="w"></span>
<span class="nl">div3_4_loop:</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">c</span><span class="w"> </span><span class="c1">; Comparaison de `C` et `A`</span><span class="w"></span>
<span class="w"> </span><span class="c1">; le registre `A` est implicite, le Z80</span><span class="w"></span>
<span class="w"> </span><span class="c1">; ne peut comparer qu'avec le registre A</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">div3_4_exit</span><span class="w"> </span><span class="c1">; Branchement si A est inférieur à C (donc A < 3)</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">z</span><span class="p">,</span><span class="nv">div3_4_end</span><span class="w"> </span><span class="c1">; Branchement si A est égal à C (donc A = 3)</span><span class="w"></span>
<span class="nl">div3_4_cont:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; A est sauvé dans B</span><span class="w"></span>
<span class="w"> </span><span class="nf">srl</span><span class="w"> </span><span class="nv">a</span><span class="w"> </span><span class="c1">; A est décalé d'un bit vers la droite</span><span class="w"></span>
<span class="w"> </span><span class="nf">srl</span><span class="w"> </span><span class="nv">a</span><span class="w"> </span><span class="c1">; A est décalé d'un bit vers la droite</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Ces deux décalages forment une division par 4</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">d</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Le résultat de la division entière est</span><span class="w"></span>
<span class="w"> </span><span class="c1">; stockée dans D</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">e</span><span class="w"> </span><span class="c1">; A prend la valeur du résultat intermédiaire A</span><span class="w"></span>
<span class="w"> </span><span class="c1">; C'est nécessaire pour faire l'addition suivante.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Le Z80 ne sait additionner qu'avec l'accumulateur</span><span class="w"></span>
<span class="w"> </span><span class="c1">; quand on traite des valeurs sur 8 bits.</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">d</span><span class="w"> </span><span class="c1">; On ajoute le résultat de la division et</span><span class="w"></span>
<span class="w"> </span><span class="c1">; le résultat intermédiaire.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">e</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Ce résultat intermédiaire et remis dans E</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">b</span><span class="w"> </span><span class="c1">; Le dividende actuel est mis dans A afin de faire</span><span class="w"></span>
<span class="w"> </span><span class="c1">; l'opération suivante.</span><span class="w"></span>
<span class="w"> </span><span class="nf">and</span><span class="w"> </span><span class="kc">$</span><span class="mi">03</span><span class="w"> </span><span class="c1">; Calcul du reste de la division par 4</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">d</span><span class="w"> </span><span class="c1">; On ajoute à ce reste le quotient de la division</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Et donc A contient le nouveau dividende.</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">div3_4_loop</span><span class="w"> </span><span class="c1">; C'est donc reparti pour un tour.</span><span class="w"></span>
<span class="nl">div3_4_end:</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">e</span><span class="w"> </span><span class="c1">; On arrive ici si le dernier dividende est 3.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; dans ce cas, on ajoute 1 au résultat final.</span><span class="w"></span>
<span class="nl">div3_4_exit:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">e</span><span class="w"> </span><span class="c1">; Le résultat final est stocké dans A</span><span class="w"></span>
<span class="w"> </span><span class="nf">exx</span><span class="w"> </span><span class="c1">; Échange des registres secondaires</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="c1">; Retour à l'appelant.</span><span class="w"></span>
</code></pre></div>
<h4>Les instructions utilisées</h4>
<p>Les instructions déjà utilisées dans les articles précédents ne sont pas répétées ici, les nouvelles sont :</p>
<ul>
<li><code>srl</code> décale le registre indiqué de 1 bit vers la droite. Le bit sortant est stocké dans le drapeau de retenu, mais ici, on ne l'utilise pas.</li>
<li><code>and</code> effectue une opération bit à bit entre l'accumulateur et l'opérande. Le résultat de chaque bit est 1 uniquement si les bits de chaque terme étaient 1 sur la même position.<ul>
<li>Exemple : <code>11111000</code> and <code>00011111</code> donne <code>00011000</code>, car seuls le 2 bits centraux sont à 1 dans les deux termes.</li>
<li>Ici, on s'en sert pour prendre le reste de la division par 4. Pourquoi est-ce que ça donne ce résultat est laissé en exercice.</li>
</ul>
</li>
</ul>
<h4>Bon alors, c'est mieux ?</h4>
<p>Un <strong>petit calcul</strong> donne pour cette troisième version :</p>
<ul>
<li>29 octets. Encore <strong>plus grand</strong>.</li>
<li>Le nombre d'étapes maximum à faire donne 84 cycles. C'est un peu mieux. Dans le domaine de définition de l'affichage d'un point, le nombre à diviser le plus grand est 75, et cela donne alors 56 cycles.</li>
</ul>
<p>Ce qui fait que la première version n'est plus rapide que pour les nombres jusqu'à 5. Cette version est <strong>intéressante</strong> et offre un <strong>bon compromis</strong> entre taille du code et vitesse d'exécution. Au <strong>détriment</strong> de la <strong>flexibilité</strong>, puisque le calcul n'est valable que pour une division par 3.</p>
<p>Le principe de l'algorithme pourrait être étendu à plus de nombres, mais au prix d'un taille plus grande et d'un fonctionnement un petit peu plus long. De toute façon ici, on ne s’intéresse qu'à une division par 3.</p>
<p>La fois <strong>prochaine</strong>, on verra une <strong>dernière manière</strong> de faire, et puis on reviendra à l'étape suivante de l'affichage d'un point.</p>VG5000µ, SetPoint en ASM, diviser plus vite2018-03-29T00:00:00+02:002018-03-29T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2018-03-29:/vg5000m-setpoint-en-asm-diviser-plus-vite.html<p>Dans le <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-diviser.html">dernier article</a>, j'avais écrit une <strong>première manière</strong> d'effectuer <strong>une division</strong>, en spécialisant la routine pour une division <strong>par 3</strong> puisque c'est ce qui m'intéresse pour <strong>afficher un point</strong> à l'écran du VG5000µ.</p>
<p>Pour référence, cette routine nécessitait 15 octets en mémoire et son exécution pouvait prendre jusqu'à 695 …</p><p>Dans le <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-diviser.html">dernier article</a>, j'avais écrit une <strong>première manière</strong> d'effectuer <strong>une division</strong>, en spécialisant la routine pour une division <strong>par 3</strong> puisque c'est ce qui m'intéresse pour <strong>afficher un point</strong> à l'écran du VG5000µ.</p>
<p>Pour référence, cette routine nécessitait 15 octets en mémoire et son exécution pouvait prendre jusqu'à 695 cycles, ce qui est plutôt grand.</p>
<h4>Micro optimisation</h4>
<p>La première optimisation est une micro optimisation. On appelle micro optimisation une <strong>amélioration</strong> de la routine par un <strong>détail</strong> de fonctionnement, plutôt que par une réflexion sur l'ensemble de la méthode.</p>
<p>Ici, l'idée est de remplacer le couple <code>push bc</code> / <code>pop bc</code> en début et fin de la routine par un couple <code>exx</code> / <code>exx</code>. L'instruction <code>exx</code> du Z80 effectue un <strong>renommage</strong> des <strong>registres</strong> <code>BC</code>, <code>DE</code> et <code>HL</code>. Ces registres (ainsi que d'autres, mais qui ne sont pas touchés par cette instruction) existent en <strong>deux exemplaires</strong> dans le Z80. Un exemplaire de chaque registre est utilisé pendant que l'autre ne l'est pas. Il est possible <strong>d'échanger les rôles</strong> de ces paires.</p>
<p>On peut imaginer un <strong>aiguillage</strong> devant les registres. Le flux de données est aiguillé soit vers l'un des registres de la paire, soit vers l'autre. L'instruction <code>exx</code> s'occupe de modifier l'aiguillage des registres <code>BC</code>, <code>DE</code> et <code>HL</code>. Les registres inactifs sont nommés <code>BC'</code>, <code>DE'</code> et <code>HL'</code>.</p>
<p>Puisqu'en entrée de la routine, je n'utilise que le registre <code>A</code>, mais que j'ai besoin des autres registres pour les calculs, je peux <strong>préserver les valeurs</strong> que les registres ont en entrée de fonction en utilisant l'autre série de registres.</p>
<p>Les instructions <code>push bc</code> et <code>pop bc</code> prennent chacun 3 cycles à s'exécuter. <code>exx</code> n'en prend qu'un. On passe donc de 6 cycles à 2, pour un <strong>gain de 4</strong>.</p>
<p>Ce n'est pas grand chose par rapport au temps d'exécution de la routine elle-même, c'est donc une micro optimisation. Mais elle est simple à faire. La routine passe de 15 octets à 13.</p>
<p><strong>Une autre</strong> micro optimisation consiste à éviter un test en initialisant <code>B</code> à <code>-1</code> plutôt que <code>0</code>. Mais comme ce n'est pas sur cette routine avec des micros optimisations que l'on va vraiment gagner, je laisse de côté.</p>
<h4>Division comme à l'école</h4>
<p>La <strong>première méthode</strong> de division, celle de l'article précédent, se faisait par <strong>soustractions successives</strong>. Tant que l'on pouvait soustraire le diviseur du dividende, on le faisait, tout en comptant le nombre de fois que cette soustraction était faite. Ce <strong>nombre de soustractions</strong> est le <strong>résultat</strong> de la division.</p>
<p>Ce n'est pas comme cela que vous avez appris à diviser à l'école. Si on a appris la même méthode, à quelque chose comme ce qui suit.</p>
<p><strong>Pour diviser</strong> (en base 10), 424 par 3, on pose :</p>
<div class="highlight"><pre><span></span><code> 421 | 3
|---
|
|
</code></pre></div>
<p>Puis on regarde si l'on peut diviser le premier 4 du dividende par 3. Oui, cela donne 1. On pose donc 1 dans le résultat et le résultat de la soustraction sous le 4 :</p>
<div class="highlight"><pre><span></span><code> 421 | 3
-3 |---
- | 1
1 |
</code></pre></div>
<p>On passe au chiffre suivant, que l'on descend au niveau du 1. Cela donne 12, que l'on essaye de diviser par 3. Ok, cela donne 4, que l'on note dans le résultat. Le reste de la soustraction est noté sur la gauche du trait vertical :</p>
<div class="highlight"><pre><span></span><code> 421 | 3
-3 |---
- | 14
12 |
-12 |
--
0
</code></pre></div>
<p>On passe au chiffre suivant, que l'on descend au niveau du 0. Cela donne 1. On essaye de diviser par 3. Cela donne 0, car 1 est plus petit que 3. On note donc 0 à la suite du résultat, on soustrait sur la gauche (on soustrait 0 du coup) :</p>
<div class="highlight"><pre><span></span><code> 421 | 3
-3 |---
- | 140
12 |
-12 |
--
01
- 0
--
1
</code></pre></div>
<p>Il n'y a plus de chiffre à traiter au dividende. Le résultat de la division est dans 140, et le reste 1.</p>
<p>Faire une division avec <strong>cette méthode</strong>, c'est dérouler <strong>un algorithme</strong>. Et on peut transposer ça à l'ordinateur. On ne peut pas utiliser de division en base 10 puisque c'est exactement ce qu'on essaie ici d'implémenter. Mais sous forme binaire, le processeur a les <strong>instructions nécessaires</strong>.</p>
<p><strong>Faisons un essai</strong>. Divisons $101_{2}$ par $10_{2}$ (en binaire, indiqué par le petit 2, donc 5 par 2 en décimal) selon la même méthode.</p>
<div class="highlight"><pre><span></span><code> 101 | 10
|----
|
</code></pre></div>
<p>On commence donc par prendre $1_{2}$ et essayer de diviser par $10_{2}$. Mais... il n'y a pas d'instruction de division sur le Z80 ! Qu'à cela ne tienne, nous sommes en binaire, et les <strong>deux seuls résultats possibles</strong> en binaire <strong>sont $0_{2}$ et $1_{2}$</strong>.</p>
<p>Autrement dit, <strong>soit le nombre</strong> que l'on considère est <strong>plus petit</strong> que le <strong>diviseur</strong> et le résultat est $0_{2}$, ce qui implique que l'on <strong>ne soustrait rien du tout</strong> au dividende. Soit le résultat est $1_{2}$, ce qui implique que l'on <strong>soustrait le diviseur</strong> au dividende.</p>
<p>Puis on adjoint à ce résultat le chiffre binaire suivant.</p>
<p>Autrement dit, nous n'avons besoin que de <strong>décalages</strong> et de <strong>soustractions</strong>.</p>
<div class="highlight"><pre><span></span><code> 101 | 10
-0 |----
- | 0
10
</code></pre></div>
<p>On considère donc $10_{2}$ qui n'est pas plus petit que le diviseur (il est même égal). Le résultat à retenir est donc 1 et l'on soustrait le diviseur :</p>
<div class="highlight"><pre><span></span><code> 101 | 10
-0 |----
- | 01
10
-10
--
001
</code></pre></div>
<p>On considère $1_{2}$, qui est plus petit que le diviseur. On ne soustrait donc rien, on note 0 et... <strong>c'est terminé</strong>.</p>
<div class="highlight"><pre><span></span><code> 101 | 10
-0 |----
- | 010
10
-10
--
001
-0
-
1
</code></pre></div>
<p>Le résultat de $101_{2}$ divisé par $10_{2}$ est donc $10_{2}$, reste $1_{2}$. Soit en base 10 : 5 divisé par 2 est égal à 2 reste 1.</p>
<h4>Algorithme</h4>
<p>Pour implémenter cet algorithme, j'ai besoin de plusieurs choses:</p>
<ul>
<li>De quoi retenir le nombre à considérer en cours,</li>
<li>De quoi retenir ce qu'il reste du dividende à considérer,</li>
<li>Le diviseur,</li>
<li>De quoi retenir le résultat en cours.</li>
</ul>
<p>Pour se <strong>simplifier la vie</strong>, on va considérer les <strong>8 bits du dividende</strong>, même s'ils commencent par des <code>0</code>. Lorsque l'on pose une division manuellement, on sait que <strong>des zéros en début</strong> de nombre donnent des zéros en début de <strong>résultat</strong>. Aucun de ces zéros ne sont significatifs.</p>
<p>Pour notre calcul, il est plus simple de dérouler l'algorithme sur <strong>l'intégralité</strong> du nombre en binaire.</p>
<p>Cela donne donc cela:</p>
<ul>
<li>Initialiser un registre comme compteur de bits à 8 (on a 8 bits à traiter)</li>
<li>Initialiser un registre contenant le diviseur</li>
<li>Initialiser un registre contenant le dividende (il est initialement dans <code>A</code>, mais ce registre, l'accumulateur, va être nécessaire pour certaines opérations)</li>
<li>Initialiser un registre à 0 pour retenir le reste</li>
</ul>
<p>Ensuite, tant que le compteur de bits est positif, on déroule ce qui suit:</p>
<ul>
<li>On <strong>décale</strong> les bits du <strong>résultat</strong> de 1 vers la gauche en insérant un 0 à droite<ul>
<li>par exemple, si le résultat est actuellement <code>101</code>, il devient <code>1010</code></li>
</ul>
</li>
<li>On <strong>décale</strong> les bits du <strong>dividende</strong> de 1 vers la gauche aussi, en insérant un 0<ul>
<li>même chose</li>
</ul>
</li>
<li>On <strong>décale</strong> les bits de l'accumulateur de 1 vers la gauche, mais en <strong>insérant</strong> cette fois le bit qui est <em>sorti</em> du dividende par la gauche lors de l'opération précédente<ul>
<li>par exemple, si le dividende était <code>11000000</code>, il a été décalé en <code>10000000</code> et le <code>1</code> est <em>sorti</em></li>
<li>si l'accumulateur contenait <code>00000010</code>, il est décalé en <code>00000101</code>.</li>
</ul>
</li>
<li>On <strong>compare</strong> le contenu de l'accumulateur avec le diviseur.</li>
<li>Si l'accumulateur est <strong>égal ou plus grand</strong> que le diviseur:<ul>
<li>on soustrait le diviseur de l'accumulateur</li>
<li>on ajoute <code>1</code> au résultat courant</li>
</ul>
</li>
<li>Sinon, rien... vu qu'on a décalé le résultat, il contient un <code>0</code> en dernière position.</li>
<li>On <strong>décrémente</strong> le compteur de bits.</li>
</ul>
<h4>Registres</h4>
<p>Les registres utilisés sont les suivants :</p>
<ul>
<li><code>B</code> est utilisé pour le <strong>compteur de bits</strong>.<ul>
<li>C'est un choix naturel sur le <strong>Z80</strong> qui permet d'utiliser l'instruction <code>DJNZ</code> qui décrémente <code>B</code> et fait un saut conditionnel si <code>B</code> n'est pas égal à <code>0</code>.</li>
</ul>
</li>
<li><code>C</code> contient le <strong>diviseur</strong>.</li>
<li><code>D</code> contient le <strong>dividende</strong> (il faut donc y transférer <code>A</code>).</li>
<li><code>E</code> contient le <strong>résultat</strong> (qu'il faut donc initialiser à <code>0</code>).</li>
</ul>
<h4>Le code</h4>
<div class="highlight"><pre><span></span><code><span class="nl">div3_3:</span><span class="w"> </span><span class="c1">; Entrée: registre A, Sortie: valeur divisée par 3, dans A</span><span class="w"></span>
<span class="w"> </span><span class="nf">exx</span><span class="w"> </span><span class="c1">; Utilisation des registres secondaires</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="mi">8</span><span class="w"> </span><span class="c1">; Initialisation du compteur de bits à 8</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="mi">3</span><span class="w"> </span><span class="c1">; Initialisation du diviseur</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">d</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Transfert du dividende dans D</span><span class="w"></span>
<span class="w"> </span><span class="nf">xor</span><span class="w"> </span><span class="nv">a</span><span class="w"> </span><span class="c1">; Mise à 0 de l'accumulateur</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">e</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Mise à 0 du résultat</span><span class="w"></span>
<span class="nl">div3_3_loop:</span><span class="w"></span>
<span class="w"> </span><span class="nf">sla</span><span class="w"> </span><span class="nv">e</span><span class="w"> </span><span class="c1">; Décalage de E vers la gauche</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Le drapeau `Carry` contient le bit fort de E (qui est 0)</span><span class="w"></span>
<span class="w"> </span><span class="nf">sla</span><span class="w"> </span><span class="nv">d</span><span class="w"> </span><span class="c1">; Décalage de D vers la gauche</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Le drapeau `Carry` contient le bit fort de D</span><span class="w"></span>
<span class="w"> </span><span class="nf">rla</span><span class="w"> </span><span class="c1">; Décalage de A vers la gauche, avec récupération de la</span><span class="w"></span>
<span class="w"> </span><span class="c1">; valeur de `Carry`</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">c</span><span class="w"> </span><span class="c1">; Comparaison de C et A</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">div3_3_skip</span><span class="w"> </span><span class="c1">; Si C est plus grand que A, saut plus loin</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">c</span><span class="w"> </span><span class="c1">; Sinon, A est réduit de la valeur de C</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">e</span><span class="w"> </span><span class="c1">; Et E est augmenté de 1</span><span class="w"></span>
<span class="nl">div3_3_skip:</span><span class="w"></span>
<span class="w"> </span><span class="nf">djnz</span><span class="w"> </span><span class="nv">div3_3_loop</span><span class="w"> </span><span class="c1">; B est décrémenté et s'il n'est pas égal à 0,</span><span class="w"></span>
<span class="w"> </span><span class="c1">; on repart en début de boucle</span><span class="w"></span>
<span class="nl">div3_3_end:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">e</span><span class="w"> </span><span class="c1">; Le résultat est transféré dans A</span><span class="w"></span>
<span class="w"> </span><span class="nf">exx</span><span class="w"> </span><span class="c1">; Échange des registres secondaires</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="c1">; Retour à l'appelant.</span><span class="w"></span>
</code></pre></div>
<h4>Les instructions utilisées</h4>
<p>Les instructions déjà utilisées dans les articles précédents ne sont pas répétées ici, les nouvelles sont :</p>
<ul>
<li><code>exx</code> a déjà été expliqué en début d'article sur la micro optimisation.</li>
<li><code>xor</code> effectue une opération de <strong>ou exclusif</strong> pour chaque bit de même position entre l'accumulateur et le registre mentionné.<ul>
<li>Dans un <em>ou exclusif</em>, le résultat est <code>1</code> uniquement si l'on des deux bits est à <code>1</code>.</li>
<li>C'est <strong>impossible ici</strong> puisque l'opération est effectuée entre l'accumulateur et... l'accumulateur.</li>
<li>Le résultat est de <strong>mettre l'accumulateur</strong> à <code>0</code>.</li>
<li>C'est un <strong>idiome classique</strong>, car un <code>LD A,0</code> prend deux octets en mémoire et deux cycles, alors que <code>XOR A</code> ne prend qu'un seul octet et 1 cycle.</li>
</ul>
</li>
<li><code>xor a</code> suivi de <code>ld ?,a</code> entre registre. C'est aussi un <strong>schéma classique</strong>.<ul>
<li>Transférer entre deux registres prend un octet et un cycle, on profite donc d'avoir initialisé l'accumulateur pour initialiser un autre registre à la même valeur de manière efficace.</li>
</ul>
</li>
<li><code>djnz</code>, comme indiqué au-dessus, effectue plusieurs opérations. Tout d'abord, <code>B</code> est décrémenté. Puis si <code>B</code> est égal à zéro, l'instruction se termine. Sinon, un branchement à lieu à l'adresse indiquée en paramètre.</li>
</ul>
<h4>Et c'est mieux ?</h4>
<p>Un <strong>petit calcul</strong> donne pour cette nouvelle version :</p>
<ul>
<li>23 octets... c'est <strong>moins bien</strong> que 12 octets avec les deux micro optimisations, certes.</li>
<li>Entre <strong>103</strong> et <em>111</em> cycles en fonction du nombre de <code>1</code> dans le résultat.</li>
</ul>
<p>Niveau vitesse, c'est beaucoup moins qu'avant la méthode précédente ! La routine est peut-être deux fois plus grosse, mais elle est beaucoup <strong>plus stable</strong> en temps d'exécution et <strong>en moyenne plus rapide</strong>.</p>
<p>Oui mais <strong>attention</strong> ! Si elle est plus rapide dans le cas général, dans le cadre de l'affichage d'un point, le nombre maximal à diviser est 75. Et pour 75, la vitesse de la première version est de 215. En fait, pour tout dividende inférieur à 36, la première version de la division par 3 est plus rapide.</p>
<p>Note de fin d'article : on peut micro-optimiser le code ci-dessous en se passant de <code>E</code> et en stockant le résultat dans <code>D</code> au fur et à mesure de son décalage (qui libère de l'espace au fur et à mesure). Cela évite deux <code>LD</code> et un <code>SLA</code>, ce qui rend la fonction compétitive plutôt vers 34.</p>Visite au Computer History Museum2018-03-28T00:00:00+02:002018-03-28T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2018-03-28:/visite-au-computer-history-museum.html<p>La semaine dernière, profitant d'un voyage à <strong>San Francisco</strong>, j'ai allongé un peu mon séjour pour pousser jusqu'à <strong>Mountain View</strong> et aller visiter un musée qui semblait fort intéressant. Le <strong>Musée de l'Histoire des Ordinateurs</strong>, ou dans le texte « <a href="http://www.computerhistory.org/">Computer History Museum</a> »</p>
<p><img alt="Dépliant du musée" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_001_Depliant.jpg"></p>
<p>Première chose à savoir si vous êtes à …</p><p>La semaine dernière, profitant d'un voyage à <strong>San Francisco</strong>, j'ai allongé un peu mon séjour pour pousser jusqu'à <strong>Mountain View</strong> et aller visiter un musée qui semblait fort intéressant. Le <strong>Musée de l'Histoire des Ordinateurs</strong>, ou dans le texte « <a href="http://www.computerhistory.org/">Computer History Museum</a> »</p>
<p><img alt="Dépliant du musée" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_001_Depliant.jpg"></p>
<p>Première chose à savoir si vous êtes à San Francisco même, que vous choisissiez le <strong>train</strong> (pas cher, mais long, avec changement) ou la <strong>voiture</strong> (plus cher, un peu moins long), <strong>prévoyez le trajet</strong> pour ne pas arriver trop tard. En effet, le musée <strong>ferme assez tôt</strong> : 17h.</p>
<p>En voiture, tant qu'à visiter, vous pourrez faire un détour par la <strong>280</strong> pour passer par des endroits sympas ou la <strong>1</strong> pour des endroits encore plus sympa, à l'aller ou au retour. Ou opter par la <strong>101</strong> pour aller au plus direct (mais potentiellement aussi plus encombrée, vérifiez avant de partir).</p>
<p>De notre côté (puisque nous étions deux), nous avons opté par un peu de balade sur le chemin.</p>
<p><img alt="La plage" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_002_Plage.jpg"></p>
<p>Arrivé au musée et ayant acquitté notre droit d'entrée, après une petite explication de l'agencement à l'accueil et la remise d'un plan, nous voici parti pour une <strong>aventure</strong> d'environ <strong>quatre heures</strong>.</p>
<p>Coup de bol ? Ce jour là, des étudiants du coin faisaient une <strong>démonstration</strong> à l'entrée d'<strong>appareils fonctionnels</strong> et d'artefacts divers (comme un reste de Silicon après découpage de Wafers). Les visiteurs pouvaient donc goûter au plaisir de perforation d'une carte (c'est un plaisir quand c'est dans un musée...).</p>
<p><img alt="Performation de carte" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_003_Punch.jpg"></p>
<p>Puis la visite commence. Un petit <strong>film d'introduction</strong> peut être visionné, mais je ne l'ai pas fait, j'avais hâte de voir du concret.</p>
<p>L'exposition principale est divisée en <strong>20 zones thématiques</strong> et globalement chronologique. Globalement car les époques recouvertes par les thèmes se superposent dans l'histoire des ordinateurs. La <strong>première zone</strong> est consacrée au <strong>calcul</strong>, le besoin initial à la base de tout ça. Règles à calcul, bouliers (que l'on peut manipuler), métier de « calculateur » permettant de dresser les tables de fonctions.</p>
<p><img alt="Règle à calcul" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_004_SlideRule.jpg"></p>
<p>Mais encore <strong>calculatrices</strong>, mécaniques tout d'abord puis électroniques (mais non programmables). Et que serait cet endroit sans la réplique de la sous-partie de démonstration de la <strong>Machine à Différences</strong> de <strong>Charles Babbage</strong> ? Près de là, les plans de la <strong>Machine Analytique</strong> du même inventeur, bien entendu avec mention des travaux d'<strong>Ada Lovelace</strong>.</p>
<p><img alt="Machine à Différences" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_005_DifferenceMachine.jpg"></p>
<p>Suit un très intéressant reportage sur la culture d'entreprise d'<strong>IBM</strong> au début du XXième siècle. Le musée est comme cela ponctué de <strong>nombreux reportages</strong> ou extraits de films, interviews et reportages d'époque. Il y en a vraiment beaucoup et malgré les quatre heures de visite, je n'ai pas tout pu regarder.</p>
<p>Ce passage est dans la zone 2, consacrée aux <strong>cartes perforées</strong>, dont IBM fut un grand fournisseur.</p>
<p><img alt="IBM, think" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_006_IBM.jpg"></p>
<p>De là, on passe en zone 3, consacrée aux <strong>calculateurs analogiques</strong> et des « batailles » qui s'ensuivent entre les modèles numériques et les modèles analogiques, ainsi que des fusions entre les deux systèmes.</p>
<p>Une machine analogique câblée nous montre bien la similitude avec l'état de certaines bases de code actuelles...</p>
<p><img alt="Calculateur Analogique" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_007_AnalogComputer.jpg"></p>
<p>Peut-être préférez-vous le câblage suivant.</p>
<p><img alt="Autre calculateur Analogique" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_008_AnalogComputer_2.jpg"></p>
<p>Les zones suivantes sont consacrées à la <strong>naissance des ordinateurs</strong> ainsi qu'à l'histoire des <strong>premières compagnies</strong> à se lancer dans le secteur. On y trouve quelques ordinateurs à lampes de dimensions conséquentes, comme des morceaux d'<strong>ENIAC</strong>.</p>
<p>Le musée présente aussi des <strong>blocs de bases</strong> d'un ordinateur dans leurs formes d'alors, et rappellent que des pistes initiales se tournaient vers la <strong>mécanique</strong>. Sur l'image suivante, une porte logique mécanique à gauche (a priori un inverseur), et une additionneur/soustracteur à lampe à droite.</p>
<p><img alt="Portes" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_009_Gates.jpg"></p>
<p>La zone suivante, la 6, nous amène aux besoins de calculs en temps réel et nous rappelle les besoins de l'<strong>armée</strong>, toujours très gourmande en calculs pour la <strong>balistique</strong>. Et très <strong>avide d'information</strong> à des fins de surveillance dans une époque de <strong>guerre froide</strong>. Des éléments du système <a href="https://fr.wikipedia.org/wiki/Semi-Automatic_Ground_Environment">SAGE</a> sont exposés.</p>
<p><img alt="Système SAGE" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_010_SAGE.jpg"></p>
<p>Tout cela demande de la <strong>puissance</strong> de calcul <strong>grandissante</strong>, et nous entrons à présent dans la zone 7, dédiée aux <strong>Mainframes</strong>. L'<strong>IBM 360</strong> y trône en bonne place, avec ses beaux dérouleurs de bande rouge et bleus.</p>
<p><img alt="IBM 360" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_011_IBM360.jpg"></p>
<p>Et avec des possibilités de calculs grandissante vient les <strong>besoins en stockage</strong> grandissants eux aussi. La zone 8 s'intéresse à <strong>la mémoire</strong>, qu'elle soit persistante ou non. En partant de quelques artefacts très lointains, comme un système de stockage d'information <strong>Inca</strong> utilisant <strong>des cordes</strong>, on arrive sur des systèmes plus récents. Un mur de stockage présente de nombreux systèmes : lecteurs de disquettes, cartouches, cassettes, disques magnétiques...</p>
<p>Tout ça, c'est bien beau, mais sans logiciel, cela ne va pas bien loin. C'est la zone 9 qui présente <strong>la programmation</strong>. Le musée est principalement focalisé sur le matériel, et si le logiciel est toujours dans l'air, ce n'est pas sont point central (voir plus loin pour la section déportée du musée). À un mur, une <strong>fresque</strong> généalogique des différents <strong>langages</strong> de programmation s’étale sur quelques mètres. C'est une version cependant <strong>très simplifiée</strong>, même si les jalons essentiels sont là.</p>
<p>Une film explicatif constitue la principale attraction de la zone. Il faut dire que ce n'est pas si évident de montrer du logiciel dans un contexte de vieilles machines dont la plupart ne fonctionnent pas et qui étaient utilisées principalement pour des calculs scientifiques, militaires et des traitements administratifs.</p>
<p>Ah si, tout de même, un badge <a href="https://fr.wikipedia.org/wiki/DECUS"><strong>DECUS</strong></a> rappelle que le principe du <strong>partage de logiciel</strong> via des sources de code ouvertes ne date pas d'hier, puisque le groupe a été créé en 1961 et que la pratique le précédait.</p>
<p><img alt="DECUS" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_012_DECUS.jpg"></p>
<p>Si les Mainframes, c'était du trop petit pour vous, la zone 10 en remet une couche avec une présentation de <strong>Super Ordinateurs</strong>. Forcément, vu la place que cela prend, il n'y a pas beaucoup de pièces. On y trouve aussi un cluster de PCs. Bien entendu, que serait une section Super Ordinateurs sans <strong>Cray</strong>.</p>
<p>Un film rapide retrace au passage la vie de <strong>Seymour Cray</strong> et des particularités de ses ordinateurs.</p>
<p><img alt="Cray" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_013_CRAY.jpg"></p>
<p>Ils ont plein de petites LEDs qui clignotent et des petits leviers tous sympas, ils sont tout minis se sont les... <strong>Minis Ordinateurs</strong> de la zone 11. Des <strong>PDP-8</strong>, <strong>PDP-11</strong>, du <strong>HP</strong>, du <strong>CDC</strong>, un modèle de PDP-8 qui a servi pour des opérations chirurgicales du cerveau et a permis d'effectuer celles-ci avec endormissement du patient (car donc, apparemment, auparavant, le patient devait rester réveillé...)</p>
<p><img alt="PDP-8" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_014_PDP8.jpg"></p>
<p>Bien évidemment, la section évoque <strong>UNIX</strong> et présente un manuel d'époque.</p>
<p>Puis l'on passe en zone 12, consacré à la <strong>logique numérique</strong> au cœur du fonctionnement de toutes ces machines jusqu'à nos tablettes et téléphones actuels. Quelques <strong>portes logiques</strong> peuvent être actionnées avec des interrupteurs (dommage, le Flip Flop ne fonctionnait pas à cause d'un bouton défectueux). Un film présente les <strong>étapes</strong> de <strong>fabrication</strong> d'un <strong>circuit intégré</strong>, et l'on peut regarder à la loupe plusieurs générations de ces puces. Quelques <strong>wafers</strong> de différentes tailles accompagnent le tout.</p>
<p>La zone 13 est consacrée à la <strong>robotique</strong> et à l'<strong>intelligence artificielle</strong>, avec de nombreux robots vintage en exposition. Mes photos de cette section étant toutes floues, passons.</p>
<p>En zone 14, on découvre la thématique de la <strong>création artistique</strong> à travers l'outil informatique. Ici, à côté d'un cube de <strong>PIXAR</strong>, de système <strong>SUN</strong>, de <strong>tables traçantes</strong> et de <strong>synthétiseurs</strong>, on trouve une machine au logo qui aura marqué une époque en <strong>image numérique</strong>.</p>
<p><img alt="Silicon Graphic" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_015_SGI.jpg"></p>
<p>J'y ai découvert aussi un accessoire pour <strong>Commodore 64</strong> que je ne connaissais pas du tout. Le <strong>Incredible Music Keyboard</strong>, qui se place au dessus de la coque d'un C64 pour le transformer en synthétiseur. J'aurais du mieux regarder la chaîne <a href="https://www.youtube.com/watch?v=RX4JfLCaBMU">8-Bit Keys</a>, qui en fait une présentation.</p>
<p><img alt="Incredible Music Keyboard" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_016_IncredibleMusicKeyboard.jpg"></p>
<p>La zone 15 s'intéresse au <strong>périphériques d'entrées et sorties</strong>. Le coin est plutôt fourni en périphériques <strong>exotiques</strong>, dont beaucoup étaient des idées... intéressantes. Et dont d'autres ont eu plus de succès. Et quoi de mieux pour accueillir le visiteur dans cette zone que le très à propos <strong>Xerox Alto</strong>.</p>
<p><img alt="Xerox Alto" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_017_XeroxAlto.jpg"></p>
<p>La zone 16 est consacrée aux <strong>jeux vidéo</strong>. Pas de trucs renversants à ce niveau-là, beaucoup de musées de vieilleries informatiques se focalisant essentiellement sur le matériel de jeu, il est <strong>difficile de faire original</strong>. Trois pods de démonstrations permettent de jouer à des jeux assez différents : un jeu d'aventure textuel, Spacewar! et PAC-MAN. Les trois en « reproduction » (plutôt des recréations que de l'émulation j'ai l'impression... mais je n'en sais rien).</p>
<p>Bref, passons ici aussi.</p>
<p>La zone 17 présente l'<strong>ordinateur personnel</strong>. Le Micro, ça y est, l'ère <strong>grand public</strong> (ou presque) commence. C'est un <strong>IBM PC</strong> qui nous reçoit dans la zone. Vu la taille de ces nouvelles machines, de <strong>nombreuses</strong> pièces différentes sont <strong>présentées</strong>. Apple I et II, TRS-80, Commodore PET, Atari 800XL, Lisa,...</p>
<p><img alt="IBM PC" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_018_IBM-PC.jpg"></p>
<p>Mais aussi de l'<strong>exotisme</strong> (vu des Etats-Unis), avec un Smaky, un Amstrad 464, du Spectrum,... Un clone russe de ZX Spectrum.</p>
<p>Et un <strong>Thomson TO7-70</strong> ! Malheureusement un peu dans l'ombre.</p>
<p><img alt="Thomson TO7-70" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_019_TO7-70.jpg"></p>
<p>Et puisqu'on est dans l'ordinateur français, un petit <strong>Micral</strong>.</p>
<p><img alt="Micral" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_020_Micral.jpg"></p>
<p>Toute une partie est à l'honneur du <strong>Do It Yourself</strong> de l'époque : ces machines que l'on pouvait recevoir en <strong>kit</strong>, ou bien dont on ne recevait que des parties essentielles, à compléter ensuite, en suivant éventuellement un <strong>plan</strong> dans un <strong>magazine</strong>.</p>
<p>Ici, un <strong>Altaïr 8800</strong> côtoie (mais de pas trop près) un <strong>Imsai 8080</strong>. Mais aussi un <strong>EDUC-8</strong> Australien (que je ne connaissais pas), une carte <strong>KIM-1</strong> et de nombreux autres.</p>
<p><img alt="Altair 8800" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_021_Altair.jpg"></p>
<p>De la zone 17 on passe à la zone... 18 ! Celle-ci présente l'<strong>informatique mobile</strong>. En partant des portatifs initiaux (et de publicités où l'on voit des utilisateurs suer avec le <strong>sourire</strong> en traînant une mallette de plomb signe de leur <strong>modernité</strong>), on passe par les ordinateurs portables de différentes époques et les organiseurs personnels.</p>
<p>Le pôle <strong>interactif</strong> offre de soulever un <strong>Osborne 1</strong> dans sa malette, pour juger du temps pendant lequel nous aurions pu <strong>garder le sourire</strong> en se baladant avec. On est loin des quelques centaines de grammes de tablettes actuelles.</p>
<p>La zone 19 présente la <strong>mise en réseau</strong>, en commençant par le commencement : la mise en relation de personnes à distance. <strong>Télégraphe</strong>, puis <strong>téléphone</strong>, avec des cartes de l'évolution des <strong>lignes</strong> reliant les <strong>continents</strong>.</p>
<p>Un petit bouton permet de retrouver le délicieux <strong>son</strong> d'une communication entre ordinateurs via <strong>Modem</strong>.</p>
<p><img alt="Modem" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_022_Modem.jpg"></p>
<p>Est exposée aussi une des premières armoire à <strong>serveurs de Google</strong>, tout un tas de <strong>modems</strong>, de <strong>routeurs</strong> et autres matériels de communication. Des initiatives aussi, comme les <strong>ordinateurs communautaires</strong> hippies.</p>
<p>Côtés ordinateurs associés à cette thématique, un <strong>NeXT Cube</strong>, mais aussi un <strong>Minitel</strong>, expliquant que bien avant l'essor grand public d'Internet, en <strong>France</strong>, il était possible d'aller <strong>chercher des renseignements</strong> en ligne ou de <strong>réserver un train</strong>.</p>
<p><img alt="Minitel" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_023_Minitel.jpg"></p>
<p>La dernière zone, la vingtième est une zone <strong>tournée vers l'avenir</strong>. Principalement un <strong>film</strong> interviewant quelques personnalités de la Silicon Valley. J'avoue ne pas l'avoir regardé. on sort avec cette zone de l'Histoire pour entrer dans l'actuel, et l'actuel est tous les jours.</p>
<p>C'est ainsi que se termine la visite de l'exposition principale. Mais pas du musée.</p>
<h4>PDP-1 et Spacewars!</h4>
<p>Si la quasi totalité des machines exposées ne sont pas en fonctionnement, le musée possède <strong>deux salles</strong> consacrées à deux <strong>machines emblématiques</strong> restaurées et <strong>en état de fonctionnement</strong>.</p>
<p>La première salle est celle du <strong>PDP-1</strong>.</p>
<p>La vénérable machine n'est cependant <strong>allumée</strong> que <strong>deux fois par mois</strong> à un horaire bien précis... Et le jour de ma visite n'était pas un jour de démonstration. C'est dommage, mais tant pis.</p>
<p>La démonstration consiste, entre autre, à lancer le jeu <strong>Spacewars!</strong> Il est même possible d'y jouer, avec des contrôles déportés dans la zone visiteur. Le <strong>stylo optique</strong> est aussi fonctionnel.</p>
<p><img alt="PDP-1" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_024_PDP-1.jpg"></p>
<h4>IBM 1401</h4>
<p>La <strong>seconde salle</strong> est encore <strong>plus vaste</strong> et est utilisée pour une installation d'<strong>IBM 1401</strong>. Un film explique l'histoire de sa <strong>restauration</strong>, qui aura été longue et fastidieuse.</p>
<p>Là encore, il faut tomber le <strong>bon jour</strong> et la <strong>bonne heure</strong> pour la <strong>démonstration</strong>. C'était le bon jour, mais nous sommes arrivés après la mise en route, qui a lieu le matin.</p>
<p>Mais même éteinte, la machine est <strong>impressionnante</strong>.</p>
<p><img alt="IBM 1401" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/CHM_025_1401.jpg"></p>
<h4>Le reste</h4>
<p>Et ce n'est pas fini. Mais le musée allait fermer 30 minutes après et j'ai parcouru le reste un peu plus rapidement. J'ai dédié la majeure partie du temps restant à l'exposition sur <strong>Ada Lovelace</strong>, qui présente quelques manuscrits de sa main et lettres de Charles Babbage à son attention. Qui retrace aussi en quelques panneaux sa vie.</p>
<p>À côté, une mini expo sur les <strong>transports automatisés</strong>, qui me semble est surtout une excuse pour présenter la voiture toute ronde de chez Google.</p>
<p>Le dernier grand espace est dédié au <strong>logiciel</strong> sous différents aspects : jeu, musique, image, connaissance, simulation, industrie textile. Chaque poste montre <strong>l'évolution du travail</strong> dans ces domaines avec l'arrivé de l'informatique. Par exemple en juxtaposant le développement de pellicule en chambre noir et Photoshop.</p>
<p>Exposition plutôt bien faite, mais que j'ai traversée un peu en coup de vent. Je n'étais pas venu pour cela.</p>
<h4>Conclusion</h4>
<p>Ce musée est <strong>fantastique</strong>. Les quatre heures passées, nullement suffisante pour tout voir dans les détails, sont passées très rapidement. Beaucoup de pièces intéressantes, des explications, des films d'archive, des reportages,... </p>
<p>Si vous passez dans le coin, <strong>n'hésitez-pas</strong> !</p>VG5000µ, SetPoint en ASM, diviser2018-03-06T00:00:00+01:002018-03-06T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2018-03-06:/vg5000m-setpoint-en-asm-diviser.html<p>À présent que j'ai un <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-verifier-les-resultats.html">garde fou</a> pour vérifier que je ne fais pas d'erreur d'inattention, me voilà près à diviser des nombres. Pour rappel j'ai besoin de diviser des nombres afin de faire les calculs permettant d'affiche le bon pixel à l'écran.</p>
<p>Pour second rappel, le <strong>Z80</strong>, au cœur …</p><p>À présent que j'ai un <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-verifier-les-resultats.html">garde fou</a> pour vérifier que je ne fais pas d'erreur d'inattention, me voilà près à diviser des nombres. Pour rappel j'ai besoin de diviser des nombres afin de faire les calculs permettant d'affiche le bon pixel à l'écran.</p>
<p>Pour second rappel, le <strong>Z80</strong>, au cœur du <strong>VG5000µ</strong> (et de beaucoup d'autres ordinateurs de l'époque) <strong>n'a pas</strong> d'instruction de <strong>division</strong>.</p>
<h4>La division</h4>
<p>Lorsque je <strong>divise</strong> de manière <strong>entière</strong> <code>a</code> par <code>b</code>, je veux <strong>trouver</strong> le nombre <code>c</code> tel quel $c * b = a$. Comme la division ne tombe pas toujours <em>juste</em>, j'ai aussi <strong>un reste</strong> <code>r</code> tel que $c * b + r = a$.</p>
<p>Autrement dit, combien de fois dois-je additionner <code>b</code> pour obtenir <code>a</code> (au reste près). Une <strong>manière</strong> de trouver le <strong>résultat</strong> est de <strong>soustraire</strong> <code>b</code> à <code>a</code> autant de fois que l'on peut sans passer sous <code>0</code>.</p>
<p>Par exemple, $\frac{21}{7}$ se trouve comme ceci :</p>
<div class="highlight"><pre><span></span><code><span class="mf">21</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">7</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">14</span><span class="w"></span>
<span class="mf">14</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">7</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">7</span><span class="w"></span>
<span class="w"> </span><span class="mf">7</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">7</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">0</span><span class="w"></span>
</code></pre></div>
<p>Il y a trois <strong>soustractions</strong>, et donc $\frac{21}{7} = 3$</p>
<p>Un autre exemple avec $\frac{17}{3}$ :</p>
<div class="highlight"><pre><span></span><code><span class="mf">17</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">14</span><span class="w"></span>
<span class="mf">14</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">11</span><span class="w"></span>
<span class="mf">11</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">8</span><span class="w"></span>
<span class="w"> </span><span class="mf">8</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">5</span><span class="w"></span>
<span class="w"> </span><span class="mf">5</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="mf">3</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="mf">2</span><span class="w"></span>
</code></pre></div>
<p>Il y a cinq <strong>soustractions</strong>, et il reste <code>2</code> à la fin, dont la soustraction de <code>5</code> donnerait <code>-3</code>. On s'arrête donc et $\frac{17}{3} = 5 + \frac{2}{3}$</p>
<p>Un <strong>problème majeur</strong> de cette façon de faire est que plus le <strong>résultat est grand</strong>, plus il a fallu de <strong>calculs</strong> pour le trouver. Si on divise de grands chiffres par de petits, cet <strong>algorithme est lent</strong>.</p>
<p>Il est par contre <strong>très facile</strong> à <strong>programmer</strong>. La séquence donne ceci :</p>
<ul>
<li>Mettre le dividende (le nombre à diviser) dans <code>A</code></li>
<li>Mettre le diviseur dans <code>C</code></li>
<li>Mettre <code>0</code> dans <code>B</code></li>
<li>Tant que <code>A</code> est supérieur à <code>C</code><ul>
<li>Soustraire <code>C</code> à <code>A</code> et mettre le résultat dans <code>A</code></li>
<li>Ajouter <code>1</code> à <code>B</code></li>
</ul>
</li>
<li>Le quotient (le résultat de la division entière) est dans <code>B</code></li>
<li>Le reste de la division entière est dans <code>A</code></li>
</ul>
<h4>Implémentation par soustractions</h4>
<p>Il y a des <strong>limitations</strong> bien entendu à faire cette division sur des registres 8 bits. Je ne pourrai pas traiter des nombres supérieurs à <code>255</code>. Pour afficher un point, je n'en ai pas besoin, c'est donc très bien.</p>
<p>Tout d'abord, je renseigne mon <strong>système de tests</strong> avec mes nouvelles <strong>informations</strong> :</p>
<div class="highlight"><pre><span></span><code><span class="nl">div3_input_data:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="mi">2</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">21</span><span class="p">,</span><span class="mi">255</span><span class="w"></span>
<span class="nl">div3_reference_data:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="mi">0</span><span class="p">,</span><span class="mi">3</span><span class="p">,</span><span class="mi">7</span><span class="p">,</span><span class="mi">85</span><span class="w"></span>
</code></pre></div>
<div class="highlight"><pre><span></span><code><span class="nl">div3_params:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="nv">div3_input_data</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="nv">div3_reference_data</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="nv">div3</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">"DIV3\0"</span><span class="w"></span>
</code></pre></div>
<p>Et j'<strong>ajoute</strong> le <strong>test</strong> à la liste :</p>
<div class="highlight"><pre><span></span><code><span class="nl">test_suite:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">id_params</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">prepare_test</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">div2_params</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">prepare_test</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">div3_params</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">prepare_test</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
</code></pre></div>
<p>Ce qui me permet de <strong>mettre au point</strong> le code et le <strong>vérifier</strong> :</p>
<div class="highlight"><pre><span></span><code><span class="nl">div3:</span><span class="w"> </span><span class="c1">; Entrée: registre A, Sortie: valeur divisée par 3, dans A</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">bc</span><span class="w"> </span><span class="c1">; J'utilise les registres B et C, je les préserve</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Le temps de l'opération</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="mi">3</span><span class="w"> </span><span class="c1">; Le registre C contient le diviseur</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="mi">0</span><span class="w"> </span><span class="c1">; Le compteur B est préparé pour retenir</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Le nombre de soustractions effectuées</span><span class="w"></span>
<span class="nl">div3_loop:</span><span class="w"></span>
<span class="w"> </span><span class="nf">cp</span><span class="w"> </span><span class="nv">c</span><span class="w"> </span><span class="c1">; A et C sont comparés</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">div3_end</span><span class="w"> </span><span class="c1">; Si A était plus petit que C, alors une retenue a eu lieu</span><span class="w"></span>
<span class="w"> </span><span class="c1">; le calcul est terminé</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Attention, ici C signifie Carry (retenue)</span><span class="w"></span>
<span class="w"> </span><span class="nf">sub</span><span class="w"> </span><span class="nv">c</span><span class="w"> </span><span class="c1">; C est soustrait de A</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">b</span><span class="w"> </span><span class="c1">; B est augmenté de 1</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">div3_loop</span><span class="w"> </span><span class="c1">; La boucle est relancée</span><span class="w"></span>
<span class="nl">div3_end:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">b</span><span class="w"> </span><span class="c1">; Le résultat de la division est dans B, il est placé dans A</span><span class="w"></span>
<span class="w"> </span><span class="c1">; À noter que A contenait le reste de la division.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Cela pourra être intéressant pour plus tard.</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">bc</span><span class="w"> </span><span class="c1">; Les registres B et C sont restaurés</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="c1">; Et la fonction est terminée</span><span class="w"></span>
</code></pre></div>
<h4>Les instructions utilisées</h4>
<p>Les instructions déjà utilisées dans les articles précédents ne sont pas répétées ici, les nouvelles sont :</p>
<ul>
<li><code>cp</code>: permet de <strong>comparer</strong> le registre <code>A</code> (qui est implicite et donc non noté) et le registre indiqué (ici, <code>C</code>). La comparaison se fait par soustraction de <code>A</code> et du registre, mais le résultat n'est pas retenu, seuls les drapeaux de résultat de l'opération le sont. Ainsi, en cas d'égalité par exemple, le drapeau <code>Z</code> sera à 1 (Zéro, car la soustraction de deux nombres égaux donne zéro). Ici, on cherche s'il y a eu une retenue, ce qui indique que le nombre dans le registre spécifié était plus grand que le nombre dans <code>A</code>, et donc le résultat de la soustraction était négatif.</li>
<li><code>sub</code>: effectue une soustraction simple. On avait précédemment vu <code>sbc</code>, qui faisait une soustraction en tenant compte de la retenue du calcul précédent. <code>sub</code> n'en tient pas compte et soustrait simplement le registre mentionné du registre <code>A</code>, qui est implicite.</li>
</ul>
<h4>Résultat</h4>
<p>Voici donc une <strong>première implémentation</strong> de <strong>division par 3</strong>, qui peut être <strong>généralisée</strong> à une division par <strong>n'importe quel nombre</strong> (inférieur à 255)</p>
<p>Pour référence futur, le <strong>code</strong> de la fonction nécessite <strong>15 octets</strong> de langage machine. <strong>L'exécution</strong> de la fonction nécessite $15 + 8 * q$ cycles processeur, où $q$ est le quotient, résultat de la division. Au pire, pour $\frac{255}{3}$, la fonction prend donc <strong>695</strong> cycles. Ce qui n'est pas négligeable.</p>
<p>Nous verrons plus tard si l'on peut faire mieux.</p>
<p><img alt="Affichage des résultats de tests dans MAME avec la division par 3" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201803/VG5000-TestFramework-2.png"></p>VG5000µ, SetPoint en ASM, vérifier les résultats2018-02-21T00:00:00+01:002018-02-21T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2018-02-21:/vg5000m-setpoint-en-asm-verifier-les-resultats.html<p>Après avoir <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-verifier-la-pile.html">mis en place</a> une vérification (légère) de l'<strong>intégrité de la pile</strong>, je passe à la <strong>vérification</strong> de la <strong>validité</strong> de l'appel d'une <strong>fonction</strong>.</p>
<p>Le fonctionnement du test est assez simple : je prends une <strong>suite de nombres</strong>, j'appelle une fonction avec en paramètre chacun de ces nombres, je …</p><p>Après avoir <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-asm-verifier-la-pile.html">mis en place</a> une vérification (légère) de l'<strong>intégrité de la pile</strong>, je passe à la <strong>vérification</strong> de la <strong>validité</strong> de l'appel d'une <strong>fonction</strong>.</p>
<p>Le fonctionnement du test est assez simple : je prends une <strong>suite de nombres</strong>, j'appelle une fonction avec en paramètre chacun de ces nombres, je vérifie que le <strong>résultat</strong> est <strong>conforme</strong> à ce que j'attendais.</p>
<p>Par exemple, si je veux tester une fonction <strong>diviser par 2</strong> (division entière), je peux utiliser la suite de nombre <code>0, 10, 32, 255</code> et comparer les résultats respectifs avec <code>0, 5, 16, 127</code> (255 étant impair, le résultat de la division entière est 127, avec un reste égal à 1).</p>
<p>Encore plus simple qu'une division par 2, il y a la <strong>fonction identité</strong> : celle qui renvoie le paramètre sans le toucher. Tester cette fonction permet de se concentrer sur le développement du test.</p>
<p>La fonction en elle-même est <strong>très simple</strong> :</p>
<div class="highlight"><pre><span></span><code><span class="nl">identity:</span><span class="w"> </span><span class="c1">; Entrée: registre A, Sortie : registre A, inchangé</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="c1">; Retour immédiat, on ne touche à rien</span><span class="w"></span>
</code></pre></div>
<h4>La boucle de test</h4>
<p>La boucle de test initialise deux <strong>pointeurs</strong> de données qui vont être augmentés en parallèle. La donnée source sera envoyée à la fonction, via le registre <code>A</code>, puis le résultat, mis dans le registre <code>A</code> aussi, sera <strong>comparé</strong> à la <strong>valeur attendue</strong>.</p>
<p>Il nous faut donc en premier lieu la liste de ces valeurs :</p>
<div class="highlight"><pre><span></span><code><span class="nl">identity_input_data:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">32</span><span class="p">,</span><span class="mi">255</span><span class="w"></span>
<span class="nl">identity_reference_data:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">32</span><span class="p">,</span><span class="mi">255</span><span class="w"></span>
</code></pre></div>
<p>La boucle en elle-même ressemble à cela :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">; Fonction de test</span><span class="w"></span>
<span class="nl">test:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">identity_reference_data</span><span class="w"> </span><span class="c1">; HL pointe sur les résultats de références</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">de</span><span class="p">,</span><span class="nv">identity_input_data</span><span class="w"> </span><span class="c1">; DE pointe sur les données en entrées</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Effacement du drapeau de retenue (voir article précédent)</span><span class="w"></span>
<span class="w"> </span><span class="nf">sbc</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">de</span><span class="w"> </span><span class="c1">; Par soustraction des deux valeurs, on obtient le nombre de valeurs</span><span class="w"></span>
<span class="w"> </span><span class="c1">; de la série.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">h</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">l</span><span class="w"> </span><span class="c1">; BC contient le nombre de valeurs à tester</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">identity_reference_data</span><span class="w"> </span><span class="c1">; HL est pointe à nouveau sur le résultat de référence</span><span class="w"></span>
<span class="nl">test_loop:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">de</span><span class="p">)</span><span class="w"> </span><span class="c1">; Chargement dans l'accumulateur de la valeur pointée par DE</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">identity</span><span class="w"> </span><span class="c1">; Appel de la fonction</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Au retour de la fonction, A contient le résultat de la fonction</span><span class="w"></span>
<span class="w"> </span><span class="nf">cpi</span><span class="w"> </span><span class="c1">; Compare A avec (HL), incrémente HL et décrémente BC</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Si BC passe à 0, le bit d'overflow (V) est mis à 0 ; 1 sinon</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Si A et (HL) sont identique, le flag Zero est mis à 1</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nz</span><span class="p">,</span><span class="nv">test_failed</span><span class="w"> </span><span class="c1">; Si A et (HL) étaient différent, saute à test_failed</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">de</span><span class="w"> </span><span class="c1">; Sinon, incrémente DE manuellement</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">v</span><span class="p">,</span><span class="nv">test_loop</span><span class="w"> </span><span class="c1">; S'il reste des valeurs (BC > 0), on boucle</span><span class="w"></span>
<span class="w"> </span><span class="c1">; DE et HL pointant à présent sur la paire de valeurs suivantes</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">test_pass_msg</span><span class="w"> </span><span class="c1">; Arrivée ici, toutes les paires de valeurs ont été</span><span class="w"></span>
<span class="w"> </span><span class="c1">; vérifiée avec succès. HL pointe donc sur le message</span><span class="w"></span>
<span class="w"> </span><span class="c1">; de succès.</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">print_test_result_msg</span><span class="w"> </span><span class="c1">; Et on saute à l'affichage.</span><span class="w"></span>
<span class="nl">test_failed:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">test_fail_msg</span><span class="w"> </span><span class="c1">; Arrivée ici, une comparaison a échouée, HL pointe</span><span class="w"></span>
<span class="w"> </span><span class="c1">; donc sur le message d'échec.</span><span class="w"></span>
<span class="nl">print_test_result_msg:</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">print_str</span><span class="w"> </span><span class="c1">; On affiche le message contenu dans HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"> </span><span class="c1">; Le test est fini !</span><span class="w"></span>
<span class="nl">test_pass_msg:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">"Pass!\r\0"</span><span class="w"></span>
<span class="nl">test_fail_msg:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">"Fail!\r\0"</span><span class="w"></span>
</code></pre></div>
<h4>Les instructions utilisées</h4>
<p>Les instructions déjà utilisées dans l'article précédent ne sont pas répétées ici, les nouvelles sont :</p>
<ul>
<li><code>defb</code> : qui est une <strong>directive</strong> pour l'assembleur, indiquant de réserver de la place mémoire et de l'initialiser avec les octets qui suivent,</li>
<li><code>cpi</code> : instruction de <strong>comparaison</strong> qui effectue plusieurs actions d'un coup, comme indiqué dans le commentaire ci-dessus. Cela contraint l'utilisation des registres <code>HL</code>, <code>BC</code> et <code>A</code>, qui sont spécialisés ainsi (<code>HL</code> pour un pointeur de mémoire, <code>BC</code> comme compteur et <code>A</code> pour l'accumulateur).</li>
<li><code>inc</code> : <strong>incrémente</strong> la valeur du registre en paramètre, c'est-à-dire lui ajoute <code>1</code>.</li>
<li><code>jp</code> : <strong>saut</strong> (<strong>jump</strong>) au label indiqué. La différence avec l'utilisation de <code>jr</code> est dans l'encodage de l'adresse de destination. Sans entrer dans le détail, <code>jr</code> est plus condensé que <code>jp</code>, car il n'encode pas l'adresse complète mais seulement un déplacement <strong>court</strong>. Cependant, il n'est pas possible d'utiliser le drapeau de dépassement de capacité (<code>V</code>) n'est pas utilisable avec <code>jr</code>.</li>
</ul>
<h4>Et la division par 2 ?</h4>
<p>À présent, il devient facile de <strong>tester</strong> différentes fonctions. Il suffit de la <strong>fonction</strong> elle-même, de la paire de liste de valeurs, et de remplacer l'appel de le fonction dans le test.</p>
<div class="highlight"><pre><span></span><code><span class="nl">div2:</span><span class="w"> </span><span class="c1">; Entrée: registre A, Sortie : valeur divisée par 2, dans A</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="w"> </span><span class="c1">; Effacement du drapeau de retenue</span><span class="w"></span>
<span class="w"> </span><span class="nf">rra</span><span class="w"> </span><span class="c1">; Rotation du registre A vers la droite, en passant par la retenue</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">div2_input_data:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="mi">10</span><span class="p">,</span><span class="mi">32</span><span class="p">,</span><span class="mi">255</span><span class="w"></span>
<span class="nl">div2_reference_data:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defb</span><span class="w"> </span><span class="mi">0</span><span class="p">,</span><span class="mi">5</span><span class="p">,</span><span class="mi">16</span><span class="p">,</span><span class="mi">127</span><span class="w"></span>
</code></pre></div>
<p>Et par exemple, si vous aviez, par étourderie comme moi, utilisé <code>rrca</code> plutôt que <code>rra</code>, le test échoue sur la division par 255.</p>
<h4>Généralisation</h4>
<p>Mais <strong>changer</strong> les <strong>pointeurs</strong> à <strong>chaque test</strong> de fonction, ça n'est <strong>pas pratique</strong>. C'est la grande différence entre des tests automatisés, qui peuvent rester à demeure et que l'on peut lancer régulièrement pour s'assurer que l'on construit un programme sur des fondations solides, et le test manuel, de temps en temps, pour s'assurer du fonctionnement en un point donné, et que l'on doit remettre en place manuellement à chaque fois.</p>
<p>Bref, il me faut <strong>généraliser</strong> ça avec, par exemple, la boucle de test qui prendrait en entrée les <strong>pointeurs nécessaires</strong>. Et pourquoi pas, même, un nom explicatif de la fonction testée sur le moment.</p>
<p>Ce que je voudrais, c'est quelque chose comme ceci :</p>
<div class="highlight"><pre><span></span><code><span class="nl">test_suite:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">id_params</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">prepare_test</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">div2_params</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">prepare_test</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">id_params:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="nv">identity_input_data</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="nv">identity_reference_data</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">"IDENTITY\0"</span><span class="w"></span>
<span class="nl">div2_params:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="nv">div2_input_data</span><span class="w"></span>
<span class="w"> </span><span class="nf">defw</span><span class="w"> </span><span class="nv">div2_reference_data</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">"DIV2\0"</span><span class="w"></span>
</code></pre></div>
<p>Il faut pour cela adapter un peut la routine <code>test</code> pour aller piocher les valeurs depuis <code>HL</code>, qui devient le paramètre d'entrée.</p>
<p>Tout d'abord, la préparation des paramètres du test va mettre sur la pile les paramètres indiqués.</p>
<p><em>Note</em> : il y a de multiples choix pour passer les paramètres des tests à la fonction. Mais aussi beaucoup de contraintes sur les instructions disponibles. Passer par la pile grâce à une fonction d'aide est assez simple à implémenter et lisible. Mais loin d'être le plus rapide.</p>
<div class="highlight"><pre><span></span><code><span class="nl">prepare_test:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="mi">3</span><span class="w"> </span><span class="c1">; B sert de compteur, on va mettre les trois premières adresses sur la pile</span><span class="w"></span>
<span class="nl">prepare_test_loop:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">e</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"> </span><span class="c1">; Récupération de la première partie de l'adresse</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">d</span><span class="p">,(</span><span class="nv">hl</span><span class="p">)</span><span class="w"> </span><span class="c1">; Récupération de la seconde partie de l'adresse</span><span class="w"></span>
<span class="w"> </span><span class="nf">inc</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">de</span><span class="w"> </span><span class="c1">; DE contient l'adresse, qui est poussée sur la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">djnz</span><span class="w"> </span><span class="nv">prepare_test_loop</span><span class="w"> </span><span class="c1">; DJNZ décrémente B et, si B n'est pas égal à zéro, retourne au label indiqué</span><span class="w"></span>
<span class="w"> </span><span class="c1">; C'est la manière canonique d'effectuer des boucles</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; La dernière adresse est poussée directement, car elle pointe sur la chaîne de caractères,</span><span class="w"></span>
<span class="w"> </span><span class="c1">; sans indirection.</span><span class="w"></span>
<span class="w"> </span><span class="nf">jp</span><span class="w"> </span><span class="nv">test</span><span class="w"> </span><span class="c1">; ici, on devrait faire un CALL à la routine de test. Mais ce CALL serait immédiatement</span><span class="w"></span>
<span class="w"> </span><span class="c1">; suivi d'un RET. Dans ce cas-ci, on peut remplacer le CALL par un JP.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Si vous avez bien compris ce que font CALL, RET et JP, alors vous devriez comprendre</span><span class="w"></span>
<span class="w"> </span><span class="c1">; pourquoi.</span><span class="w"></span>
</code></pre></div>
<p>À présent, à l'appel de la routine test, il y a sur la pile, dans l'autre du plus « <strong>haut</strong> » vers le plus « <strong>bas</strong> » : l'identifiant sous forme de chaîne de caractères, l'adresse de la fonction à appeler, le pointeur de données de références, le pointeur de données en entrée.</p>
<p>Il s'agit de récupérer tout cela.</p>
<p>Voici le début de la routine modifiée, le reste ne change pas :</p>
<div class="highlight"><pre><span></span><code><span class="nl">test_sep_msg:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">": \0"</span><span class="w"> </span><span class="c1">; Une chaîne de caractère, voir plus loin</span><span class="w"></span>
<span class="nl">test:</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; La première opération consiste à afficher l'identifiant</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">print_str</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">test_sep_msg</span><span class="w"> </span><span class="c1">; Suivi de la nouvelle chaîne de caractère, pour afficher les deux points</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">print_str</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; La valeur suivante récupérée est l'adresse d'appel de la fonction</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Les appels indirects sur un Z80 ne sont pas naturels, il n'existe pas de CALL</span><span class="w"></span>
<span class="w"> </span><span class="c1">; à une adresse non préalablement fixée.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="p">(</span><span class="nv">call_func</span><span class="o">+</span><span class="mi">1</span><span class="p">),</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; Du coup, on profite du fait d'être en RAM pour modifier le code à la volée</span><span class="w"></span>
<span class="w"> </span><span class="c1">; en modifiant directement l'adresse du CALL à la fonction.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Cela ne serait pas possible avec un programme en ROM par exemple, mais il</span><span class="w"></span>
<span class="w"> </span><span class="c1">; existe plusieurs autres possibilités (utilisation de vecteurs et</span><span class="w"></span>
<span class="w"> </span><span class="c1">; modification manuelle de la pile par exemple)</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Ce genre de manipulation vient avec des contraintes, mais qui dans notre cas</span><span class="w"></span>
<span class="w"> </span><span class="c1">; sont tout à fait acceptables.</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; Récupération de l'adresse des données de référence</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"> </span><span class="c1">; Récupération de l'adresse des données en entrée</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; Sauvegarde temporaire de HL</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Le calcul du nombre de données, comment avant</span><span class="w"></span>
<span class="w"> </span><span class="nf">sbc</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">b</span><span class="p">,</span><span class="nv">h</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">c</span><span class="p">,</span><span class="nv">l</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; Récupération de la sauvegarde temporaire de HL</span><span class="w"></span>
<span class="nl">test_loop:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">a</span><span class="p">,(</span><span class="nv">de</span><span class="p">)</span><span class="w"></span>
<span class="nl">call_func:</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="kc">$</span><span class="mi">0000</span><span class="w"> </span><span class="c1">; Ici, le CALL à l'adresse $0000 sera modifié dynamiquement par</span><span class="w"></span>
<span class="w"> </span><span class="c1">; la manipulation décrite ci-dessus. Lors de l'exécution de cette instruction,</span><span class="w"></span>
<span class="w"> </span><span class="c1">; c'est donc bien la fonction spécifiée qui sera appelée.</span><span class="w"></span>
</code></pre></div>
<h4>Résultats</h4>
<p>En situation réelle, il est très peu probable que j'utilise des fonctions <strong>identité</strong> ou <strong>division par 2</strong>. Les calculs seront faits sur place. Cependant, tester ces fonctions m'ont permis de développer mon petit framework de tests, assez minimaliste, et cela va m'être bien utile pour la suite, pour attaquer la division.</p>
<p><img alt="Affichage des résultats de tests dans MAME" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201802/VG5000-TestFramework.png"></p>VG5000µ, SetPoint en ASM, vérifier la pile2018-02-18T00:00:00+01:002018-02-18T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2018-02-18:/vg5000m-setpoint-en-asm-verifier-la-pile.html<p>Il y a maintenant pas mal de temps, j'avais implémenté, en <strong>BASIC</strong>, une routine pour <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-limplementation.html">afficher un point</a> à l'écran. Puis de là, une routine pour tracer <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html"><strong>une ligne</strong></a>, puis <a href="https://www.triceraprog.fr/trace-dun-cercle-en-basic-sur-vg5000m.html"><strong>un cercle</strong></a>. Le constat était que <strong>c'était très lent</strong>. Le <strong>BASIC</strong> interprété est déjà plutôt lent de manière générale, et …</p><p>Il y a maintenant pas mal de temps, j'avais implémenté, en <strong>BASIC</strong>, une routine pour <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-limplementation.html">afficher un point</a> à l'écran. Puis de là, une routine pour tracer <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html"><strong>une ligne</strong></a>, puis <a href="https://www.triceraprog.fr/trace-dun-cercle-en-basic-sur-vg5000m.html"><strong>un cercle</strong></a>. Le constat était que <strong>c'était très lent</strong>. Le <strong>BASIC</strong> interprété est déjà plutôt lent de manière générale, et celui du VG5000µ n'est pas particulièrement rapide.</p>
<p>Il y a plusieurs raisons à cela, et ce sera peut-être le contenu d'articles futurs.</p>
<p>Mais en attendant, et après tous les efforts pour se confectionner un environnement de travail pour développer en assembleur, je repars sur l'implémentation du tracé d'un <strong>point à l'écran</strong>, et cette fois-ci, <strong>en assembleur</strong>.</p>
<p>L'avantage d'un programme écrit dans un langage de « <strong>haut niveau</strong> », comme le <strong>BASIC</strong>, est de simplifier bien des choses. Faire une division d'une variable <code>A</code> par 3 par exemple, peut s'écrire <code>B = A/3</code>. C'est simple, concis, lisible.</p>
<p>Traduire cela en assembleur n'est pas toujours simple. <strong>Diviser</strong> par un nombre quelconque n'est pas direct car le processeur <strong>Z80</strong> n'a <strong>pas</strong> d'instruction <strong>de division</strong>. Le processeur possède des instructions pour additionner et soustraire, mais pas pour multiplier, ni de diviser.</p>
<p>Diviser par des nombres en particuliers est parfois simple, comme <strong>diviser par 2</strong>, qui consiste à décaler tous les bits d'un nombre binaire « vers la droite », tout comme <strong>diviser par 10</strong> dans notre arithmétique courante consiste à décaler tous les chiffres vers la droite (ou supprimer l'unité, si vous préférez).</p>
<p>Pour <code>30</code> par exemple, en décimal, une vision par <code>10</code> donne <code>3</code>. En binaire, le même nombre s'écrit <code>00011110</code> (sur 8 bits) et sa division par <code>2</code> donne <code>00001111</code>, c'est-à-dire <code>15</code> en décimal. Diviser (ou multiplier) par des multiples de la base dans laquelle on représente les nombres est simple.</p>
<p>Cependant, dans le calcul du pixel à afficher, j'avais une division par <code>3</code>. Et là... c'est plus <strong>compliqué</strong>.</p>
<p>La première étape, puisque je pars de zéro est d'implémenter une division. Puisque le calcul n'a besoin que de diviser par <code>2</code> et par <code>3</code>, et que la division par <code>2</code> est simple, je vais me contenter d'une division par <code>3</code>.</p>
<p>Mais <strong>STOP</strong>. Si vous suivez ce blog, vous avez peut-être vu que j'aime essayer de transposer des techniques modernes sur d'anciennes machines. C'est de la rétro-programmation anachronique, mais qui peut convenir au fait que, de toute façon, programmer ce genre de machines depuis un ordinateur actuel est anachronique.</p>
<p>Une <strong>technique moderne</strong> de développement (qui a ses détracteurs) est de guider sa programmation à travers des <strong>tests</strong> qui peuvent, à chaque instant, indiquer si une erreur apparaît. La méthode est globalement de : 1/ écrire un test... qui échoue 2/ écrire le minimum pour faire passer ce test 3/ améliorer (sans ajouter de nouvelle fonctionnalité).</p>
<p>Le point 3/ en particulier, permet de tester des choses en étant certain que ce que l'on a programmé ne « casse » pas. Suite à une optimisation un peu trop cavalière par exemple. Je ne suis pas un spécialiste de l'écriture de programmes en assembleur Z80, et un outil qui me permet de vérifier que mon changement ne casse pas tout m'intéresse.</p>
<p>Puisque je vais <strong>implémenter une fonction</strong>, mon environnement de test prendra en entrée une <strong>suite de nombres</strong>, y <strong>appliquera</strong> la <strong>fonction</strong> puis, testera que <strong>les résultats</strong> sont ceux que j'attends. J'affiche ensuite le résultat de la comparaison.</p>
<p>Techniquement, un tel système de tests devrait lui-même être testé... mais à si bas niveau, c'est aller un peu trop loin pour ce que je veux faire.</p>
<h4>Le premier test</h4>
<p>La première chose dont je veux m'assurer, c'est que <strong>la pile</strong> à la sortie de mon traitement soit dans le <strong>même état</strong> qu'au <strong>début</strong>. En effet, si ce n'est pas le cas, il va se passer des choses probablement à classer dans le domaine du « mal ». Dans le meilleur des cas une erreur bizarre, dans le pire (et souvent), un <em>reboot</em> de la machine.</p>
<p><em>Aparté</em>: je crois n'avoir jamais parlé de <strong>la pile</strong> dans un article précédent. Très rapidement, c'est un endroit en mémoire où l'on peut stocker des informations sous forme de <strong>pile</strong> (imaginez une pile d'assiettes). La <strong>dernière</strong> donnée <strong>mise</strong> sur le pile est aussi la <strong>première</strong> que l'on <strong>lira</strong> par la suite. Cette pile est entre autre la moyen lors de l'appelle d'une routine d'en revenir. À l'appel, l'adresse du code appelant est mis sur la pile. Pour retrouver cette adresse, il faut donc que l'état de la pile soit le même en entrée et en sortie de fonction.</p>
<p>Voilà la première partie de la vérification de l'état de la pile.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="mi">0</span><span class="w"> </span><span class="c1">; Il n'est pas possible sur Z80 de prendre le pointeur de pile pour le mettre dans un registre</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nb">sp</span><span class="w"> </span><span class="c1">; l'astuce est donc d'ajouter à 0 la valeur du pointeur de pile, en deux étapes.</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; Et je pousse la valeur du pointeur de pile dans la pile</span><span class="w"></span>
</code></pre></div>
<p>Il y a à présent en haut de la pile une valeur arbitraire suivi de l'adresse de la pile en début de fonction.</p>
<p>Voici ensuite la seconde partie de la vérification de l'état de la pile. C'est un peu plus long car il y a la vérification ainsi que l'affichage du résultat.</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"> </span><span class="c1">; Récupération de la valeur depuis la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"> </span><span class="c1">; Reset de la retenue</span><span class="w"></span>
<span class="w"> </span><span class="nf">sbc</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nb">sp</span><span class="w"> </span><span class="c1">; Soustraction de cette valeur avec le pointeur de pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nz</span><span class="p">,</span><span class="nv">print_stk_fail</span><span class="w"> </span><span class="c1">; Si le résultat n'est pas zéro, c'est qu'on n'a pas trouvé la bonne valeur dans la pile</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Dans ce cas, saut à print_stk_fail</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Si tout ce passe bien, c'est à dire que la pile n'a pas été corrompue et qu'elle</span><span class="w"></span>
<span class="w"> </span><span class="c1">; est au même « niveau » qu'au début, alors...</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">stack_ok</span><span class="w"> </span><span class="c1">; on charge dans HL le message de succès</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="kc">$</span><span class="mi">36</span><span class="nv">aa</span><span class="w"> </span><span class="c1">; et on l'affiche</span><span class="w"></span>
<span class="w"> </span><span class="c1">; [...] ; On verra plus tard ce qui est ici.</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">print_stk_fail:</span><span class="w"> </span><span class="c1">; On arrive ici en cas d'échec</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">stack_fail</span><span class="w"> </span><span class="c1">; On charge dans HL le message d’échec</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="kc">$</span><span class="mi">36</span><span class="nv">aa</span><span class="w"> </span><span class="c1">; et on l'affiche</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Mais on ne peut pas sortir comme ça de la fonction. Puisque la pile n'est pas dans</span><span class="w"></span>
<span class="w"> </span><span class="c1">; le même état qu'au début, l'instruction `RET` ne va pas trouver l'adresse ramenant</span><span class="w"></span>
<span class="w"> </span><span class="c1">; au programme appelant, et cela ne va rien amener de bon (un reboot très souvent)</span><span class="w"></span>
<span class="nl">endless_loop:</span><span class="w"></span>
<span class="w"> </span><span class="nf">halt</span><span class="w"> </span><span class="c1">; Alors on arrête tout... ou presque. L'instruction HALT va arrêter le système jusqu'à</span><span class="w"></span>
<span class="w"> </span><span class="c1">; la prochaine interruption, qui est, sur VG5000µ, l'interruption d'affichage.</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">endless_loop</span><span class="w"> </span><span class="c1">; Puis à la fin de l'affichage, ou boucle à l'infinie.</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Cela permet de voir le message d'erreur.</span><span class="w"></span>
<span class="nl">stack_ok:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">"Stack Pass!\0"</span><span class="w"></span>
<span class="nl">stack_fail:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">"Stack Fail!\0"</span><span class="w"></span>
</code></pre></div>
<h4>Les instructions utilisées</h4>
<p>Lire de l'assembleur peut être un peu déroutant au premier abord. Les explications fonctionnelles sont disponibles en commentaire dans le code ci-dessous, voici à présent les instructions utilisées dans l'ordre de leur apparition :</p>
<ul>
<li><code>ld</code> : abréviation de <strong>load</strong>, c'est-à-dire <strong>charge</strong>. La valeur à droite de la virgule est <strong>chargée</strong> dans le registre à gauche. Par exemple, après <code>ld hl,0</code>, le registre <code>HL</code> contiendra la valeur <code>0</code>,</li>
<li><code>add</code> : la valeur de droite (ou le contenu du registre à droite) de la virgule est <strong>additionné</strong> (<strong>add</strong>) dans le registre à gauche. Arès <code>add hl,sp</code>, le contenu de <code>HL</code> sera augmenté de la valeur de <code>SP</code>,</li>
<li><code>push</code> : <strong>pousse</strong> la valeur contenu dans le registre sur la pile, après <strong>push hl</strong>, le contenu de <code>HL</code> est <strong>en haut</strong> de la pile, <code>HL</code> n'est pas modifié,</li>
<li><code>pop</code> : est le contraire de <code>push</code>. La valeur <strong>en haut</strong> de la pile est <strong>chargée</strong> dans le registre en paramètre, puis la pile est positionné sur l'élément <strong>au-dessous</strong>. Après <strong>pop hl</strong>, <code>HL</code> contient le contenu qui était <strong>en haut</strong> de la pile,</li>
<li><code>or</code> : permet d'effectuer une opération <strong>ou</strong>. Ici, cependant, cette instruction est utilisée uniquement pour mettre le <strong>drapeau</strong> de retenue à zéro, à cause de l'instruction suivante,</li>
<li><code>sbc</code> : <strong>soustraction avec retenue</strong>, la valeur du registre de droite est soustraite de la valeur du registre de gauche. Il n'est pas possible sur Z80 de faire une soustraction sans retenue entre deux registres 16 bits. De là vient la nécessité d'effacer la retenue, au cas où, avec le <code>or</code> précédent. Le résultat est <strong>chargé</strong> dans le registre de gauche.</li>
<li><code>jr</code> : <strong>saut relatif</strong>, un branchement, c'est-à-dire une modification d'ordre d'exécution, va être effectué au <strong>label</strong> indiqué. Dans le code ci-dessus, <code>nz</code> à gauche de la virgule indique que le saut sera conditionné par le résultat du calcul précédent si celui-ci n'était pas nul (<code>not zero</code>). Après <code>jr nz,print_stk_fail</code>, le processeur continuera son exécution à l'emplacement indiqué par <code>print_stk_fail</code> si le résultat n'est pas <code>0</code>. Dans le cas contraire, le branchement n'a pas lieu et l'exécution continue à l'instruction suivante,</li>
<li><code>call</code> : est un <strong>appel</strong> de sous-routine. L'emplacement de l'instruction suivante est mise sur la pile (équivalent d'un <strong>push</strong>) puis un branchement est fait au <strong>label</strong> indiqué,</li>
<li><code>ret</code> : est le pendant de <strong>call</strong>, la valeur <strong>en haut</strong> de la pile est prise pour prochaine instruction grâce à l'équivalent d'un <strong>pop</strong>. L'exécution continue donc là d'où avait été appelée la routine,</li>
<li><code>halt</code> : <strong>arrête</strong> l'exécution du processeur. Lorsqu'une interruption matérielle est reçue, le processeur se remet en route.</li>
<li><code>defm</code> : ce n'est pas une instruction du processeur mais une directive pour l'assembleur. Une zone mémoire est réservée et initialisée avec la chaîne de caractères qui suit.</li>
</ul>
<h4>Garder le contexte</h4>
<p>Cette routine de vérification de la pile modifie quelques registres. Pour laisser les choses dans l'était où elles étaient lorsque ce code sera appelé, il est de bon ton de sauvegarder l'état des registres et de les restituer à la fin. Cela peut se faire par une série de <code>push</code> et de <code>pop</code>. Ce qui donne au final :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="c1">; Vérification de pile</span><span class="w"></span>
<span class="w"> </span><span class="k">org</span><span class="w"> </span><span class="kc">$</span><span class="mi">7000</span><span class="w"></span>
<span class="w"> </span><span class="nf">defc</span><span class="w"> </span><span class="nv">print_str</span><span class="w"> </span><span class="err">=</span><span class="w"> </span><span class="kc">$</span><span class="mi">36</span><span class="nv">aa</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Sauve le contexte</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Enregistre la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="kc">$</span><span class="mi">0</span><span class="w"></span>
<span class="w"> </span><span class="nf">add</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nb">sp</span><span class="w"></span>
<span class="w"> </span><span class="nf">push</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="c1">;</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Opérations futures...</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Vérification de la pile</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">or</span><span class="w"> </span><span class="nv">a</span><span class="p">,</span><span class="nv">a</span><span class="w"></span>
<span class="w"> </span><span class="nf">sbc</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nb">sp</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">nz</span><span class="p">,</span><span class="nv">print_stk_fail</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Message en cas de succès</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">stack_ok</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">print_str</span><span class="w"></span>
<span class="w"> </span><span class="c1">; Restitution du contexte</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">de</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">af</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">bc</span><span class="w"></span>
<span class="w"> </span><span class="nf">pop</span><span class="w"> </span><span class="nv">hl</span><span class="w"></span>
<span class="w"> </span><span class="nf">ret</span><span class="w"></span>
<span class="nl">print_stk_fail:</span><span class="w"></span>
<span class="w"> </span><span class="nf">ld</span><span class="w"> </span><span class="nv">hl</span><span class="p">,</span><span class="nv">stack_fail</span><span class="w"></span>
<span class="w"> </span><span class="nf">call</span><span class="w"> </span><span class="nv">print_str</span><span class="w"></span>
<span class="nl">loop:</span><span class="w"></span>
<span class="w"> </span><span class="nf">halt</span><span class="w"></span>
<span class="w"> </span><span class="nf">jr</span><span class="w"> </span><span class="nv">loop</span><span class="w"></span>
<span class="nl">stack_ok:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">"Stack Pass!\0"</span><span class="w"></span>
<span class="nl">stack_fail:</span><span class="w"></span>
<span class="w"> </span><span class="nf">defm</span><span class="w"> </span><span class="s">"Stack Fail!\0"</span><span class="w"></span>
</code></pre></div>
<h4>Résultat</h4>
<p>Pour le moment, pas grand chose, tout se passe bien et un message est affiché indiquant que... la pile est ok.</p>
<p><img alt="Affichage du test de pile dans MAME" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201802/VG5000-StackPass.jpeg"></p>Automatisation : utilisation de Sublime Text 32018-02-12T00:00:00+01:002018-02-12T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2018-02-12:/automatisation-utilisation-de-sublime-text-3.html<p>Dans les articles précédents sur l'<strong>automatisation</strong> au niveau des outils de développement, j'avais vu comment <strong>injecter un programme</strong> dans MAME en émulation <strong>VG5000µ</strong> d'abord <a href="https://www.triceraprog.fr/injecter-un-programme-dans-un-emulateur-vg5000m.html">grâce au debuggeur</a> manuellement, puis avec <a href="https://www.triceraprog.fr/injecter-un-programme-dans-un-emulateur-vg5000m-partie-ii.html">un script LUA</a> grâce aux possibilité d'extensions de <strong>MAME</strong>.</p>
<p>Ce n'est toujours <strong>pas suffisant</strong> pour moi. Comme je l'ai …</p><p>Dans les articles précédents sur l'<strong>automatisation</strong> au niveau des outils de développement, j'avais vu comment <strong>injecter un programme</strong> dans MAME en émulation <strong>VG5000µ</strong> d'abord <a href="https://www.triceraprog.fr/injecter-un-programme-dans-un-emulateur-vg5000m.html">grâce au debuggeur</a> manuellement, puis avec <a href="https://www.triceraprog.fr/injecter-un-programme-dans-un-emulateur-vg5000m-partie-ii.html">un script LUA</a> grâce aux possibilité d'extensions de <strong>MAME</strong>.</p>
<p>Ce n'est toujours <strong>pas suffisant</strong> pour moi. Comme je l'ai déjà écrit dans ces autres articles, je ne veux pas faire ce qu'une automatisation peut faire bien <strong>plus facilement</strong>, et <strong>sans se tromper</strong>. Lancer l'assembleur en ligne de commande, puis lancer <strong>MAME</strong> avec les bons paramètres, cela n'est pas bien compliqué avec un <code>shell</code> moderne (comme <code>fish</code>).</p>
<p>Un des <strong>principaux problèmes</strong> que j'y vois, outre que ce sont des <strong>opérations manuelles</strong>, c'est que ce sont des opérations <strong>non documentées</strong>. Si je fais une pause dans un projet et que j'y reviens deux mois après (ça arrive bien souvent, surtout pour du hobby), il y a de bonnes chances que je ne me <strong>souvienne plus</strong> des opérations exactes à faire. J'ai pu prendre des notes, laisser des instructions, écrire un article même. Mais quoi de mieux comme instructions que de laisser des <strong>outils qui fonctionneront</strong> et seront une <strong>source de documentation</strong> si besoin ?</p>
<p>Un premier script assez simple serait de regrouper l'assemblage et le lancement dans un même script. Ou pourquoi pas dans un <code>Makefile</code>.</p>
<p>J'ai choisi d'ajouter les outils à un environnement <code>Sublime Text 3</code>, qui est l'un des deux éditeurs de texte que j'utilise le plus souvent.</p>
<h4>De la couleur</h4>
<p>Et puisque j'en étais à créer un petit environnement confortable pour mettre au point des <strong>programmes en assembleurs</strong> pour <code>Z80</code>, j'ai fait un petit détour par la <strong>colorisation syntaxique</strong>. N'ayant pas trouvé de colorisation adéquat à de l'assembleur tel qu'attendu par <code>z80asm</code>, j'ai écrit une <strong>description simpliste</strong> que vous trouverez dans le package en fin d'article.</p>
<p>Il ne gère pas tous les cas, et je pense l'améliorer en fonction des besoins.</p>
<p>Au passage, <code>Sublime Text 3</code> a un système de tests pour la colorisation syntaxique qui est extrêmement pratique pour mettre au point le fichier. Le fichier de test est disponible dans le paquetage au côté des autres, pour référence.</p>
<h4>Un système de build</h4>
<p><strong>Deuxième étape</strong>, la plus importante, celle qui permet de <strong>construire un code objet</strong> à partir d'un <strong>code source</strong> en assembleur. La mise en place de la colorisation syntaxique n'a pas amené que des couleurs à l'affichage. Elle a aussi permis de déclarer l'existence d'un <strong>format spécifique</strong>, que j'ai appelé <strong>z80asm</strong>.</p>
<p>Lorsque l'éditeur de texte ouvrira un fichier <code>.asm</code>, il l'associera par défaut à ce format (sauf si vous avez d'autres associations pour <code>.asm</code>, auquel cas cela sera un choix possible).</p>
<p>Pour créer un nouveau système de <code>build</code> avec Sublime Text 3, il suffit d'aller dans <code>Tools</code> -> <code>Build System</code> -> <code>New Build System</code>. Un nouveau fichier de description, en <code>JSON</code>, permettra d'indiquer à l'éditeur les actions qu'il devra effectuer lors de la construction du fichier.</p>
<p>Pour une <strong>construction simple</strong>, qui appel l'assembleur <code>z80asm</code> se trouvant dans vos chemin de recherche d'exécutables, cela peut donner ceci :</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"selector"</span><span class="p">:</span><span class="w"> </span><span class="s2">"source.asm.z80"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"cmd"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"z80asm"</span><span class="p">,</span><span class="w"> </span><span class="s2">"-b"</span><span class="p">,</span><span class="w"> </span><span class="s2">"-v"</span><span class="p">,</span><span class="w"> </span><span class="s2">"$file"</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"file_patterns"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"*.asm"</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"file_regex"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^Error at file '([^']+)' line (\\d+): ()(.+)"</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<ul>
<li><code>selector</code>: indique que ce type de construction est valable pour le type de fichier associé au format <code>source.asm.z80</code>.</li>
<li><code>cmd</code>: indique la commande à exécuter. <code>$file</code> indiquant le fichier actif au moment du lancement de la construction.</li>
<li><code>file_patterns</code>: indique le type de fichier valable pour ce type de construction aussi, au cas où le format de <code>selector</code> n'aurait pas été appliqué</li>
<li><code>file_regex</code>: est une expression régulière identifiant les erreurs de construction, et permettant à l'éditeur de texte d'indiquer ces erreurs directement dans le fichier édité.</li>
</ul>
<p>Avec ceci, un <code>Ctrl-B</code> (sous Linux et Windows) transforme votre fichier <code>.asm</code> en fichier du même nom <code>.bin</code>.</p>
<h4>Lancer MAME après le build</h4>
<p>Je voulais aller <strong>un peu plus loin</strong> et avoir la possibilité de lancer <code>MAME</code> depuis <code>Sublime Text</code> si l'assemblage était un succès. Pour cela, il m'a fallu écrire un script <code>python</code> un peu plus complexe qui lance l'assemblage de la même manière que ce qui est indiqué au paragraphe précédent, et en cas de succès, lance <code>MAME</code> avec les bons paramètres.</p>
<p>Il aurait été aussi <strong>tout à fait possible</strong>, et probablement <strong>plus simple</strong>, d'appeler un fichier <strong>script shell</strong> (ou batch). Mais j'avais depuis quelques temps envie de comprendre comment écrire un plugin de build pour <code>Sublime Text</code>, j'ai donc fait un détour.</p>
<p>Je ne rentrerai pas dans le détail du fonctionnement du <strong>plugin</strong> de build que vous pourrez trouver dans le paquetage à la fin de l'article.</p>
<p>Le système est améliorable. Pour le moment, l'adresse de démarrage du code objet est fixé par le script <code>vgboot.lua</code>. Il faudra donc changer cette adresse dans le script à l'endroit de l'injection du code et au moment du <code>CALL</code> si votre code objet est situé à une autre adresse mémoire.</p>
<p>Dans le fichier <code>README.md</code>, vous trouverez les instructions pour créer le système de build correspondant. Je le reprends ici pour l'expliquer :</p>
<div class="highlight"><pre><span></span><code><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"selector"</span><span class="p">:</span><span class="w"> </span><span class="s2">"source.asm.z80"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"cmd"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"z80asm"</span><span class="p">,</span><span class="w"> </span><span class="s2">"-b"</span><span class="p">,</span><span class="w"> </span><span class="s2">"-v"</span><span class="p">,</span><span class="w"> </span><span class="s2">"$file"</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"file_patterns"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="s2">"*.asm"</span><span class="p">],</span><span class="w"></span>
<span class="w"> </span><span class="nt">"file_regex"</span><span class="p">:</span><span class="w"> </span><span class="s2">"^Error at file '([^']+)' line (\\d+): ()(.+)"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"variants"</span><span class="p">:</span><span class="w"> </span><span class="p">[</span><span class="w"></span>
<span class="w"> </span><span class="p">{</span><span class="w"></span>
<span class="w"> </span><span class="nt">"target"</span><span class="p">:</span><span class="w"> </span><span class="s2">"z80_asm"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"name"</span><span class="p">:</span><span class="w"> </span><span class="s2">"run"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"mame_path"</span><span class="p">:</span><span class="w"> </span><span class="s2">"mame"</span><span class="p">,</span><span class="w"></span>
<span class="w"> </span><span class="nt">"script"</span><span class="p">:</span><span class="w"> </span><span class="s2">"vgboot.lua"</span><span class="w"></span>
<span class="w"> </span><span class="p">}</span><span class="w"></span>
<span class="w"> </span><span class="p">]</span><span class="w"></span>
<span class="p">}</span><span class="w"></span>
</code></pre></div>
<p>Outre le début, qui est identique à la version du paragraphe précédent, une section <code>variants</code> a fait son apparition. Cette section indique des <strong>versions alternatives</strong> de construction. Le paramètre <code>target</code> indique quel est le <strong>plugin</strong> à invoquer. Les autres paramètres sont passés tel quel au plugin.</p>
<p>Les variantes hérites aussi des paramètres principaux, mais mon <strong>plugin</strong> se contente de les ignorer pour tout gérer seul.</p>
<p>Pour sélectionner une <strong>variante</strong> dans <code>Sublime Text 3</code>, le raccourci est <code>Ctrl-Shift-B</code> (sous Linux et Windows).</p>
<h4>Le paquetage</h4>
<p>Les sources du paquetages sont <a href="https://github.com/Triceraprog/z80asm.sublime-package">disponibles sur Github</a> mais aussi sous forme de paquetage <code>Sublime Text 3</code> (qui est en fait un fichier zip) <a href="https://www.triceraprog.fr/files/201802/z80asm.sublime-package">directement ici</a> (à la version lors de la publication de l'article).</p>
<h4>Le résultat</h4>
<p>À présent, je peux <strong>écrire de l'assembleur</strong> dans mon éditeur de texte et, par un seul <strong>raccourci</strong>, lancer l'<strong>assemblage</strong>, avoir un <strong>retour d'erreur</strong> annoté directement dans le code source, et en cas de réussite, le <strong>programme</strong> lancé directement <strong>dans l'émulateur</strong>.</p>
<p>On pourrait aller encore plus loin, avec le script <code>MAME</code> qui ouvrirait un canal de communication afin d'injecter le code sans relancer l'émulateur... Mais je vais m'arrêter ici et revenir sur le sujet qui a entraîné tout cela.</p>Scripter MAME pour explorer la machine2018-01-18T00:00:00+01:002018-01-18T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2018-01-18:/scripter-mame-pour-explorer-la-machine.html<p>Puisque je suis actuellement dans l'utilisation de <a href="https://www.lua.org/">LUA</a> pour scripter MAME, faisons un petit détour pour <strong>explorer</strong> quelques <strong>possibilités</strong>.</p>
<p>Les <strong>fonctions accessibles</strong> aux scripts LUA sont assez nombreuses. Je n'en connais pas de <strong>documentation</strong> complète si ce n'est dans les <strong>sources du programme</strong> lui-même à cet endroit : <code>mame/src/frontend …</code></p><p>Puisque je suis actuellement dans l'utilisation de <a href="https://www.lua.org/">LUA</a> pour scripter MAME, faisons un petit détour pour <strong>explorer</strong> quelques <strong>possibilités</strong>.</p>
<p>Les <strong>fonctions accessibles</strong> aux scripts LUA sont assez nombreuses. Je n'en connais pas de <strong>documentation</strong> complète si ce n'est dans les <strong>sources du programme</strong> lui-même à cet endroit : <code>mame/src/frontend/mame/luaengine.cpp</code>. Des groupes de <strong>commentaires</strong> indiquent les fonctions et <strong>leur usage</strong>. Reste à utiliser la <a href="https://www.triceraprog.fr/injecter-un-programme-dans-un-emulateur-vg5000m.html">console</a> pour <strong>expérimenter</strong> un peu la façon de les appeler.</p>
<p>L'une des possibilités intéressantes est de pouvoir <strong>agir sur l'affichage</strong> de l'émulateur. Cela peut être assez pratique pour suivre certaines valeurs en mémoire, pour afficher des informations sur le comportement de la machine, voire d'ajouter des fonctionnalités interactives à l'émulateur facilement.</p>
<h4>Affichage à l'écran</h4>
<p>La <strong>première chose</strong> à faire pour afficher quelque chose à l'écran est de récupérer un objet permettant de le manipuler. Pour cela, en passant par l'objet <code>manager</code>, on récupère la machine, puis un écran qui dans notre cas se nomme <code>:screen</code>.</p>
<p>Grâce à cet objet, il est ensuite possible d'<strong>afficher du texte</strong>, de <strong>tracer des lignes</strong> et des boites. C'est limité, mais déjà pas mal.</p>
<p>Ce qui suit est un <strong>exemple</strong> d'utilisation dans lequel je vais <strong>chercher</strong> directement <strong>en mémoire</strong> les <strong>coordonnées</strong> X et Y <strong>du curseur</strong> du VG5000µ, qui sont situées aux emplacement mémoire fixes <code>$4805</code> et <code>$4806</code>. Par un objet donnant accès à la mémoire et l'utilisation des fonctions <code>read_u8</code> (comme <strong>lecture d'un entier non signé sur 8 bits</strong>), je récupère les valeurs et je les affiche.</p>
<p>Simple et efficace. Je peux à présent, si le script est dans un fichier nommé <code>vgdisplay.lua</code>, lancer la commande suivant pour suivre les évolutions des coordonnées du curseur.</p>
<p><code>mame64 vg5k -ramsize 48k -nomax -window -autoboot_delay 0 -autoboot_script vgdisplay.lua</code></p>
<h4>Le script</h4>
<div class="highlight"><pre><span></span><code><span class="c1">-- Script for MAME that launches the vg5k emulator</span>
<span class="c1">-- and continuously displays the position of the cursor</span>
<span class="c1">-- Get the only screen of the VG-5000µ</span>
<span class="kd">local</span> <span class="n">screen</span> <span class="o">=</span> <span class="n">manager</span><span class="p">:</span><span class="n">machine</span><span class="p">().</span><span class="n">screens</span><span class="p">[</span><span class="s2">":screen"</span><span class="p">]</span>
<span class="c1">-- Get access to the memory</span>
<span class="kd">local</span> <span class="n">cpu</span> <span class="o">=</span> <span class="n">manager</span><span class="p">:</span><span class="n">machine</span><span class="p">().</span><span class="n">devices</span><span class="p">[</span><span class="s2">":maincpu"</span><span class="p">]</span>
<span class="kd">local</span> <span class="n">memory</span> <span class="o">=</span> <span class="n">cpu</span><span class="p">.</span><span class="n">spaces</span><span class="p">[</span><span class="s2">"program"</span><span class="p">]</span>
<span class="kr">function</span> <span class="nf">draw_hud</span><span class="p">()</span>
<span class="kd">local</span> <span class="n">cursor_x</span> <span class="o">=</span> <span class="n">memory</span><span class="p">:</span><span class="n">read_u8</span><span class="p">(</span><span class="mh">0x4805</span><span class="p">)</span>
<span class="kd">local</span> <span class="n">cursor_y</span> <span class="o">=</span> <span class="n">memory</span><span class="p">:</span><span class="n">read_u8</span><span class="p">(</span><span class="mh">0x4806</span><span class="p">)</span>
<span class="n">screen</span><span class="p">:</span><span class="n">draw_text</span><span class="p">(</span><span class="mi">300</span><span class="p">,</span> <span class="mi">40</span><span class="p">,</span> <span class="s2">"X: "</span> <span class="o">..</span> <span class="n">cursor_x</span><span class="p">,</span> <span class="mh">0xffff0000</span><span class="p">);</span>
<span class="n">screen</span><span class="p">:</span><span class="n">draw_text</span><span class="p">(</span><span class="mi">300</span><span class="p">,</span> <span class="mi">50</span><span class="p">,</span> <span class="s2">"Y: "</span> <span class="o">..</span> <span class="n">cursor_y</span><span class="p">,</span> <span class="mh">0xffff0000</span><span class="p">);</span>
<span class="kr">end</span>
<span class="n">emu</span><span class="p">.</span><span class="n">register_frame_done</span><span class="p">(</span><span class="n">draw_hud</span><span class="p">)</span>
</code></pre></div>
<h4>Le résultat</h4>
<p><img alt="Affichage en surimpression dans MAME" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201801/MameAffichageCurseur-750.png"></p>Injecter un programme dans un émulateur VG5000µ, partie II2018-01-10T00:00:00+01:002018-01-10T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2018-01-10:/injecter-un-programme-dans-un-emulateur-vg5000m-partie-ii.html<p>La <a href="({filename}/Machines/20180103-VG5000-MameAutomation.md)">fois dernière</a> était consacrée au <strong>lancement</strong> d'un programme sur <strong>VG5000µ</strong> avec l'émulateur <strong>MAME</strong> de manière à injecter un programme binaire directement en mémoire puis à l'appeler. Tout cela <strong>automatiquement</strong>. Pour rappel, l'idée derrière cela est d'éviter les erreurs de manipulations qui arrivent de temps en temps.</p>
<p>La <strong>première option …</strong></p><p>La <a href="({filename}/Machines/20180103-VG5000-MameAutomation.md)">fois dernière</a> était consacrée au <strong>lancement</strong> d'un programme sur <strong>VG5000µ</strong> avec l'émulateur <strong>MAME</strong> de manière à injecter un programme binaire directement en mémoire puis à l'appeler. Tout cela <strong>automatiquement</strong>. Pour rappel, l'idée derrière cela est d'éviter les erreurs de manipulations qui arrivent de temps en temps.</p>
<p>La <strong>première option</strong> utilisée dans l'article précédent était d'<strong>utiliser un script</strong> pour le debuggeur d'une part, et la capacité de <strong>MAME</strong> à <strong>entrer au clavier</strong> de la machine émulée une suite de frappes de touches.</p>
<p>Cette méthode fonctionne mais a ses limites : il faut régler le délai avant la frappe de touches de manière empirique, et le script est linéaire. Dans cet article, je vais utiliser une autre possibilité de <strong>MAME</strong> pour résoudre ces deux écueils.</p>
<h4>La console</h4>
<p>Commençons par une première manipulation.</p>
<p><code>mame64 vg5k -ramsize 48k -nomax -window -debug -debugger none -console</code></p>
<p>Dans le <strong>shell</strong> depuis lequel vous avez entré cette commande s'affiche un bandeau et une invite <code>[MAME]></code>. Vous avez à présent accès à une <strong>console</strong> qui comprend le <a href="https://www.lua.org/">LUA</a>. C'est bien entendu le paramètre <code>-console</code> qui a permis cela.</p>
<p>Le couple <code>-debug</code> <code>-debugger none</code> est plus surprenant. Il indique que le <strong>debuggeur</strong> est <strong>activé</strong>, mais <strong>sans interface graphique</strong>. Le debuggeur est donc utilisable depuis la console, et c'est parfait. Au passage, vous remarquerez que, en l'absence d'interface graphique pour le debuggeur, l'émulateur ne s'est pas mis en pause.</p>
<p>Depuis la console, vous pouvez essayer ceci :</p>
<div class="highlight"><pre><span></span><code>m = manager:machine()
md = m:debugger()
print(md)
</code></pre></div>
<p>Vous devez voir quelque chose comme <code>sol.debugger_manager*: 0xf9de9a8</code> qui indique que <code>md</code> contient bien un debuggeur (l'adresse sera différente). Si vous obtenez <code>nil</code>, c'est que le debuggeur n'est pas actif.</p>
<p>L'object <code>md</code> permet de s'adresser au debuggueur.</p>
<p>Par exemple :</p>
<div class="highlight"><pre><span></span><code><span class="n">md</span><span class="o">:</span><span class="n">command</span><span class="o">(</span><span class="s2">"help"</span><span class="o">)</span><span class="w"></span>
</code></pre></div>
<p>Et l'aide du debuggeur s'affiche. Nous <strong>communiquons</strong> bien avec le <strong>debuggeur</strong>. C'est pratique.</p>
<h4>Script LUA</h4>
<p>Mais si une console est pratique pour essayer des choses, ce n'est pas ce que je recherche au final. Ce que je recherche à faire est toujours automatiser le démarrage d'un programme pour sa mise au point.</p>
<p>Et <strong>MAME</strong> a la commande qu'il me faut : <code>-autoboot_script</code>. Cette commande, suivie par un nom de fichier, va <strong>exécuter un script</strong> écrit en <strong>LUA</strong> qui aura accès au fonctionnement de l'émulateur.</p>
<p>La commande pourra ressembler à quelque chose comme ça :</p>
<p><code>mame64 vg5k -ramsize 48k -nomax -window -debug -debugger none -autoboot_delay 0 -autoboot_script vgboot.lua</code></p>
<p><code>-autoboot_delay 0</code> indique que le script doit être lancé dès le démarrage de l'émulation de la machine. La première chose (ou presque) à faire dans le script si vous voulez contrôler la machine cible dès le début est alors un <code>emu.pause()</code> que vous pourrez balancer avec un <code>emu.unpause()</code> lorsque votre script sera prêt.</p>
<p>Le script que j'utilise est trop long pour s'afficher sur cette page. Vous pouvez le <a href="https://www.triceraprog.fr/files/201801/20180108-vgboot.lua">trouver ici</a>.</p>
<p>Des choses intéressantes à savoir :</p>
<ul>
<li><code>manager:machine()</code> renvoie l'objet contrôlant machine émulée,</li>
<li><code>manager:machine():debugger()</code> renvoie l'objet de contrôle du debuggeur,</li>
<li><code>manager:machine():debugger().consolelog</code> est le log du debuggeur, pratique pour vérifier les retours de commandes,</li>
<li><code>manager:machine().devices[":maincpu"]</code> renvoie l'objet contrôlant le CPU principal de la machine (le Z80 pour le VG5000µ),</li>
<li><code>manager:machine().devices[":maincpu"]:debug()</code> renvoie l'objet de commande du debuggeur sur ce CPU (qui offre des facilités sur les commandes du debuggeur pour ce processeur en particulier).</li>
</ul>
<p>Sur l'objet renvoyé par <code>debug()</code> sur le CPU, par exemple, on peut appeler <code>bpset()</code> pour placer un <strong>point d'arrêt</strong>. Sur l'objet renvoyé par <code>debugger()</code>, on peut envoyer tous les <strong>commandes</strong> que l'on entrerait dans l'interface graphique avec <code>command()</code>.</p>
<p>Ainsi <code>...:debugger():command("go")</code> a le même effet, sur cette machine, que <code>...:debug():go()</code>.</p>
<p>L'objet <code>emu</code> qui permet de faire <code>pause()</code> et <code>unpause()</code> permet aussi de <strong>simuler</strong> les appuis sur les <strong>touches</strong> du <strong>clavier</strong> émulé. Par exemple <code>emu.keypost('CALL &"7000"\n')</code>.</p>
<h4>C'est plus complexe !</h4>
<p>Oui, cette méthode est plus complexe, plus longue à écrire. Elle permet aussi un meilleur contrôle et des choses que ne permettent pas un script de debuggeur simple. À vous de choisir !</p>
<p><img alt="La console LUA dans MAME" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201801/MameConsole.png"></p>Injecter un programme dans un émulateur VG5000µ2018-01-03T00:00:00+01:002018-01-03T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2018-01-03:/injecter-un-programme-dans-un-emulateur-vg5000m.html<p>Reprenons sur la <strong>programmation en assembleur</strong> sur <strong>VG5000µ</strong> depuis un ordinateur actuel. Car s'il est possible et tout à fait légitime, par hobby, de programmer directement sur la machine, à l'ancienne, pour <strong>« retro programmer »</strong> comme il y a du « retro gaming » en éprouvant les sensations originelles, ce n'est pas mon …</p><p>Reprenons sur la <strong>programmation en assembleur</strong> sur <strong>VG5000µ</strong> depuis un ordinateur actuel. Car s'il est possible et tout à fait légitime, par hobby, de programmer directement sur la machine, à l'ancienne, pour <strong>« retro programmer »</strong> comme il y a du « retro gaming » en éprouvant les sensations originelles, ce n'est pas mon but ici.</p>
<p><strong>Mon but</strong>, c'est de pouvoir programmer <strong>depuis un éditeur de texte</strong> simple, mais pas trop, et de pouvoir mettre au point avec certaines facilités comme un <strong>retour d'erreur</strong> d'assemblage et l'accès à un <strong>debuggeur</strong>. Et tout ceci avec une chaîne qui minimise les manipulations répétitives.</p>
<p><strong>Minimiser</strong> les <strong>manipulations répétitives</strong> permet de rester concentré au maximum sur ce que je fais. Je n'ai pas envie, à chaque lancement, de taper des commandes particulières ou de faire quelques clics souris, toujours les mêmes, avec un <strong>risque de me tromper</strong> et de chasser un bug que je crois être là alors que je me suis juste trompé dans une manipulation.</p>
<p>Il y a de nombreuses manière d'automatiser une chaîne de construction de programme. Certains choix sont contraints par les outils que l'on utilise. D'autres sont des choix par préférences.</p>
<p>Après avoir fait mon travail de reflexion, lorsque je passe à la partie d'entrée au clavier, les données sont les suivantes :</p>
<ul>
<li>j'entre le <strong>code source</strong> dans un éditeur,</li>
<li>j'effectue une action qui produit un <strong>fichier binaire</strong>,</li>
<li>ce fichier binaire doit se lancer dans un <strong>émulateur</strong> et/ou produire un fichier pour chargement sur <strong>machine réelle</strong>,</li>
<li>s'il y a des <strong>erreurs</strong>, les <strong>afficher</strong>,</li>
<li>s'il n'y a pas d'erreurs l'<strong>émulateur</strong> se lance <strong>automatiquement</strong> et lance à son tour le programme.</li>
</ul>
<p>Cette dernière <strong>opération</strong> est mon <strong>point de départ</strong> et le sujet de cet article. Vous aurez compris au passage, et suite à <a href="https://www.triceraprog.fr/travailler-sur-emulateur-et-dump-de-la-rom-vg5000m.html">l'article précédent</a>, que j'utiliserai un <strong>émulateur</strong> pour la <strong>mise au point</strong>, avant de passer sur <strong>machine réelle</strong> pour <strong>confirmation</strong> du fonctionnement.</p>
<h4>Choix de l'émulateur</h4>
<p>Au niveau <strong>VG5000µ</strong>, au moment de l'écriture, il n'y a pas beaucoup de choix : soit <a href="http://dcvg5k.free.fr/">DCVG5K</a>, soit <a href="http://mamedev.org/">MAME</a>. Les <strong>deux</strong> possèdent un <strong>debuggeur</strong> et le moyen d'injecter un binaire en mémoire.</p>
<p>Je vois en <strong>MAME</strong> deux avantages pour moi : la possibilité de <strong>scripter le debuggeur</strong> et le lancement en <strong>natif sous Linux</strong>, qui est derrière le système d'exploitation que j'utilise (Ubuntu). <strong>DCVG5K</strong> a l'avantage de la <strong>légèreté</strong> dû au fait qu'il est dédié à une seule machine, ainsi que de la <strong>simplicité d'utilisation</strong>.</p>
<p>Pour savoir comment utiliser <strong>MAME</strong>, c'est très simple, il suffit de taper <code>mame -showusage</code> en ligne de commande... <strong>Ooops</strong>... 462 lignes d'explication de paramètres... Et ce n'est qu'un résumé.</p>
<p><strong>MAME</strong> est un <strong>gros morceau</strong> et j'espère qu'avec les <strong>renseignements</strong> de cet article, je vous aurait <strong>débrousaillé</strong> le terrain.</p>
<h4>Lancement de l'émulation</h4>
<p><em>Avant toute chose</em> : tout ce que j'explique est du lancement en <strong>ligne de commande</strong>. Il est possible de faire la même chose à travers des fichiers de configuration ou des raccourcis Windows avec paramètres mais le but final est d'inclure ça à un script de lancement. La ligne de commande est aussi la manière la <strong>plus simple</strong> de jouer avec les paramètres (pour peu qu'on utilise un <strong>shell moderne</strong>).</p>
<p>Pour lancer l'émulation du <strong>VG5000µ</strong>, c'est tout simplement :</p>
<p><code>mame64 vg5k</code></p>
<p><em>Note</em> : l'exécutable s'appelle mame64 pour quoi car je compile la version depuis les sources. Suivant les distributions de MAME, il peut avoir un autre nom.</p>
<p><em>Note</em> : Si MAME vous annonce qu'il vous manque une ROM, revenez à mon <a href="https://www.triceraprog.fr/travailler-sur-emulateur-et-dump-de-la-rom-vg5000m.html">article précédent</a>, <strong>dumpez votre ROM</strong>, placez-là dans le répertoire attendu par MAME et c'est reparti.</p>
<p>Le lancement de l'émulateur dans cette configuration <strong>lance la machine</strong> après un bandeau la présentant et, à l'heure actuelle, un bandeau rouge indiquant que l'émulation ne fonctionne pas. En effet, l'émulation n'est pas considérée comme terminée ; mais en ce qui nous concerne, cela sera suffisant.</p>
<p>J'ai évoqué un <strong>debuggeur</strong>. Il est accessible sous plusieurs formes et je vais laisser celle par défaut :</p>
<p><code>mame64 vg5k -ramsize 48k -nomax -window -debug</code></p>
<p>J'ai ajouté au passage quelques options supplémentaires :</p>
<ul>
<li><code>-ramsize 48k</code> indique que je veux l'extension RAM maximale disponible sur le VG5000µ (l'autre option est 32k, et par défaut, la machine aura ses 16k)</li>
<li><code>-nomax</code> indique que je ne veux pas maximiser la fenêtre</li>
<li><code>-window</code> indique que je veux lancer l'émulateur dans une fenêtre et non en plein écran</li>
<li><code>-debug</code>, bien entendu, lance le debugger</li>
</ul>
<p>En <strong>mode debugger</strong> par défaut, l'émulation ne se lance pas tout de suite. Elle est immédiatement <strong>mise en pause</strong>. Sur l'image suivante, vous pouvez voir que le <code>PC</code> est à <code>$0000</code>, sur la première instruction qui sera exécutée par le processeur de la machine <code>jp $1000</code>.</p>
<p><img alt="Debugger par défaut (Qt) sur MAME" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201801/VG5000-Mame-Debug-Qt.jpeg"></p>
<p>Ce <strong>debugger</strong> accepte des <strong>raccourcis clavier</strong> mais peut aussi accepter des <strong>commandes</strong>. Le debugger peut aussi prendre ses commandes <strong>depuis un fichier</strong> texte grace à la commande <code>source</code>.</p>
<p>Je vous invite à entrer le texte suivant dans un fichier <code>debug.txt</code>.</p>
<div class="highlight"><pre><span></span><code><span class="n">go</span><span class="w"> </span><span class="o">$</span><span class="mi">2</span><span class="n">adf</span><span class="w"></span>
<span class="nb">load</span><span class="w"> </span><span class="n">hello</span><span class="o">.</span><span class="n">bin</span><span class="p">,</span><span class="o">$</span><span class="mi">7000</span><span class="w"></span>
<span class="n">go</span><span class="w"></span>
</code></pre></div>
<p><em>Note</em> : <code>go $2adf</code> indique de lancer l'émulation avec un point d'arrêt temporaire à l'adresse $2adf, qui est une addresse qui, lorsqu'elle est atteinte, garanti que le VG5000µ a terminé son initialisation.</p>
<p>Puis d'avoir un <strong>fichier binaire</strong> au nom de <code>hello.bin</code> de mettre ces deux fichiers dans le répertoire de travail de MAME.</p>
<p>Et enfin, depuis le debuggeur, taper <code>source debug.txt</code></p>
<p>Le debuggeur devrait afficher :</p>
<div class="highlight"><pre><span></span><code><span class="o">></span><span class="n">go</span><span class="w"> </span><span class="o">$</span><span class="mi">2</span><span class="n">adf</span><span class="w"></span>
<span class="n">Stopped</span><span class="w"> </span><span class="n">at</span><span class="w"> </span><span class="n">temporary</span><span class="w"> </span><span class="k">breakpoint</span><span class="w"> </span><span class="mi">2</span><span class="n">ADF</span><span class="w"> </span><span class="n">on</span><span class="w"> </span><span class="n">CPU</span><span class="w"> </span><span class="s1">':maincpu'</span><span class="w"></span>
<span class="o">></span><span class="nb">load</span><span class="w"> </span><span class="n">hello</span><span class="o">.</span><span class="n">bin</span><span class="p">,</span><span class="o">$</span><span class="mi">7000</span><span class="w"></span>
<span class="n">Data</span><span class="w"> </span><span class="n">loaded</span><span class="w"> </span><span class="n">successfully</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="n">memory</span><span class="w"> </span><span class="p">:</span><span class="w"> </span><span class="mh">0x7000</span><span class="w"> </span><span class="n">to</span><span class="w"> </span><span class="mh">0x7018</span><span class="w"></span>
<span class="o">></span><span class="n">go</span><span class="w"></span>
</code></pre></div>
<p>Suivant votre fichier, l'addresse <code>0x7018</code> peut varier. Dans mon cas, le fichier provient du résultat de l'assembleur <a href="https://www.triceraprog.fr/du-langage-machine-a-lassembleur-sur-vg5000m.html">d'un article précédent</a>.</p>
<p>Dans le debuggeur, vous pouvez ouvrir une fenêtre de <strong>visualisation de mémoire</strong> pour vérifier que le fichier binaire a été injecté. Et si votre fichier est le même que le mien, vous pouvez entrer <code>CALL &"7000"</code> dans la machine émulée pour voir apparaître <code>Bonjour!</code> à l'écran.</p>
<p>C'est <strong>déjà bien</strong>, non ?</p>
<h4>Automatique on a dit !</h4>
<p>C'est bien... mais taper <code>source</code> à chaque lancement, c'est long, <strong>on peut se tromper</strong> et cela serait mieux si cela pouvait être automatique.</p>
<p>Et c'est tout à fait possible :</p>
<p><code>mame64 vg5k -ramsize 48k -window -nomax -debug -debugscript debug.txt</code></p>
<p><code>-debugscript</code>, comme son nom l'indique, fourni un nom de script pour le debugger qui sera <strong>exécuté au démarrage</strong>. Juste après le lancement de l'émulateur, le fichier binaire est donc injecté <strong>sans manipulation</strong>.</p>
<p>D'accord, mais il faut encore taper la commande <code>CALL &"7000"</code> pour lancer le programme, et avec la disposition d'un clavier qui change entre la machine émulée et la machine hôte, personnellement, <strong>je trouve cela fastidieux</strong> (surtout pour la touche ", qui est une touche morte dans ma configuration par défaut).</p>
<p>Pas de problème :</p>
<p><code>mame64 vg5k -ramsize 48k -window -nomax -debug -debugscript debug.txt -autoboot_command 'CALL &"7000"\n' -autoboot_delay 2</code></p>
<ul>
<li><code>-autoboot_command</code> indique la <strong>chaîne</strong> à passer à l'émulateur comme si les <strong>touches</strong> étaient <strong>appuyées</strong>,</li>
<li><code>-autoboot_delay</code> pose un <strong>délai</strong> de 2 secondes avant de lancer les commandes, ce qui laisse le temps au script de s'exécuter.</li>
</ul>
<p>À vrai dire, le délai de 2 secondes est la valeur par défaut, mais l'idée était de montrer la commande, pour pouvoir ajuster le délai.</p>
<p>Là, ça commence à être pas mal.</p>
<h4>Mais...</h4>
<p>Mais il y a <strong>deux choses</strong> encore qui me <strong>chiffonnent</strong>. Tout d'abord, la fenêtre du debuggeur se place au-dessus de ma fenêtre d'émulation, et je dois <strong>systématiquement la bouger</strong> pour voir le résultat. Il y a bien la possibilité d'utiliser la commande <code>-debugger none</code>, mais alors le script n'est plus exécuté.</p>
<p>Il y a la possibilité d'utiliser un debuggeur qui utilise la bibliothèque <code>imgui</code> et qui s'affiche dans la fenêtre d'émulation. Je vous suggère alors de passer <code>-nomax</code> en <code>-max</code>. Possibilité aussi pour le debugger par défaut (mais imgui est semi-transparent, le debugger par défaut opaque).</p>
<p><code>mame64 vg5k -ramsize 48k -window -max -debug -debugscript debug.txt -autoboot_command 'CALL &"7000"\n' -autoboot_delay 2 -debugger imgui -video bgfx</code></p>
<p>Cela donne déjà <strong>de quoi s'amuser</strong> et <strong>automatiser</strong> la chaîne de développement. Il y a cependant moyen d'aller encore plus loin avec <strong>MAME</strong>. Ça sera pour la prochaine fois.</p>
<p><img alt="Debugger utilisant ImGUI sur MAME" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201801/VG5000-Debug-imgui.jpeg"></p>Travailler sur émulateur et dump de la ROM VG5000µ2018-01-01T00:00:00+01:002018-01-01T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2018-01-01:/travailler-sur-emulateur-et-dump-de-la-rom-vg5000m.html<p><a href="https://www.triceraprog.fr/des-outils-pour-programmer-en-assembleur-sur-vg5000m.html">Précédemment</a>, j'ai commencé à parler d'une chaîne d'assemblage pour développer un programme depuis un <strong>ordinateur actuel</strong> vers une <strong>machine ancienne</strong>, le VG5000µ qui est l'ordinateur qui nous suit depuis le début de ces articles.</p>
<p>Un des avantages d'utiliser une machine de développement <strong>différente</strong> de la machine cible est de ne …</p><p><a href="https://www.triceraprog.fr/des-outils-pour-programmer-en-assembleur-sur-vg5000m.html">Précédemment</a>, j'ai commencé à parler d'une chaîne d'assemblage pour développer un programme depuis un <strong>ordinateur actuel</strong> vers une <strong>machine ancienne</strong>, le VG5000µ qui est l'ordinateur qui nous suit depuis le début de ces articles.</p>
<p>Un des avantages d'utiliser une machine de développement <strong>différente</strong> de la machine cible est de ne pas souffrir des erreurs, plantages et reboot provoquées par des <strong>erreurs</strong>. Imaginez, développer sur la machine que l'on programme elle-même, à une époque où le <strong>système d'exploitation</strong> ne protège rien du tout : une erreur, un reboot et si vous n'avez pas sauvé, <strong>il faut recommencer</strong>. Et si vous avez sauvé, les temps de chargements et de sauvegardes sont <strong>assez long</strong>, surtout sur une machine qui n'a pour mémoire externe qu'un <strong>magnétophone à cassettes</strong> !</p>
<p>Bref, développer sur une <strong>machine annexe</strong> et envoyer sur la machine cible, c'est <strong>plus simple</strong>, cela permet de profiter d'outils modernes mais... <strong>ça reste lent</strong> dans la plupart des cas. En tout cas sur une machine comme le VG5000µ qui n'a pas eu de développement spécifique qui pourrait injecter du code depuis une autre machine (pour le moment tout du moins, car c'est possible et j'ai cru comprendre qu'une carte était à l'étude).</p>
<p>Viens la solution de l'émulation. Un <strong>émulateur</strong> est un logiciel qui va reproduire le fonctionnement de la machine cible. Étant énormément plus lente que la machine hôte, la machine cible pourra être émulée de manière tout à fait satisfaisante. Un émulateur pourra aussi facilement et rapidement <strong>charger un programme</strong> depuis la machine hôte pour la mise au point, réduisant la fréquence d'essai sur machine véritable.</p>
<p>Mais pour <strong>utiliser</strong> un émulateur, dans la majorité des cas, il faut <strong>fournir la ROM</strong>, c'est-à-dire le programme résident de la machine. Ce n'est pas nécessaire pour <a href="http://dcvg5k.free.fr/">DCVG5K</a>, l'émulateur développé par Daniel Coulom. Par contre, c'est nécessaire pour l'utilisation de <a href="http://mamedev.org/">MAME</a> (plus exactement sa sous-partie MESS, qui émule les ordinateurs). De manière générale, vous êtes supposés avoir une copie de votre <strong>propre version de la ROM</strong>. Passons sur la partie un peu ridicule de cette restriction puisque le fichier que vous allez obtenir sera en tout point identique à celui obtenu par extraction depuis une autre machine...</p>
<p>Toujours est-il qu'il est très simple de copier (dumper) le contenu de la ROM d'une machine de ce type, puisqu'elle est visible et complètement adressable par l'utilisateur.</p>
<h4>La méthode</h4>
<p>Le VG5000µ doit être <strong>branché</strong>, bien entendu, et <strong>relié</strong> à une carte d'acquisition audio. Cela peut être tout simplement <strong>l'entrée son de votre ordinateur</strong>, certains utilisent des enregistreurs séparés.</p>
<p>L'idée est donc de sauver le contenu de la <strong>ROM</strong>, tel quel, dans un fichier. Rien de plus simple :</p>
<div class="highlight"><pre><span></span><code><span class="vg">CSAVEM</span><span class="s2">"ROM"</span><span class="p">,</span><span class="il">0</span><span class="p">,</span><span class="il">16384</span>
</code></pre></div>
<p><code>CSAVEM</code> est la commande pour sauver un fichier depuis la <strong>M</strong>émoire. Le nom du fichier est entre guillemets. <code>0</code> est l'adresse de départ et 16384 la taille en octets de la plage à sauvegarder. La ROM faisant 16 kio, la sauvegarde contiendra l'intégralité de la <strong>ROM</strong>.</p>
<p>Avant d'appuyer sur la touche <code>ENTRÉE</code> du VG5000µ, il faudra démarrer l'enregistrement sur l'ordinateur. J'utilise <a href="http://audacity.fr/"><code>Audacity</code></a> pour cela. Un logiciel libre de manipulation audio.</p>
<p>Assurez-vous au niveau du niveau d'entrée que le signal sature, cela sera plus facile pour le décodage. N'oublions pas qu'on est-ici dans un codage de <strong>signal numérique</strong>, pas dans de l'écoutable, ce qui est important, c'est que les signaux soient <strong>distinguables</strong>.</p>
<p>L'opération est assez longue, environ 2 minutes 30.</p>
<p><img alt="Signal audio d'un enregistrement sur K7" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201712/RomSignalAudio.jpeg"></p>
<p>Une fois le <strong>signal enregistré</strong>, il faut l'exporter en format <code>WAV</code> puis le décoder. Comment décoder un signal en sortie de VG5000µ sera l'objet d'un futur article ou je décrirai le programme que j'utilise (et que je publierai sur GitHub). En attendant, l'outil <a href="http://dcvg5k.free.fr/">DCToolBox</a> de Daniel Coulom fonctionne bien, et se lance tout à fait avec Wine sous Ubuntu si vous n'êtes pas sous Windows.</p>
<p>Avec mon outil, le contenu binaire intéressant est extrait simplement.</p>
<p>Avec DCToolBox, il vous faudra transformer le <code>WAV</code> en format <code>K7</code> et en extraire les informations. Le format <code>K7</code> est extrêmement simple, il contient l'ensemble des <strong>données encodées</strong> dans le <strong>signal numérique</strong> produit par la machine. Tout d'abord les 32 octets d'<strong>en-tête</strong>, qui ne nous intéressent pas ici, puis 10 octets d'amorce du contenu sauvé, puis le <strong>contenu</strong> lui-même. La taille, nous la connaissons, est de 16384 octets.</p>
<p>Il faut donc aller chercher les 16384 octets à partir du 42ième, ce qui s'écrit très simplement dans un <strong>shell Unix</strong> :</p>
<div class="highlight"><pre><span></span><code>tail -c+43 dump.k7 <span class="p">|</span> head -c <span class="m">16384</span> > dump.rom
</code></pre></div>
<p><code>dump.k7</code> étant le nom du fichier au format <code>K7</code> et <code>dump.rom</code> le fichier résultat.</p>
<p>Si vous n'avez pas accès à un shell Unix... je suis preneur d'une méthode simple pour l'ajouter à l'article.</p>
<h4>Et voilà !</h4>
<p>Vous avez à présent une extraction de la <strong>ROM</strong> d'une machine <strong>VG5000µ</strong>, utilisable par les émulateurs la nécessitant.</p>
<p><img alt="Sauver la ROM sur K7" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201712/DumpRomVG5k.jpg"></p>Joyeuses fêtes en haute résolution2017-12-30T00:00:00+01:002017-12-30T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-12-30:/joyeuses-fetes-en-haute-resolution.html<p>Puisque la fin de l'année approche et que les fêtes dites de fin d'années ainsi que le nouvel an sont d'actualités, j'ai ressorti mon convertisseur d'image vers le <strong>VG5000µ</strong> publié <a href="https://github.com/Triceraprog/ImgToVG5000">ici</a> et décrit <a href="https://www.triceraprog.fr/vg5000m-haute-resolution.html">ici</a> pour une image « haute résolution » sur la machine.</p>
<p>L'astuce pour que la compression passe bien a …</p><p>Puisque la fin de l'année approche et que les fêtes dites de fin d'années ainsi que le nouvel an sont d'actualités, j'ai ressorti mon convertisseur d'image vers le <strong>VG5000µ</strong> publié <a href="https://github.com/Triceraprog/ImgToVG5000">ici</a> et décrit <a href="https://www.triceraprog.fr/vg5000m-haute-resolution.html">ici</a> pour une image « haute résolution » sur la machine.</p>
<p>L'astuce pour que la compression passe bien a été de positionner le maximum de motifs identiques sur une grille. Pour les dates et l'adresse du site, j'ai ajouté dans le programme <strong>BASIC</strong> à la main l'affichage en texte.</p>
<div class="highlight"><pre><span></span><code><span class="nl">91</span><span class="w"> </span><span class="vg">J$</span><span class="o">=</span><span class="s2">"22001177--22001188"</span>
<span class="nl">92</span><span class="w"> </span><span class="nl">CURSORX15:CURSORY17:</span><span class="vg">TX6</span><span class="p">,</span><span class="il">3</span><span class="o">:</span><span class="vg">PRINTJ$</span>
<span class="nl">93</span><span class="w"> </span><span class="nl">CURSORX15:CURSORY18:</span><span class="vg">TX6</span><span class="p">,</span><span class="il">3</span><span class="o">:</span><span class="vg">PRINTJ$</span>
<span class="nl">95</span><span class="w"> </span><span class="nl">CURSORX23:CURSORY23:</span><span class="vg">TX6</span><span class="p">,</span><span class="il">0</span><span class="o">:</span><span class="kr">PRINT</span><span class="s2">"TRICERAPROG.FR"</span>
</code></pre></div>
<p>On y voit que pour écrire en double hauteur, il faut doubler l'affichage sur la ligne suivante. Et pour écrire en largeur, il faut doubler les lettres à afficher. Le tout en précisant <strong>3</strong> en second paramètre de l'instruction <code>TX</code>.</p>
<p>Le dithering qu'effectue la conversion malheureusement n'a pas été des plus heureux sur l'un des <strong>e</strong>, ce qui fait que la palette aurait pu être plus petite qu'elle ne l'est.</p>
<p>Voici l'image tramée :</p>
<p><img alt="L'image tramées" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201712/dithered_fetes.png"></p>
<p>Ainsi que la palette obtenue :</p>
<p><img alt="La palette" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201712/palette_fetes.png"></p>
<p>Avant de passer au résultat, un <a href="https://www.triceraprog.fr/files/201712/fetesvg5k17.zip">fichier zip</a> est disponible contenant le programme en format <code>.k7</code> ainsi qu'en <code>.wav</code>.</p>
<p><em>Petit détail</em> : sur un <strong>émulateur</strong>, l'écran reste noir pendant la construction de l'image, puis celle-ci <strong>apparaît</strong> lorsque dans la deuxième partie du programme, les caractères étendus sont construits.</p>
<p>Par contre, sur une <strong>machine réelle</strong>, la mémoire du processeur graphique n'étant <strong>pas effacée</strong> au démarrage, on commence par voir s'afficher des bandes verticales mêlées à un peu de bruit, puis le message apparaît lorsque les caractères étendus sont construits.</p>
<p>Dans les deux cas, après un <strong>redémarrage</strong> à chaud (le programme <strong>BASIC</strong> n'est pas effacé de la mémoire), l'affichage se fait progressivement, car les caractères étendus sont déjà construits.</p>
<p><img alt="Le résultat" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201712/JoyeusesFetes1718.jpeg"></p>Les différents VG5000µ2017-12-14T00:00:00+01:002017-12-14T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-12-14:/les-differents-vg5000m.html<p>Depuis le début de la création de ce site, la majorité des articles a été consacré à du développement sur VG5000µ. Les programmes sont testés sur du matériel réel, même si je les mets au points sur un ordinateur moderne. Les captures d'écrans sont aussi faites sur un vrai VG5000µ …</p><p>Depuis le début de la création de ce site, la majorité des articles a été consacré à du développement sur VG5000µ. Les programmes sont testés sur du matériel réel, même si je les mets au points sur un ordinateur moderne. Les captures d'écrans sont aussi faites sur un vrai VG5000µ, en sortie sur un moniteur récent grâce à un adaptateur Peritel vers HDMI.</p>
<p>Le VG5000µ a cette particularité d'exister sous différentes marques : Philipps, Schneider et Radiola. Plus de détails peuvent se trouver sur des sites plus détaillés dans l'histoire de cette machine, comme je l'indiquais dans un <a href="https://www.triceraprog.fr/vg5000m.html">billet en début d'année</a>.</p>
<p>En attendant la suite des articles sur les outils pour programmer cette machine en assembleur, voici une petite photo de famille.</p>
<p><img alt="Photo de 4 VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201712/QuatreVG5000-700.jpeg"></p>Des outils pour programmer en assembleur sur VG5000µ2017-11-16T00:00:00+01:002017-11-16T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-11-16:/des-outils-pour-programmer-en-assembleur-sur-vg5000m.html<p>La <a href="https://www.triceraprog.fr/du-langage-machine-a-lassembleur-sur-vg5000m.html">dernière fois</a>, j'avais évoqué le <strong>logiciel d'assemblage</strong>, ou <strong>assembleur</strong> comme étant le programme permettant de passer d'un programme sous sa forme <strong>source</strong>, c'est-à-dire écrite sous forme lisible par un humain, dans une forme <strong>binaire</strong>, qui sera plus exactement nommée forme <strong>objet</strong>. C'est cette forme qui pourra être exécutée par …</p><p>La <a href="https://www.triceraprog.fr/du-langage-machine-a-lassembleur-sur-vg5000m.html">dernière fois</a>, j'avais évoqué le <strong>logiciel d'assemblage</strong>, ou <strong>assembleur</strong> comme étant le programme permettant de passer d'un programme sous sa forme <strong>source</strong>, c'est-à-dire écrite sous forme lisible par un humain, dans une forme <strong>binaire</strong>, qui sera plus exactement nommée forme <strong>objet</strong>. C'est cette forme qui pourra être exécutée par la machine.</p>
<p>Pour programmer, quel que soit la forme que revêt cette programmation, on a besoin <strong>d'outils</strong>. Les outils primaires peuvent être une table de référence, des manuels, un stylo, des feuilles et beaucoup de patience. Rapidement, le besoin d'outils pour effectuer les tâches <strong>automatiques</strong> et <strong>répétitives</strong> qui forment la transformation d'un programme source en programme objet se fait sentir. Et quoi de mieux pour traier des tâches automatiques et répétitives de transformation qu'un... <strong>ordinateur</strong>.</p>
<p>Le problème de la poule et de l'œuf n'est pas le sujet ici. Le principal est qu'à l'heure de l'écriture de cet article, il y a des outils utilisables sur un ordinateur actuel pour travailler plus facilement sur les ordinateurs plus anciens qui m'intéressent ici.</p>
<p>Pour le processeur <strong>Z80</strong>, il y a le choix. Mais comme du travail a déjà été fourni pour le <strong>VG5000µ</strong>, autant s'en servir. Et c'est vers la suite d'outils <a href="https://github.com/z88dk/z88dk">z88dk</a> que je vais me tourner. Cette suite contient bien plus qu'un assembleur Z80 et j'en ferai un petit tour par la suite. Mais pour le moment, ce qui m'intéresse, c'est l'assembleur.</p>
<h4>Outil de listing</h4>
<p>Pour voir en quoi un assembleur peut nous aider, commençons par un <strong>test simple</strong>. Tout d'abord, j'écris le programme de l'article précédent dans un fichier <code>hello.asm</code>. Pour rappel, ce programme affichait <strong>Bonjour !</strong> à l'écran avec les commandes suivantes :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="n">ORG</span><span class="w"> </span><span class="err">$</span><span class="mh">7000</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">Spécification</span><span class="w"> </span><span class="n">de</span><span class="w"> </span><span class="n">l</span><span class="p">'</span><span class="n">adresse</span><span class="w"> </span><span class="n">mémoire</span><span class="w"> </span><span class="n">d</span><span class="p">'</span><span class="n">implentation</span><span class="w"></span>
<span class="w"> </span><span class="n">PUSH</span><span class="w"> </span><span class="n">AF</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">Sauvegarde</span><span class="w"> </span><span class="n">des</span><span class="w"> </span><span class="n">registres</span><span class="w"> </span><span class="n">sur</span><span class="w"> </span><span class="n">la</span><span class="w"> </span><span class="n">pile</span><span class="w"></span>
<span class="w"> </span><span class="n">PUSH</span><span class="w"> </span><span class="n">BC</span><span class="w"></span>
<span class="w"> </span><span class="n">PUSH</span><span class="w"> </span><span class="n">DE</span><span class="w"></span>
<span class="w"> </span><span class="n">PUSH</span><span class="w"> </span><span class="n">HL</span><span class="w"></span>
<span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">Le</span><span class="w"> </span><span class="n">cœur</span><span class="w"> </span><span class="n">du</span><span class="w"> </span><span class="n">programme</span><span class="w"> </span><span class="o">:</span><span class="w"></span>
<span class="w"> </span><span class="n">LD</span><span class="w"> </span><span class="n">HL</span><span class="p">,</span><span class="w"> </span><span class="n">chaine</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">Chargement</span><span class="w"> </span><span class="n">de</span><span class="w"> </span><span class="n">l</span><span class="p">'</span><span class="n">adresse</span><span class="w"> </span><span class="n">du</span><span class="w"> </span><span class="n">texte</span><span class="w"> </span><span class="n">dans</span><span class="w"> </span><span class="n">le</span><span class="w"> </span><span class="n">registre</span><span class="w"> </span><span class="n">HL</span><span class="w"></span>
<span class="w"> </span><span class="n">CALL</span><span class="w"> </span><span class="err">$</span><span class="mh">36</span><span class="n">AA</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">Puis</span><span class="w"> </span><span class="n">appel</span><span class="w"> </span><span class="n">de</span><span class="w"> </span><span class="n">la</span><span class="w"> </span><span class="n">routine</span><span class="w"> </span><span class="n">d</span><span class="p">'</span><span class="n">affichage</span><span class="w"> </span><span class="n">de</span><span class="w"> </span><span class="n">chaîne</span><span class="w"> </span><span class="n">de</span><span class="w"> </span><span class="n">caractères</span><span class="p">.</span><span class="w"></span>
<span class="w"> </span><span class="n">POP</span><span class="w"> </span><span class="n">HL</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">Restauration</span><span class="w"> </span><span class="n">des</span><span class="w"> </span><span class="n">registres</span><span class="w"> </span><span class="n">depuis</span><span class="w"> </span><span class="n">la</span><span class="w"> </span><span class="n">pile</span><span class="w"></span>
<span class="w"> </span><span class="n">POP</span><span class="w"> </span><span class="n">DE</span><span class="w"></span>
<span class="w"> </span><span class="n">POP</span><span class="w"> </span><span class="n">BC</span><span class="w"></span>
<span class="w"> </span><span class="n">POP</span><span class="w"> </span><span class="n">AF</span><span class="w"></span>
<span class="w"> </span><span class="n">RET</span><span class="w"> </span><span class="p">;</span><span class="w"> </span><span class="n">Retour</span><span class="w"> </span><span class="n">au</span><span class="w"> </span><span class="n">programme</span><span class="w"> </span><span class="n">appelant</span><span class="w"></span>
<span class="nl">chaine:</span><span class="w"></span>
<span class="w"> </span><span class="n">DEFB</span><span class="w"> </span><span class="s">"Bonjour !"</span><span class="p">,</span><span class="w"> </span><span class="mh">0</span><span class="w"></span>
</code></pre></div>
<p>Dans un terminal, j'exécute la commande suivante : <code>z80asm -l hello.asm</code> et j'obtiens très rapidement un nouveau fichier, <code>hello.lis</code> avec le contenu suivant :</p>
<div class="highlight"><pre><span></span><code><span class="mf">1</span><span class="w"> </span><span class="mf">0000</span><span class="w"> </span><span class="ow">OR</span><span class="n">G</span><span class="w"> </span><span class="err">$</span><span class="mf">7000</span><span class="w"></span>
<span class="mf">2</span><span class="w"> </span><span class="mf">0000</span><span class="w"> </span>
<span class="mf">3</span><span class="w"> </span><span class="mf">0000</span><span class="w"> </span><span class="n">F5</span><span class="w"> </span><span class="n">PUSH</span><span class="w"> </span><span class="n">AF</span><span class="w"></span>
<span class="mf">4</span><span class="w"> </span><span class="mf">0001</span><span class="w"> </span><span class="n">C5</span><span class="w"> </span><span class="n">PUSH</span><span class="w"> </span><span class="n">BC</span><span class="w"></span>
<span class="mf">5</span><span class="w"> </span><span class="mf">0002</span><span class="w"> </span><span class="n">D5</span><span class="w"> </span><span class="n">PUSH</span><span class="w"> </span><span class="n">DE</span><span class="w"></span>
<span class="mf">6</span><span class="w"> </span><span class="mf">0003</span><span class="w"> </span><span class="n">E5</span><span class="w"> </span><span class="n">PUSH</span><span class="w"> </span><span class="n">HL</span><span class="w"></span>
<span class="mf">7</span><span class="w"> </span><span class="mf">0004</span><span class="w"> </span>
<span class="mf">8</span><span class="w"> </span><span class="mf">0004</span><span class="w"> </span><span class="mf">21</span><span class="w"> </span><span class="mf">0</span><span class="n">F</span><span class="w"> </span><span class="mf">00</span><span class="w"> </span><span class="n">LD</span><span class="w"> </span><span class="n">HL</span><span class="p">,</span><span class="w"> </span><span class="n">chaine</span><span class="w"></span>
<span class="mf">9</span><span class="w"> </span><span class="mf">0007</span><span class="w"> </span><span class="n">CD</span><span class="w"> </span><span class="n">AA</span><span class="w"> </span><span class="mf">36</span><span class="w"> </span><span class="n">CALL</span><span class="w"> </span><span class="err">$</span><span class="mf">36</span><span class="n">AA</span><span class="w"></span>
<span class="mf">10</span><span class="w"> </span><span class="mf">000</span><span class="n">A</span><span class="w"> </span>
<span class="mf">11</span><span class="w"> </span><span class="mf">000</span><span class="n">A</span><span class="w"> </span><span class="n">E1</span><span class="w"> </span><span class="n">POP</span><span class="w"> </span><span class="n">HL</span><span class="w"></span>
<span class="mf">12</span><span class="w"> </span><span class="mf">000</span><span class="n">B</span><span class="w"> </span><span class="n">D1</span><span class="w"> </span><span class="n">POP</span><span class="w"> </span><span class="n">DE</span><span class="w"></span>
<span class="mf">13</span><span class="w"> </span><span class="mf">000</span><span class="n">C</span><span class="w"> </span><span class="n">C1</span><span class="w"> </span><span class="n">POP</span><span class="w"> </span><span class="n">BC</span><span class="w"></span>
<span class="mf">14</span><span class="w"> </span><span class="mf">000</span><span class="n">D</span><span class="w"> </span><span class="n">F1</span><span class="w"> </span><span class="n">POP</span><span class="w"> </span><span class="n">AF</span><span class="w"></span>
<span class="mf">15</span><span class="w"> </span><span class="mf">000</span><span class="n">E</span><span class="w"> </span>
<span class="mf">16</span><span class="w"> </span><span class="mf">000</span><span class="n">E</span><span class="w"> </span><span class="n">C9</span><span class="w"> </span><span class="n">RET</span><span class="w"></span>
<span class="mf">17</span><span class="w"> </span><span class="mf">000</span><span class="n">F</span><span class="w"> </span><span class="n">chaine</span><span class="p">:</span><span class="w"></span>
<span class="mf">18</span><span class="w"> </span><span class="mf">000</span><span class="n">F</span><span class="w"> </span><span class="mf">42</span><span class="w"> </span><span class="mf">6</span><span class="n">F</span><span class="w"> </span><span class="mf">6</span><span class="n">E</span><span class="w"> </span><span class="mf">6</span><span class="n">A</span><span class="w"> </span><span class="mf">6</span><span class="n">F</span><span class="w"> </span><span class="mf">75</span><span class="w"> </span><span class="mf">72</span><span class="w"> </span><span class="mf">20</span><span class="w"> </span><span class="mf">21</span><span class="w"> </span><span class="mf">00</span><span class="w"> </span>
<span class="w"> </span><span class="kd">DEF</span><span class="n">B</span><span class="w"> </span><span class="s">"Bonjour !"</span><span class="p">,</span><span class="w"> </span><span class="mf">0</span><span class="w"></span>
<span class="mf">19</span><span class="w"> </span><span class="mf">0019</span><span class="w"></span>
</code></pre></div>
<p>Ce fichier <strong>listing</strong> est constitué en première colonne d'un <strong>numéro de ligne</strong>, en seconde colonne d'une <strong>adresse mémoire</strong> relative au début du fichier, dans la colonne suivante de <strong>valeurs hexadécimales</strong> correspondant au résultat de l'assemblage, autrement dit au langage machine, puis enfin vient le contenu du <strong>programme source</strong>.</p>
<p>C'est donc un mélange de code source et d'une représentation lisible du code objet. Pratique pour suivre le résultat de la compilation simplement. Mais pas pratique pour essayer de lancer le programme sur la machine cible. Ce format est fait pour être <strong>lisible par un humain</strong>.</p>
<h4>Création d'un binaire</h4>
<p>Pour être adapté à la machine, par exemple en utilisant le programme de chargement en <strong>BASIC</strong> utilisé précédemment, j'ai besoin d'une forme qui ne contient que les <strong>octets indiqués dans le listing</strong>, sans tous les commentaires. J'ai besoin d'une seconde chose : que le programme soit logé à l'adresse mémoire que j'ai indiqué avec <code>ORG $7000</code>.</p>
<p>En effet, le listing ci-dessus a été produit avec des adresses commençant à l'adresse mémoire <code>0</code>. Sur un VG5000µ, comme dans tous les ordinateurs à base de Z80, c'est un emplacement de <strong>ROM</strong>, une zone dans laquelle je ne pourrai pas écrire ce programme. J'avais donc indiqué une autre zone mémoire précise pour implenter le programme.</p>
<p>Appartée : à cause des instructions que j'ai utilisé, c´est une indication obligatoire. L'instruction <code>LD HL, chaine</code> est une instruction qui prend une adresse de manière absolue. La chaîne de caractère doit donc se trouver à un endroit connu et absolu en mémoire. Mon programme n'est pas <strong>relogeable</strong>. Si je n'avais utilisé que des instructions adressant la mémoire de manière <strong>relative</strong>, c'est-à-dire toujours sous forme d'une distance à partir de l'instruction courante, alors le programme aurait été relogeable, et l'adresse d'implentation n'aurait pas été importante. Faire des programmes relogeables sur Z80 n'est pas si simple, et même si <code>z80asm</code> peut aider cela sort du cadre de l'article.</p>
<p>Voyons ce que produit cette autre commande : <code>z80asm -b hello.asm</code>. Cette fois, c'est une fichier <code>hello.bin</code> que j'obtiens. Et si j'essaie de l'ouvrir comme un fichier texte, je n'obtiens rien de franchement lisible, à part la chaîne de caractères <strong>Bonjour !</strong> à la fin.</p>
<p><img alt="Ouverture du fichier binaire comme du texte" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201711/20171116-OuvertureFichierBinaire.png"></p>
<p>Cependant, la taille du fichierest de <strong>25 octets</strong> et c'est exactement le nombre de données hexadécimales indiqué dans le listing obtenu plus tôt. C'est aussi le nombre d'octets dans le chargeur en <strong>BASIC</strong> utilisé à l'article précédent.</p>
<p>Si je regarde le contenu de ce fichier avec un outil qui permet d'afficher le contenu de fichier sous forme hexadécimale, j'obtiens bien la même chose.</p>
<div class="highlight"><pre><span></span><code><span class="mf">00000000</span><span class="w"> </span><span class="n">f5</span><span class="w"> </span><span class="n">c5</span><span class="w"> </span><span class="n">d5</span><span class="w"> </span><span class="n">e5</span><span class="w"> </span><span class="mf">21</span><span class="w"> </span><span class="mf">0</span><span class="n">f</span><span class="w"> </span><span class="mf">70</span><span class="w"> </span><span class="n">cd</span><span class="w"> </span><span class="n">aa</span><span class="w"> </span><span class="mf">36</span><span class="w"> </span><span class="n">e1</span><span class="w"> </span><span class="n">d1</span><span class="w"> </span><span class="n">c1</span><span class="w"> </span><span class="n">f1</span><span class="w"> </span><span class="n">c9</span><span class="w"> </span><span class="mf">42</span><span class="w"> </span><span class="err">|</span><span class="mf">....</span><span class="err">!</span><span class="mf">.</span><span class="n">p</span><span class="mf">..6.....</span><span class="n">B</span><span class="err">|</span><span class="w"></span>
<span class="mf">00000010</span><span class="w"> </span><span class="mf">6</span><span class="n">f</span><span class="w"> </span><span class="mf">6</span><span class="n">e</span><span class="w"> </span><span class="mf">6</span><span class="n">a</span><span class="w"> </span><span class="mf">6</span><span class="n">f</span><span class="w"> </span><span class="mf">75</span><span class="w"> </span><span class="mf">72</span><span class="w"> </span><span class="mf">20</span><span class="w"> </span><span class="mf">21</span><span class="w"> </span><span class="mf">00</span><span class="w"> </span><span class="err">|</span><span class="kr">on</span><span class="n">jour</span><span class="w"> </span><span class="err">!</span><span class="mf">.</span><span class="err">|</span><span class="w"></span>
</code></pre></div>
<p>Une utilisation possible est alors de transférer le contenu de ce fichier sous forme de <strong>DATA</strong> dans un chargeur en <strong>BASIC</strong> et voilà.</p>
<p>Par exemple, avec un petit script écrit en <strong>Python 3</strong>, cela pourrait donner ça :</p>
<div class="highlight"><pre><span></span><code><span class="n">basic</span> <span class="o">=</span> <span class="s2">"""10 S=&"7000"</span>
<span class="s2">20 READ A$</span>
<span class="s2">30 IF A$="FIN" THEN END</span>
<span class="s2">40 A$="&"+CHR$(34)+A$+CHR$(34):A=VAL(A$)</span>
<span class="s2">50 POKE S,A</span>
<span class="s2">60 S=S+1</span>
<span class="s2">70 GOTO 20</span>
<span class="s2">300 DATA </span><span class="si">{0}</span><span class="s2"></span>
<span class="s2">400 DATA FIN"""</span>
<span class="k">with</span> <span class="nb">open</span><span class="p">(</span><span class="s1">'hello.bin'</span><span class="p">,</span> <span class="s1">'rb'</span><span class="p">)</span> <span class="k">as</span> <span class="n">f</span><span class="p">:</span>
<span class="n">content</span> <span class="o">=</span> <span class="n">f</span><span class="o">.</span><span class="n">read</span><span class="p">()</span>
<span class="n">data</span> <span class="o">=</span> <span class="p">[</span><span class="nb">hex</span><span class="p">(</span><span class="n">d</span><span class="p">)[</span><span class="mi">2</span><span class="p">:]</span><span class="o">.</span><span class="n">upper</span><span class="p">()</span> <span class="k">for</span> <span class="n">d</span> <span class="ow">in</span> <span class="n">content</span><span class="p">]</span>
<span class="n">data</span> <span class="o">=</span> <span class="s2">","</span><span class="o">.</span><span class="n">join</span><span class="p">(</span><span class="n">data</span><span class="p">)</span>
<span class="nb">print</span><span class="p">(</span><span class="n">basic</span><span class="o">.</span><span class="n">format</span><span class="p">(</span><span class="n">data</span><span class="p">))</span>
</code></pre></div>
<p>C'est un script assez limité, sans protection au niveau de la taille du programme, ce qui pourrait générer un trop grand nombre de <code>DATA</code>, mais cela peut être utile pour, par exemple... écrire des articles avec des petits bouts de programmes à copier/coller dans un émulateur facilement.</p>
<p>Cependant, cela reste très <strong>manuel</strong> et ce n'est pas très satisfaisant. Nous verrons donc dans l'article suivant comment aller plus loin en utilisant les outils à disposition et se faciliter la vie.</p>Cross Chase, un défi multiplateforme2017-09-29T00:00:00+02:002017-09-29T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-09-29:/cross-chase-un-defi-multiplateforme.html<p>Des jeux vidéo récents sur des vieilles machines, il en sort régulièrement. Ainsi que des <strong>portages</strong> de jeux d'une de ces machines vers une autre de la même époque, qui n'avait pas eu sa version. Ou bien encore des versions améliorées.</p>
<p><strong><a href="https://github.com/Fabrizio-Caruso/CROSS-CHASE/releases">CROSS-CHASE</a></strong> est un jeu développé par <strong>Fabrizio Caruso</strong> dont …</p><p>Des jeux vidéo récents sur des vieilles machines, il en sort régulièrement. Ainsi que des <strong>portages</strong> de jeux d'une de ces machines vers une autre de la même époque, qui n'avait pas eu sa version. Ou bien encore des versions améliorées.</p>
<p><strong><a href="https://github.com/Fabrizio-Caruso/CROSS-CHASE/releases">CROSS-CHASE</a></strong> est un jeu développé par <strong>Fabrizio Caruso</strong> dont l'idée est un peu <strong>différente</strong>. Le défi est de développer un jeu vidéo sur <strong>l'ensemble des machines 8 bits</strong>. Le défi n'est pas terminé mais déjà bien avancé : Jupiter ACE, Apple II, Aquarius, Oric Atmos, C64, CPC, VIC20, VG5000µ,...</p>
<p>L'enjeu est donc de trouver un gameplay assez simple qui pourra être supporté par toutes ses machines, et programmer de manière à minimiser l'effort de <strong>portage</strong> sur chacun de ces hardwares.</p>
<p>Le joueur contrôle un personnage qui doit fuir des cercles en s'en débarassant par collision sur des <code>X</code>. C'est fun et rapide, jouable, dans l'esprit de l'époque. Il y a même plusieurs niveaux sur les version complètes.</p>
<p><img alt="CROSS-CHASE" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201709/20170919_CrossChase-03.jpg"></p>
<p>En effet, il y a des <strong>limites</strong> à la portabilité entre ces machines du fait principalement de leurs <strong>capacités mémoire</strong>. Le jeu est donc séparé en une <strong>version complète</strong> nécessitant plus de mémoire qu'une <strong>version minimale</strong>, consommant elle un maximum de 16 ko.</p>
<p>J'ai pu essayer sur du vrai matériel les version <strong>VG5000µ</strong> et <strong>Commodore 64</strong>. La version Commodore 64 dispose de <strong>caractères redéfinis</strong>, ce qui ajoute une touche sympathique. Elle n'est cependant jouable qu'au joystick, ce que j'ai trouvé assez dommage, car cela manque de la précision que l'on peut avoir au clavier. La version VG5000µ ne se joue elle qu'au clavier, mais la disposition des touches de cette machine ne rend pas le contrôle du jeu optimal.</p>
<p>N'empêche, je me suis <strong>bien amusé</strong>.</p>
<h4>Et la technique ?</h4>
<p>Ce site parlant programmation, voyons quels sont les outils utilisés par <strong>CROSS-CHASE</strong>.</p>
<p>Tout d'abord, la grande partie du jeu est programmée en <code>C</code>, compilé avec <code>CC64</code> pour les ordinateurs utilisant un CPU 6502 et <code>Z88DK</code> pour les ordinateurs utilisant un CPU Z80 (je ne fais là que paraphraser le contenu du README). Le choix du <code>C</code> permet un code objet compact, proche de la machine tout en restant portable. Le code ne sera pas optimal pour chaque machine, mais pour un portage sur autant de plateformes différentes, le choix est judicieux. On se rapproche ici des méthodes modernes de jeux développés sur plusieurs plateformes.</p>
<p>Dans le code VG5000µ, l'appel de l'affichage de caractère est néanmoins en <strong>assembleur</strong>, ainsi que le code pour faire disparaître le curseur. Le reste profite du <strong>support</strong> des compilateurs et de leurs <strong>bibliothèques</strong>.</p>
<p>Le reste de la stratégie de portage consiste à un mélange de <strong>fichiers spécifiques</strong> compilés uniquement pour les bonnes cibles et l'utilisation de <code>#define/#ifdef</code>. Le choix entre jeu minimal et complet est aussi fait par utilisation de <code>#define/#ifdef</code>.</p>
<p>Niveau compilation, depuis quelques jours un <code>Makefile</code> est apparu et à remplacé la multitudes des fichiers batch de compilation spécialisés par plateforme (qui ont été déplacées au chaud dans un répertoire <code>batches</code>). C'est bien plus pratique à lire.</p>
<p>Cerise sur le gâteau : <a href="https://github.com/Fabrizio-Caruso/CROSS-CHASE">les sources sont disponibles</a>, ce qui donne un bon exercice de lecture.</p>
<p>Je vous conseille d'aller voir ce projet et de jouer au jeu sur la machine de votre choix. Il devrait bien y avoir une version pour votre machine préférée !</p>
<p><img alt="CROSS-CHASE" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201709/20170919_CrossChase-01.jpg"></p>Lecture : Eight Bit Magazine2017-09-13T00:00:00+02:002017-09-13T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-09-13:/lecture-eight-bit-magazine.html<p>Il y a quelques mois, je tombais par hasard (autant que l'on puisse attribuer au hasard la manière dont est transmise l'information sur Internet) sur un Kickstarter pour le numéro trois d'un magazine nommé <strong>Eight bit</strong>.</p>
<p>La promesse est celle d'un magazine dont les sujets tournent autour des ordinateurs de …</p><p>Il y a quelques mois, je tombais par hasard (autant que l'on puisse attribuer au hasard la manière dont est transmise l'information sur Internet) sur un Kickstarter pour le numéro trois d'un magazine nommé <strong>Eight bit</strong>.</p>
<p>La promesse est celle d'un magazine dont les sujets tournent autour des ordinateurs de l'époque <strong>8 bits</strong>, s'adressant à la fois aux collectionneurs mais aussi aux <strong>utilisateurs</strong> de ces machines. Ceci sous la forme d'articles tournant autour d'Histoire et présentation de matériel, d'articles techniques et de tests.</p>
<p>La côté assumé et séduisant est celui d'être un vrai <strong>magazine papier</strong>, avec une mise en page à l'ancienne un peu modernisé. Une sorte de fanzine d'extrêmement bonne qualité avec un contenu fourni.</p>
<p>Après réception et lecture des <strong>trois</strong> premiers <strong>numéros</strong>, je peux dire que j'ai apprécié. Dans l'ensemble du moins : comme dans chaque magazine, il y a des parties que je survole un peu plus que les autres. Je ne suis pas très friand du format interview par exemple, dans ce magazine comme dans d'autres.</p>
<h4>Les numéros</h4>
<p>Dans le <strong>volume 1</strong>, l'article le plus intéressant pour moi est celle sur le <strong>galaksija</strong>, ordinateur yougoslave à l'architecture ouverte, diffusée par magazine et construite à la main par les utilisateurs de l'époque. Je ne connaissais pas du tout cette machine et l'histoire en est fascinante.</p>
<p>Le reste du magazine est aussi très intéressant, avec un tour au centre du Commodore 64, de la revue de matériel et une mini introduction à ce qu'est la programmation. La promesse est là. Un magazine des années 80 télé-transporté en 201x.</p>
<p>Pas de grosse découverte dans le <strong>numéro 2</strong>, mais des articles plaisants pour aller au cœur du ZX81 et comprendre en surface comment sont programmés les sprites sur Commodore 64.</p>
<p>Le <strong>troisième numéro</strong> présente le CPC 464, article qui ne contient pas vraiment de surprise pour moi. La très passionnante histoire des débuts du jeu d'aventure sur micro est mon point d'intérêt majeur.</p>
<h4>Conclusion</h4>
<p>« <a href="http://eightbitmagazine.com/">Eight Bit Magazine</a> » est une bonne découverte. C'est bien écrit et vogue entre nostalgie et information.</p>
<p>Du coup, j'attends le numéro 4.</p>
<p><img alt="3 numéros de Eight Bit Magazine" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201709/20170913-8bitmagazine.jpeg"></p>Du langage machine à l'assembleur sur VG5000µ2017-09-06T00:00:00+02:002017-09-06T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-09-06:/du-langage-machine-a-lassembleur-sur-vg5000m.html<p>Après cette pause estivale, reprenons là où l'on était restés. Dans <a href="https://www.triceraprog.fr/z80-et-vg5000m.html">l'article précédent</a>, je parlais du <strong>langage machine</strong>, une suite de signaux provoquant l'activité d'un processeur selon des directives précises.</p>
<p>Un inconvénient du langage machine, c'est qu'il est <strong>peu pratique</strong> à manipuler. Par exemple, sur un Z80, charger l'accumulateur (une …</p><p>Après cette pause estivale, reprenons là où l'on était restés. Dans <a href="https://www.triceraprog.fr/z80-et-vg5000m.html">l'article précédent</a>, je parlais du <strong>langage machine</strong>, une suite de signaux provoquant l'activité d'un processeur selon des directives précises.</p>
<p>Un inconvénient du langage machine, c'est qu'il est <strong>peu pratique</strong> à manipuler. Par exemple, sur un Z80, charger l'accumulateur (une mémoire spécifique interne au processeur) avec la valeur 1, je dois écrire : <code>00111110 00000001</code> en binaire, ou encore <code>3E 01</code> en système hexadécimal.</p>
<p>Écrire de cette manière n'est pas simple, prend beaucoup de <strong>temps</strong> avec un grand <strong>risque d'erreurs</strong>. Relire est encore pire. Programmez une machine de cette manière et vous ressentirez a priori rapidement le besoin de manipuler des éléments plus faciles à comprendre pour un Humain.</p>
<p>Et c'est ainsi que du langage machine on passe au <strong>langage d'assemblage</strong>, ou, par abus de langage, à <strong>l'assembleur</strong>.</p>
<h4>L'assembleur</h4>
<p>La première chose à savoir est que <strong>l'assembleur est un programme</strong>. Mais <strong>par extension</strong>, le <strong>langage</strong> qui est utilisé par ce programme est aussi appelé <strong>assembleur</strong>. Dans la suite, le mot servira principalement à désigner le langage en lui-même.</p>
<p>L'assembleur donc, est un langage à la syntaxe et à la grammaire extrêmement proche du langage machine. Chaque <strong>instruction</strong> en assembleur est <strong>directement traduisible</strong> dans sa version en langage machine et chaque instruction en langage machine a son écriture en assembleur.</p>
<p>En assembleur Z80, pour continuer sur le même exemple, la suite de signaux <code>00111110 00000001</code> qui charge 1 dans l'accumulateur s'écrit <code>LD A, 1</code> en assembleur. C'est toujours un peu cryptique, mais ça se lit bien <strong>plus facilement</strong>. LD pour « load », <em>charger</em> en anglais, est suivi de deux paramètres : la destination <code>A</code>, qui désigne l'accumulateur, et la valeur <code>1</code>.</p>
<p>Je précise bien assembleur Z80 car, tout comme le langage machine diffère d'un processeur à l'autre, l'assembleur, qui est une transcription lisible de ce langage machine est lui aussi <strong>spécifique</strong> à chaque <strong>processeur</strong>.</p>
<h4>Ça reste ardu non ?</h4>
<p>Les commandes du processeur Z80 couvrent le chargement de données dans sa mémoire interne (les registres), des calculs, des comparaisons,... Ces commandes étant très simples, il en faut souvent <strong>un certain nombre</strong> pour arriver au <strong>résultat</strong> voulu. Et la programmation est <strong>sans filet</strong> : le processeur exécutera à la lettre les commandes envoyées.</p>
<p>C'est pour cela que des langages de plus haut niveau <strong>d'abstraction</strong>, comme le <strong>BASIC</strong> (pour rester sur le VG5000µ), ont été créés. Le BASIC a des instructions simples à utiliser, son interpréteur se charge de vérifier les erreurs, de gérer la mémoire,... Les instructions de base du BASIC sont aussi les mêmes, pour ce qui est du coeur du langage, d'une machine à une autre, alors qu'il existe un assembleur différent dès que l'on change de processeur.</p>
<p>Toute la <strong>simplification</strong> des actions et la <strong>gestion d'erreurs</strong> a par contre <strong>un coût</strong>. De plus, le <strong>BASIC</strong> des machines telles que le VG5000µ est interprété, c'est-à-dire que les instructions et leurs paramètres sont décodées à chaque fois qu'elles sont exécutées. Cette interprétation amène au final à des instructions machines, mais très nombreuses.</p>
<p>Programmer en assembleur permet de s'affranchir de toute la lourdeur d'un langage interprété comme le BASIC, au prix d'un peu d'effort et d'attention. Et sur ce point, programmer une ancienne machine en 2017 aide beaucoup. Imaginez : programmer sur la machine elle-même à l'époque nécessitait de charger un programme d'édition, ce qui pouvait être long, et de repartir de zéro au moindre problème, puisqu'une erreur pouvait amener au <strong>plantage</strong> de la machine.</p>
<p>En 2017, avec émulation et outils d'assemblage qui tournent sur un ordinateur récent pour une vérification ultime sur la vraie machine, c'est beaucoup, <strong>beaucoup plus simple</strong>.</p>
<h4>La mission</h4>
<p>Le premier objectif en assembleur sur VG5000µ va être <strong>d'afficher une chaîne de caractères</strong> à l'écran. L'équivalent d'un <code>PRINT "Bonjour!"</code>. Mais avant cela, il me faut parler un peu de la <code>ROM</code> de la machine.</p>
<p>La <code>ROM</code> est une mémoire persistante, son contenu reste le même à chaque fois que la machine est en fonctionnement et il ne peut pas être modifié pendant l'exécution. On l'oppose souvent à la <code>RAM</code>, qui est une mémoire volatile, dont le contenu est perdu dès que la machine est éteinte.</p>
<p>La <code>ROM</code>, dans une machine comme le VG5000µ, contient l'ensemble des opérations nécessaires à son fonctionnement : comment dialoguer avec le processeur qui s'occupe de l'affichage, comment jouer un son, comment charger et sauver des données sur cassette,... La <code>ROM</code> contient aussi <strong>l'interpréteur BASIC</strong> et la gestion de <strong>l'interaction</strong> avec l'utilisateur.</p>
<p>Si son contenu n'est pas modifiable, il est par contre tout à fait possible de le lire, ou même d'en <strong>appeler des routines</strong>.</p>
<p>Dans le cadre de cette mission, je vais donc utiliser la routine d'affichage de chaîne de caractères. C'est exactement la même qui est utilisée <em>in fine</em> par l'instruction <code>PRINT</code>, mais je vais l'utiliser directement.</p>
<h4>La routine</h4>
<p>La routine d'affichage de chaîne de caractères est située à l'adresse $36AA (le préfixe <code>$</code> indique un nombre hexadécimal, en décimal, c'est l'équivalent de l'adresse <code>13994</code>).</p>
<p>Cette routine se charge en fait de <strong>l'impression</strong> à l'écran, sur une imprimante ou sur une cassette de sauvegarde. À défaut d'une action spéciale, l'affichage se fait <strong>à l'écran</strong>, je ne touche donc à rien.</p>
<p>La chaîne de caractères doit se terminer par le caractère de valeur <code>0</code> en mémoire.</p>
<p>La routine a besoin de connaître l'adresse en mémoire de la chaîne de caractères à afficher et pour celui utilise le registre <code>HL</code>. Tous les autres registres seront modifiés, il faudra donc en <strong>préserver le contenu</strong>.</p>
<p><em>Aparté sur les registres</em> : programmer en assembleur nécessite de connaître <strong>l'architecture</strong> logique <strong>du processeur</strong>. Une de ses composantes est que sa <strong>mémoire interne</strong> est découpée en <strong>registres</strong>. On peut imaginer les registres comme des conteneurs d'informations numériques.</p>
<p>Le programme va donc ressembler à quelque chose comme ça :</p>
<ul>
<li>Sauvegarde des registres</li>
<li>Charger le registre HL avec l'adresse de la chaîne de caractères</li>
<li>Appeler la routine <code>$36AA</code></li>
<li>Restaurer les registres</li>
<li>Revenir au programme appelant</li>
</ul>
<p><br/></p>
<p>La dernière étape de retour au programme appelant est nécessaire car l'appel depuis le <strong>BASIC</strong> se fera par l'instruction <code>CALL</code> qui appelle une routine en assembleur qui doit rendre la main afin que le programme <strong>BASIC</strong> puisse continuer.</p>
<h4>Écriture en assembleur</h4>
<p>Pour sauvegarder les registres, je vais utiliser la <strong>pile</strong>. La pile est une zone de mémoire dans laquelle on peut pousser des informations pour les récupérer plus tard, dans un ordre tel que la <strong>dernière information poussée</strong> est la <strong>première information récupérée</strong>. Vous pouvez imaginez une pile de feuilles de papiers sur laquelle vous pouvez ajouter une nouvelle feuille sur le dessus, ou bien retirer la feuille du dessus pour en lire le contenu.</p>
<p>Pousser tous les registres sur la pile peut se faire comme ceci :</p>
<div class="highlight"><pre><span></span><code>PUSH AF
PUSH BC
PUSH DE
PUSH HL
</code></pre></div>
<p>À la fin de ce petit programme, il faudra dépiler les valeurs de registre dans l'ordre inverse :</p>
<div class="highlight"><pre><span></span><code>POP HL
POP DE
POP BC
POP AF
</code></pre></div>
<p>Pour charger le registre <code>HL</code> avec l'adresse de la chaîne de caractère, on va demander de l'aide à <strong>l'assembleur</strong> (le programme, ici). Cette adresse en effet pourrait être fixée à la main, mais cela ne serait pas très flexible. Plutôt qu'une adresse, on utilise plutôt une étiquette (un label) comme une référence au contenu pointé.</p>
<div class="highlight"><pre><span></span><code> LD HL, chaine
(... plus loin ...)
chaine:
DEFB "Bonjour !", 0
</code></pre></div>
<p><code>DEFB</code> n'est pas réellement une instruction qui s'associe au langage machine. C'est une pseudo instruction, ou <strong>directive d'assemblage</strong>, qui indique à l'assembleur les valeurs numériques à placer directement en mémoire. Ici, on y place les valeurs numériques correspondantes aux caractères de "Bonjour !", suivi de la valeur 0, qui indique la fin de la chaîne. L'étiquette <code>chaîne</code> prendra automatiquement lors de l'assemblage l'adresse mémoire calculée.</p>
<p>Si <code>DEFB</code> est séparé de programme lui-même, c'est qu'une fois en mémoire, le processeur Z80 exécutera les instructions une à une, sans avoir de moyen de distinguer ce qui est une véritable instruction de ce qui est de la <strong>donnée</strong>. Si la chaîne de caractères se trouve sur son chemin d'exécution, elle sera prise pour une série d'instructions.</p>
<p><em>Pour les curieux</em> : « Bonjour! » pris comme une suite d'instructions donne ce qui suit et qui n'a pas beaucoup de sens</p>
<div class="highlight"><pre><span></span><code>LD B,D
LD L,A
LD L,(HL)
LD L,D
LD L,A
LD (HL),L
LD (HL),D
LD HL,$0000
</code></pre></div>
<p><br/></p>
<p>Reprenons... appeler la routine <code>$36AA</code> est simplement :</p>
<div class="highlight"><pre><span></span><code>CALL $36AA
</code></pre></div>
<p>Et revenir au programme appelant se fait avec l'instruction <code>RET</code>.</p>
<p>Dernière petite opération, il faut spécifier à l'assembleur l'adresse de <strong>début du programme</strong>, afin que les adresses des étiquettes puissent être calculées. Cela peut se faire, entre autre méthode, par l'utilisation de la directive d'assemblage <code>ORG</code>, comme <em>origin</em>.</p>
<p>Ce qui donne au final la séquence suivante :</p>
<div class="highlight"><pre><span></span><code> ORG $7000
PUSH AF
PUSH BC
PUSH DE
PUSH HL
LD HL, chaine
CALL $36AA
POP HL
POP DE
POP BC
POP AF
RET
chaine:
DEFB "Bonjour !", 0
</code></pre></div>
<p>Ce qui donne une fois passé à l'assembleur la <strong>séquence en langage machine</strong> suivante (en représentation hexadécimale) :</p>
<div class="highlight"><pre><span></span><code>E5 C5 F5 D5 CD AA 36 D1 F1 C1 E1 C9 42 6F 6E 6A 6F 75 72 21 00
</code></pre></div>
<h4>Lancement du programme</h4>
<p>Cette <strong>séquence</strong> en langage machine doit ensuite être <strong>implantée</strong> dans la <strong>mémoire</strong> et appelée depuis le <strong>BASIC</strong>. Une manière de faire est d'utiliser les instructions <code>DATA</code> et <code>POKE</code> du <strong>BASIC</strong>. <code>DATA</code> indique des séquences de valeurs à lire avec <code>READ</code>. <code>POKE</code> modifie une valeur de la mémoire à l'adresse spécifiée par le premier paramètre avec la valeur en deuxième paramètre.</p>
<p>Voici un exemple de chargeur de programme en langage machine écrit en BASIC sur VG5000µ.</p>
<div class="highlight"><pre><span></span><code><span class="mf">10</span><span class="w"> </span><span class="n">S</span><span class="o">=</span><span class="err">&</span><span class="s">"7000"</span><span class="w"></span>
<span class="mf">20</span><span class="w"> </span><span class="kr">READ</span><span class="w"> </span><span class="n">A$</span><span class="w"></span>
<span class="mf">30</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="n">A$</span><span class="o">=</span><span class="s">"FIN"</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">END</span><span class="w"></span>
<span class="mf">20</span><span class="w"> </span><span class="n">A$</span><span class="o">=</span><span class="s">"&"</span><span class="o">+</span><span class="nb">CHR$</span><span class="p">(</span><span class="mf">34</span><span class="p">)</span><span class="o">+</span><span class="n">A$</span><span class="o">+</span><span class="nb">CHR$</span><span class="p">(</span><span class="mf">34</span><span class="p">):</span><span class="n">A</span><span class="o">=</span><span class="nb">VAL</span><span class="p">(</span><span class="n">A$</span><span class="p">)</span><span class="w"></span>
<span class="mf">30</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="n">S</span><span class="p">,</span><span class="n">A</span><span class="w"></span>
<span class="mf">50</span><span class="w"> </span><span class="n">S</span><span class="o">=</span><span class="n">S</span><span class="o">+</span><span class="mf">1</span><span class="w"></span>
<span class="mf">60</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="mf">20</span><span class="w"></span>
<span class="mf">300</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="n">F5</span><span class="p">,</span><span class="n">C5</span><span class="p">,</span><span class="n">D5</span><span class="p">,</span><span class="n">E5</span><span class="p">,</span><span class="mf">21</span><span class="p">,</span><span class="mf">07</span><span class="p">,</span><span class="mf">70</span><span class="p">,</span><span class="n">CD</span><span class="p">,</span><span class="n">AA</span><span class="p">,</span><span class="mf">36</span><span class="p">,</span><span class="n">E1</span><span class="p">,</span><span class="n">D1</span><span class="p">,</span><span class="n">C1</span><span class="p">,</span><span class="n">F1</span><span class="p">,</span><span class="n">C9</span><span class="p">,</span><span class="mf">42</span><span class="p">,</span><span class="mf">6</span><span class="n">F</span><span class="p">,</span><span class="mf">6</span><span class="n">E</span><span class="p">,</span><span class="mf">6</span><span class="n">A</span><span class="p">,</span><span class="mf">6</span><span class="n">F</span><span class="p">,</span><span class="mf">75</span><span class="p">,</span><span class="mf">72</span><span class="p">,</span><span class="mf">20</span><span class="p">,</span><span class="mf">21</span><span class="p">,</span><span class="mf">00</span><span class="w"> </span>
<span class="mf">400</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="n">FIN</span><span class="w"></span>
</code></pre></div>
<p>Une fois ce programme entré et lancé avec <code>RUN</code>, la routine d'affichage peut être appelée avec <code>CALL &"7000"</code> et affichera <code>Bonjour!</code> à l'écran.</p>
<h4>Résultat</h4>
<p>Forcément, j'ai déployé <strong>beaucoup d'efforts</strong> pour afficher une simple chaîne de caractères. Il n'y a pas vraiment d'intérêt autre que <strong>pédagogique</strong>. Le programme en lui-même est long à cause de la sauvegarde et la restauration des registres, il faut l'accompagner d'un chargeur qui est plus grand que le programme en assembleur lui-même, et l'action n'étant appelée qu'une fois, il n'a pas d'intérêt en vitesse d'exécution.</p>
<p>Mais nous avons pu voir à quoi ressemblait la <strong>programmation en assembleur</strong>.</p>
<p><img alt="Bonjour!" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201709/VG5000-Output-List.jpeg">
<img alt="Bonjour!" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201709/VG5000-Output-Result.jpeg"></p>Z80 et VG5000µ2017-06-29T00:00:00+02:002017-06-29T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-06-29:/z80-et-vg5000m.html<p>Lors des <a href="https://www.triceraprog.fr/apres-le-point-la-ligne.html">articles</a> <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html">précédents</a> sur l'affichage, un résultat était net : <strong>c'est lent</strong> ! Extrêmement lent. Les magazines ou de livres consacrés à la programmation des machines personnelles des années 1980 affirmaient tous ceci : si vous voulez quelque chose de rapide, <strong>passez à l'assembleur</strong>.</p>
<p>Que signifie utiliser l'<strong>assembleur</strong>, et en quoi …</p><p>Lors des <a href="https://www.triceraprog.fr/apres-le-point-la-ligne.html">articles</a> <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html">précédents</a> sur l'affichage, un résultat était net : <strong>c'est lent</strong> ! Extrêmement lent. Les magazines ou de livres consacrés à la programmation des machines personnelles des années 1980 affirmaient tous ceci : si vous voulez quelque chose de rapide, <strong>passez à l'assembleur</strong>.</p>
<p>Que signifie utiliser l'<strong>assembleur</strong>, et en quoi c'est différent du <strong>BASIC</strong> ? Pourquoi est-ce que c'est plus rapide ? Était-ce vraiment la seule solution ? C'est ce que nous allons voir dans cet article et les suivants.</p>
<p>J'ai tenté plusieurs approches pour arriver au premier programme en assembleur dans une série d'articles. Et j'en suis arrivé à la conclusion qu'il n'y a pas moyen de passer outre <strong>quelques explications</strong> rapides des constituants de <strong>l'ordinateur</strong> et de leurs fonctionnements.</p>
<p>On va tout de même garder une vue large et schématique pour la plupart des composants. Pour le microprocesseur, un Z80 sur le VG5000µ, il faudra descendre un peu vers le fonctionnement de la puce, sans y sombrer.</p>
<p>En effet, programmer en assembleur, c'est <strong>programmer la machine au plus près</strong>, suivant ses caractéristiques, sans l'aide d'un langage de programmation offrant des abstractions.</p>
<h3>La machine, en très bref</h3>
<p>Le VG5000µ est composé de :</p>
<ul>
<li>Un <strong>microprocesseur Z80A</strong>, que je désignerai plus simplement comme Z80, qui est le nom générique pour cette série. Le microprocesseur est souvent désigné comme le cœur de l'ordinateur. Je préfère le désigner comme chef d'orchestre. C'est ce composant qui exécute les instructions du programme.</li>
<li>Un <strong>clavier</strong>, qui est une série d'interrupteurs agencés sous forme de matrice dont l'état peut-être lu grâce au contrôleur d'entrée/sortie du Z80.</li>
<li>Une <strong>mémoire</strong> au contenu figé (<strong>ROM</strong>) de 16ko, qui contient le programme de gestion de la machine.</li>
<li><strong>Trois mémoires</strong> vives (<strong>RAM</strong>) de 8ko, contenant toutes les données dynamiques de la machine. Deux de ces mémoires servent au fonctionnement général, la troisième est réservée au fonctionnement du processeur graphique.</li>
<li>Un <strong>processeur graphique</strong>, EF9394, qui gère l'affichage.</li>
<li>Une <strong>interface cassette</strong>.</li>
<li>Des <strong>BUS</strong> et des circuits logiques pour relier le tout.</li>
</ul>
<p><img alt="Schéma simplifié VG5000" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201706/20170629-SchemaVG5000.png"></p>
<h3>Le fonctionnement du CPU, en très bref</h3>
<p>Au démarrage de la machine, le Z80, alimenté, va entrer dans une boucle d'exécution jusqu'à son extinction :</p>
<ul>
<li><strong>Acquérir</strong> une instruction à l'adresse mémoire de l'instruction suivante (adresse 0 au démarrage).</li>
<li><strong>Changement</strong> de l'adresse de l'instruction suivante.</li>
<li><strong>Exécution</strong> de l'instruction acquise.</li>
</ul>
<p>À cela s'ajoute le traitement des <strong>interruptions</strong>. Lorsque le processeur reçoit une interruption, l'adresse mémoire de l'instruction suivante est forcée à une valeur spécifique. Une instruction dédiée permet de reprendre plus tard l'exécution là où elle avait été interrompue.</p>
<p><img alt="Boucle du CPU" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201706/20170629-BoucleCPU.png"></p>
<p>C'est une vue un simplifiée du fonctionnement du Z80, je <strong>laisse de côté les détails</strong>, comme le fait que l'acquisition d'une nouvelle instruction n'attend pas la fin de l'exécution de la précédente. D'un point de vue logique, en ce qui nous concerne, cela revient au même.</p>
<p>Voyons les étapes plus en détails.</p>
<h4>Acquisition de l'instruction</h4>
<p>Le Z80 est relié via <strong>trois BUS</strong> aux mémoires du VG5000µ, exception faite de la mémoire dédiée au processeur graphique. Un BUS, c'est tout simplement une <strong>série de conducteurs électriques</strong> (disons, des fils) qui permettent à différents composants de communiquer.</p>
<p>Pour acquérir son instruction, le Z80 va utiliser un premier <strong>BUS</strong>, celui <strong>d'adresse</strong>, pour signaler de quelle adresse mémoire l'instruction courante est voulue. Puis, via un second <strong>BUS</strong>, celui <strong>de contrôle</strong>, le Z80 signal qu'il veut lire le contenu à cette adresse. Après une brève attente, le troisième <strong>BUS</strong>, celui <strong>de données</strong>, pourra être lu et contiendra l'information recherchée, fournie par l'une des mémoires.</p>
<p>Une instruction complète pour un Z80 peut être composée d'un ou plusieurs octets. Dans le cas d'instructions à plusieurs octets, ceux-ci seront demandés les uns après les autres.</p>
<h4>Exécution de l'instruction</h4>
<p>Une fois l'instruction acquise, celle-ci est <strong>décodée</strong> et <strong>exécutée</strong>. Une instruction en langage machine Z80 est une suite de 1 à 4 octets. Chaque octets est une série de 8 informations binaires. Ces informations binaires, au niveau du microprocesseur, sont des <strong>informations électriques</strong> : est-ce que le signal est haut (1), ou bas (0) ?</p>
<p>La manière dont est architecturé le Z80 fait que ces signaux <strong>activent</strong> à leur tour d'autres signaux, qui provoquent le <strong>transfert d'information</strong> à travers les circuits internes au Z80. Ces circuits internes peuvent être des mémoires (<strong>les registres</strong>), des <strong>unités de calculs</strong> simples, des unités de calculs et <strong>de logique</strong>. Les signaux peuvent aussi atteindre les BUS pour émettre des informations ou en recevoir.</p>
<p>Ainsi, par <strong>exemple</strong>, l'instruction de retour d'un sous-routine, dont le nom est 'RET', se compose en langage machine des <strong>signaux suivants</strong> <code>11001001</code>. C'est une instruction sans argument qui déclenche une <strong>série de <em>micro-actions</em> </strong>. La première est de demander à la mémoire le <strong>contenu à l'adresse</strong> désignée par le <strong>registre SP</strong>, qui est un registre dédié à ce genre d'opérations, tout en augmentant la valeur de SP de 1. La seconde est identique : demander le contenu mémoire à l'adresse SP nouvellement modifiée tout en l'incrémentant de 1.</p>
<p>Le résultat de ces <strong>deux octets obtenus</strong> est placé dans le <strong>registre PC</strong>, qui est le registre qui désigne l'instruction suivante à obtenir.</p>
<h4>Ouf !</h4>
<p>C'est un peu lourd ? Voilà ce qu'il faut retenir : une instruction en <strong>langage machine</strong> déclenche une série de <strong>micro instructions</strong> influant les composants internes du microprocesseur.</p>
<p>C'est justement parce que tout ce détail est <strong>encombrant</strong> pour réfléchir à la résolution d'un problème que nous allons vite monter d'un cran en abstraction, et <strong>passer à l'assembleur</strong>, dans le prochain article.</p>Récréation 3D, VG5000µ version 22017-06-10T00:00:00+02:002017-06-10T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-06-10:/recreation-3d-vg5000m-version-2.html<p>Il y a deux mois, je publiais ma première récréation en 3D, une <a href="https://www.triceraprog.fr/recreation-3d.html">évocation d'un VG5000µ</a>. J'avais modélisé la machine un peu au jugé et au final, il y avait pas mal d'erreurs dans les dimensions. Un peu trop à mon goût. J'ai donc refait l'exercice, cette fois avec un …</p><p>Il y a deux mois, je publiais ma première récréation en 3D, une <a href="https://www.triceraprog.fr/recreation-3d.html">évocation d'un VG5000µ</a>. J'avais modélisé la machine un peu au jugé et au final, il y avait pas mal d'erreurs dans les dimensions. Un peu trop à mon goût. J'ai donc refait l'exercice, cette fois avec un VG5000µ et une règle à côté de moi.</p>
<p>Et voici le nouveau résultat, bien plus satisfaisant.</p>
<p><img alt="VG5000µ en 3D, version 2" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201706/vg5000-v2-3d-700.png"></p>VG5000µ, la maison en format k72017-06-03T00:00:00+02:002017-06-03T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-06-03:/vg5000m-la-maison-en-format-k7.html<p>Après la description de l'utilisation de l'<a href="https://www.triceraprog.fr/vg5000m-haute-resolution.html">affichage haute-définition</a> du VG5000µ, voici le fichier au <a href="https://www.triceraprog.fr/files/201706/maison_hd.k7.zip">format K7</a> et <a href="https://www.triceraprog.fr/files/201706/maison_hd.wav.zip">format WAV</a> qui vous permettra de lancer le programme sur votre propre VG5000µ ou un émulateur.</p>
<p>Au passage, le programme contient la version avec la maison sans l'inversion vidéo qui avait lieu …</p><p>Après la description de l'utilisation de l'<a href="https://www.triceraprog.fr/vg5000m-haute-resolution.html">affichage haute-définition</a> du VG5000µ, voici le fichier au <a href="https://www.triceraprog.fr/files/201706/maison_hd.k7.zip">format K7</a> et <a href="https://www.triceraprog.fr/files/201706/maison_hd.wav.zip">format WAV</a> qui vous permettra de lancer le programme sur votre propre VG5000µ ou un émulateur.</p>
<p>Au passage, le programme contient la version avec la maison sans l'inversion vidéo qui avait lieu dans l'article précédent.</p>VG5000µ, haute résolution2017-06-01T00:00:00+02:002017-06-01T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-06-01:/vg5000m-haute-resolution.html<p>Dans les articles précédents sur l'affichage du <strong>VG5000µ</strong>, je travaillais dans une résolution que je nomme « <strong>gros pixels</strong> », basée sur des <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">caractères semi-graphiques</a>. Grâce à l'implémentation d'une routine d'<a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-changements-de-reperes.html">affichage de pixel</a>, j'avais pu programmer un <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html">affichage de ligne</a> et un <a href="https://www.triceraprog.fr/trace-dun-cercle-en-basic-sur-vg5000m.html">affichage de cercle</a>.</p>
<p>Avec ce mode d'affichage, on obtient …</p><p>Dans les articles précédents sur l'affichage du <strong>VG5000µ</strong>, je travaillais dans une résolution que je nomme « <strong>gros pixels</strong> », basée sur des <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">caractères semi-graphiques</a>. Grâce à l'implémentation d'une routine d'<a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-changements-de-reperes.html">affichage de pixel</a>, j'avais pu programmer un <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html">affichage de ligne</a> et un <a href="https://www.triceraprog.fr/trace-dun-cercle-en-basic-sur-vg5000m.html">affichage de cercle</a>.</p>
<p>Avec ce mode d'affichage, on obtient une <strong>résolution</strong> de 80 pixels horizontalement par 75 pixels verticalement. Ce qui donne un total de <strong>6000 pixels</strong>. Cependant, le manuel d'utilisation de la machine indique une définition d'image de <strong>80 000 points</strong>. Plus de 13 fois plus. Existe-t-il une façon d'afficher des <strong>pixels plus petits</strong> et d'atteindre une haute (toute proportion gardée) définition ?</p>
<p><em>Petit rappel</em> : je ne m'occupe pour le moment que des capacités du VG5000µ offertes par le <strong>BASIC</strong>, et se mettant à la place d'une utilisation à l'époque, avec juste le manuel de base.</p>
<h4>Plus fin</h4>
<p>Pour atteindre une définition plus fine depuis le <strong>BASIC</strong>, il faut utiliser les informations du Chapitre 16, sur les jeux de caractères définissables par l'utilisateur.</p>
<p>Comme on l'a vu avec les caractères semi-graphique, l'écran du VG5000µ est organisé suivant une <strong>grille</strong> de 40 colonnes et 25 lignes, avec chaque emplacement <strong>pouvant afficher un caractère</strong>. Ce caractère peut être un chiffre, une lettre, de la ponctuation, ou bien un caractère semi-graphique. Mais il existe aussi des caractères <strong>laissés libres</strong> dont il est possible de définir le contenu.</p>
<p>C'est un peu comme si l'on pouvait définir une nouvelle <strong>police de caractères</strong>. Et la manière dont est configuré le VG5000µ laisse deux plages de 96 de ces caractères. Une plage pour le mode texte et une plage pour le mode graphique. Ce qui, si l'on met de côté les restrictions d'attributs d'affichage qui sont différents en texte (mode TX) et en graphique (mode GR), donne <strong>192 caractères programmables</strong>.</p>
<p>Ces caractères, comme les autres prédéfinis, sont des <strong>blocs</strong> de <strong>8 pixels</strong> de large par <strong>10 pixels</strong> de haut. Ce qui donne donc un total théorique de <strong>15360 pixels</strong> qui peuvent être simultanément affichés en haute définition à l'écran. Avec des contraintes sur les couleurs, sur les emplacements et les enchaînements des modes TX et GR.</p>
<p>Cela donne une théorie d'un peu moins de 20% de l'écran qui peut être en haute définition.</p>
<p>Par la <strong>réutilisation</strong> de caractères identiques à plusieurs emplacements et le <strong>mélange</strong> de ces caractères avec les caractères de bases texte et semi-graphique, il est possible de pousser la théorie. Au prix d'un <strong>effort considérable</strong>. Dessiner de belles images directement sur la machine est long et complexe.</p>
<h4>Définition des caractères</h4>
<p>L'objectif de cet article est d'obtenir sur le VG5000µ une image en haute définition affichée depuis le <strong>BASIC</strong>. Voyons donc déjà le principe de la programmation de ces caractères, qui est <strong>similaire</strong> à celui que l'on trouve sur <strong>d'autres machines</strong>, comme le CPC6128.</p>
<p>Chaque caractère est composé de 10 lignes de 8 pixels, chacun des pixels pouvant avoir deux états : allumé et éteints, ou plutôt, couleur d'encre et couleur de fond.</p>
<p>8 pixels avec deux états, c'est parfait pour tenir dans un <strong>octet</strong> de 8 bits. Un caractère sera donc défini par une série de 10 octets.</p>
<p>La manière de créer un caractère est de prendre du papier quadrillé, de colorier des cases, puis de transformer ces cases coloriées en 1, celles qui ne le sont pas en 0 pour obtenir les valeurs d'<strong>encodage</strong> du caractère.</p>
<p>Voici par exemple ce qui pourrait être une sorte de vaisseau pour un jeu.</p>
<p><img alt="Sprite" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201705/grille-sprite-ship.png"></p>
<p>Ce qui, donne l'encodage suivant.</p>
<table>
<thead>
<tr>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th></th>
<th>Codage en base 10</th>
</tr>
</thead>
<tbody>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>-></td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>-></td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>-></td>
<td>24</td>
</tr>
<tr>
<td>0</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td>0</td>
<td>-></td>
<td>126</td>
</tr>
<tr>
<td style="background-color: gray;">1</td>
<td>0</td>
<td style="background-color: gray;">1</td>
<td>0</td>
<td>0</td>
<td style="background-color: gray;">1</td>
<td>0</td>
<td style="background-color: gray;">1</td>
<td>-></td>
<td>165</td>
</tr>
<tr>
<td>0</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td>0</td>
<td>-></td>
<td>126</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td style="background-color: gray;">1</td>
<td style="background-color: gray;">1</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>-></td>
<td>24</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>-></td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>-></td>
<td>0</td>
</tr>
<tr>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>-></td>
<td>0</td>
</tr>
</tbody>
</table>
<p>La commande VG5000µ pour programmer les caractères demande de transformer cette série de nombre sous une écriture <strong>hexadécimal</strong>, puis, par d'écrire le tout sous une forme de <strong>chaîne de caractères</strong>.</p>
<p><em>Hexadécimal</em> : cette notation sort du cadre de l'article. Rapidement, il s'agit de compter dans une base de <em>16</em> chiffres, plutôt que les <em>10</em> habituels. Les 6 chiffres après le 9 sont notés <code>A</code>, <code>B</code>, <code>C</code>, <code>D</code>, <code>E</code> et <code>F</code>.</p>
<p>Dans notre exemple, la série de nombres (en base 10) de haut en bas <code>0, 0, 24, 126, 165, 126, 24, 0, 0, 0</code> se transforme en série de nombres (en base 16) <code>0, 0, 18, 7E, A5, 7E, 18, 0, 0, 0</code>. Comme un nombre hexadécimal de 0 à 255 ne prendra jamais plus de deux caractères, la commande demande à mettre tous les nombres sur deux caractères et assembler le tout. Cela donne au final : <code>0000187EA57E18000000</code>.</p>
<h4>Exemple plus poussé</h4>
<p>Calculer une grille pour <strong>chaque caractère</strong> voulu est <strong>fastidieux</strong>. Très fastidieux. C'est pourquoi pour essayer ce mode, je me suis servi d'un <strong>programme annexe</strong>, sur une machine moderne, afin de <strong>transformer</strong> une image en série de caractères définis.</p>
<p>Par simplicité, je me suis limité à la palette <strong>graphique étendue</strong>, et donc à 96 caractères maximum. J'ai pris l'image de la maison que j'avais tracée dans l'article sur le <a href="https://www.triceraprog.fr/tramage-basse-definition-pour-le-vg5000m.html">tramage</a>, je l'ai redimensionnée puis je l'ai passé à ma moulinette.</p>
<p><img alt="Maison d'origine, dithered low" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/maison-dithered-low.png"></p>
<p>Le programme <strong>BASIC</strong> en sorti est constitué de <strong>trois parties</strong>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">5</span><span class="w"> </span><span class="vg">INIT</span><span class="w"> </span><span class="il">6</span><span class="o">:</span><span class="vg">EG0</span><span class="p">,</span><span class="il">6</span>
<span class="nl">10</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">12</span>
<span class="nl">20</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">0</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">9</span>
<span class="nl">30</span><span class="w"> </span><span class="kr">READ</span><span class="w"> </span><span class="vg">I</span>
<span class="nl">40</span><span class="w"> </span><span class="vg">CURSORX</span><span class="w"> </span><span class="vg">X</span><span class="o">+</span><span class="il">14</span><span class="o">:</span><span class="vg">CURSORY</span><span class="w"> </span><span class="vg">Y</span><span class="o">+</span><span class="il">4</span>
<span class="nl">50</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="kr">CHR$</span><span class="p">(</span><span class="vg">I</span><span class="p">);</span>
<span class="nl">60</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">X</span>
<span class="nl">70</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">Y</span>
</code></pre></div>
<p>La <strong>première partie</strong> efface l'écran et pioche dans une liste de données les <strong>numéros</strong> de caractères à afficher à l'écran. Pour cela, <strong>deux boucles</strong> <code>FOR</code> imbriquées font parcourir au curseur la zone à afficher.</p>
<p>À noter <strong>ligne 50</strong> le point-virgule à la fin du <code>PRINT</code>, qui empêche l'instruction d'afficher le caractère de retour chariot, pas nécessaire car ce n'est pas un caractère affichable dans ce mode, mais qui évite certains calculs au <strong>BASIC</strong>.</p>
<p>L'instruction <code>READ</code> prend une à une les valeurs fournies par l'instruction <code>DATA</code> de la troisième partie du programme.</p>
<p>La <strong>seconde partie</strong> est une liste des définitions de tous les caractères.</p>
<div class="highlight"><pre><span></span><code><span class="nl">100</span><span class="w"> </span><span class="vg">SETEG</span><span class="w"> </span><span class="il">115</span><span class="p">,</span><span class="s2">"5CE854B4D868AAB855DB"</span>
<span class="nl">110</span><span class="w"> </span><span class="vg">SETEG</span><span class="w"> </span><span class="il">55</span><span class="p">,</span><span class="s2">"FAAFDA77DDB6ED77ADFB"</span>
<span class="nl">120</span><span class="w"> </span><span class="vg">SETEG</span><span class="w"> </span><span class="il">93</span><span class="p">,</span><span class="s2">"94224885284214429449"</span>
<span class="o">...</span>
<span class="o">...</span>
<span class="o">...</span>
<span class="nl">1020</span><span class="w"> </span><span class="vg">SETEG</span><span class="w"> </span><span class="il">103</span><span class="p">,</span><span class="s2">"44922449924491249249"</span>
<span class="nl">1030</span><span class="w"> </span><span class="vg">SETEG</span><span class="w"> </span><span class="il">119</span><span class="p">,</span><span class="s2">"08020000000000000000"</span>
<span class="nl">1040</span><span class="w"> </span><span class="vg">SETEG</span><span class="w"> </span><span class="il">39</span><span class="p">,</span><span class="s2">"00000000000000010103"</span>
</code></pre></div>
<p>Enfin, la <strong>troisième partie</strong> indique les caractères à afficher, lus par la première partie.</p>
<div class="highlight"><pre><span></span><code><span class="nl">1050</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">33</span><span class="p">,</span><span class="il">34</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">35</span><span class="p">,</span><span class="il">36</span><span class="p">,</span><span class="il">37</span>
<span class="nl">1060</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">38</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">39</span><span class="p">,</span><span class="il">40</span><span class="p">,</span><span class="il">41</span><span class="p">,</span><span class="il">42</span><span class="p">,</span><span class="il">43</span><span class="p">,</span><span class="il">44</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">45</span>
<span class="nl">1070</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">46</span><span class="p">,</span><span class="il">47</span><span class="p">,</span><span class="il">48</span><span class="p">,</span><span class="il">49</span><span class="p">,</span><span class="il">50</span><span class="p">,</span><span class="il">51</span><span class="p">,</span><span class="il">52</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">53</span><span class="p">,</span><span class="il">54</span><span class="p">,</span><span class="il">55</span><span class="p">,</span><span class="il">56</span><span class="p">,</span><span class="il">57</span><span class="p">,</span><span class="il">58</span><span class="p">,</span><span class="il">59</span>
<span class="nl">1080</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">60</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">61</span><span class="p">,</span><span class="il">62</span><span class="p">,</span><span class="il">63</span><span class="p">,</span><span class="il">64</span><span class="p">,</span><span class="il">65</span><span class="p">,</span><span class="il">66</span><span class="p">,</span><span class="il">67</span><span class="p">,</span><span class="il">68</span><span class="p">,</span><span class="il">69</span><span class="p">,</span><span class="il">70</span><span class="p">,</span><span class="il">71</span><span class="p">,</span><span class="il">72</span><span class="p">,</span><span class="il">73</span><span class="p">,</span><span class="il">74</span>
<span class="nl">1090</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">75</span><span class="p">,</span><span class="il">76</span><span class="p">,</span><span class="il">77</span><span class="p">,</span><span class="il">78</span><span class="p">,</span><span class="il">79</span><span class="p">,</span><span class="il">80</span><span class="p">,</span><span class="il">81</span><span class="p">,</span><span class="il">82</span><span class="p">,</span><span class="il">83</span><span class="p">,</span><span class="il">84</span><span class="p">,</span><span class="il">85</span><span class="p">,</span><span class="il">86</span><span class="p">,</span><span class="il">87</span><span class="p">,</span><span class="il">88</span><span class="p">,</span><span class="il">89</span><span class="p">,</span><span class="il">90</span>
<span class="nl">1100</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">91</span><span class="p">,</span><span class="il">92</span><span class="p">,</span><span class="il">93</span><span class="p">,</span><span class="il">94</span><span class="p">,</span><span class="il">95</span><span class="p">,</span><span class="il">96</span><span class="p">,</span><span class="il">97</span><span class="p">,</span><span class="il">98</span><span class="p">,</span><span class="il">99</span><span class="p">,</span><span class="il">100</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">101</span><span class="p">,</span><span class="il">102</span><span class="p">,</span><span class="il">103</span><span class="p">,</span><span class="il">104</span><span class="p">,</span><span class="il">105</span>
<span class="nl">1110</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">106</span><span class="p">,</span><span class="il">107</span><span class="p">,</span><span class="il">108</span><span class="p">,</span><span class="il">109</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">110</span><span class="p">,</span><span class="il">111</span><span class="p">,</span><span class="il">112</span><span class="p">,</span><span class="il">113</span><span class="p">,</span><span class="il">114</span><span class="p">,</span><span class="il">115</span><span class="p">,</span><span class="il">116</span><span class="p">,</span><span class="il">117</span><span class="p">,</span><span class="il">118</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span>
<span class="nl">1120</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">119</span><span class="p">,</span><span class="il">120</span><span class="p">,</span><span class="il">121</span><span class="p">,</span><span class="il">122</span><span class="p">,</span><span class="il">123</span><span class="p">,</span><span class="il">124</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">125</span><span class="p">,</span><span class="il">126</span><span class="p">,</span><span class="il">32</span><span class="p">,</span><span class="il">32</span>
<span class="nl">1130</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">32</span><span class="p">,</span><span class="il">32</span>
</code></pre></div>
<p>On y remarque la <strong>répétition</strong> du caractère <code>32</code>, qui est un caractère entièrement de la couleur du fond (et qui, avec un peu plus de travail, aurait pu être ignoré, afin de gagner un caractère programmable).</p>
<p>Le fait d'afficher d'abord les caractères puis de les définir offre au passage un <strong>petit effet</strong> intéressant d'affichage progressif.</p>
<h4>Ça consomme !</h4>
<p>Cette manière d'afficher permet du détail, mais, surtout en <strong>BASIC</strong>, elle consomme <strong>beaucoup de place</strong> en mémoire. Le programme pour afficher l'image en fin d'article prend un peu moins de <strong>4 ko</strong>, ce qui est énorme considérant les capacités de la machine. On pourrait stocker 3 images comme celle-ci pour la configuration mémoire <strong>sans extension</strong>, et puis c'est tout.</p>
<p>Le fait d'utiliser les commandes <strong>BASIC</strong> pour cela est en parti le problème. La place nécessaire à ces données est plutôt de l'ordre de <strong>1 ko</strong>, et le programme qui décode l'image peut-être plus léger. J'y reviendrai plus tard.</p>
<h4>Le résultat</h4>
<p>Voilà un exemple d'affichage en <strong>haute définition</strong> depuis le <strong>BASIC</strong> du <strong>VG5000µ</strong>. Cela conclue la série d'article d'exploration des capacités graphiques du VG5000µ en n'utilisant que les instructions décrites par le manuel d'utilisation. La prochaine fois que je reviendrai sur l'affichage, cela sera pour aller au-delà des limitations du <strong>BASIC</strong>.</p>
<p><img alt="Maison en haute résolution" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201705/vg5000-maison-hidef.jpeg"></p>Récréation 3D, Atari STe2017-05-25T00:00:00+02:002017-05-25T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-05-25:/recreation-3d-atari-ste.html<p>Après les machines précédemment évoquées en synthèse, toutes basées sur des processeurs 8 bits, voici ma première recréation de l'époque 16 bits, un Atari STe. Dans ma chronologie de programmeur, le 512 STe (gonflé en mémoire) a remplacé l'<a href="https://www.triceraprog.fr/recreation-3d-amstrad-cpc-6128.html">Amstrad CPC 6128</a>. Un plutôt gros changement.</p>
<p>Côté musique, l'Atari était …</p><p>Après les machines précédemment évoquées en synthèse, toutes basées sur des processeurs 8 bits, voici ma première recréation de l'époque 16 bits, un Atari STe. Dans ma chronologie de programmeur, le 512 STe (gonflé en mémoire) a remplacé l'<a href="https://www.triceraprog.fr/recreation-3d-amstrad-cpc-6128.html">Amstrad CPC 6128</a>. Un plutôt gros changement.</p>
<p>Côté musique, l'Atari était relié à un synthétiseur et j'ai pu découvrir les joies de la programmation MIDI et écrire un petit séquenceur à usage personnel (et très limité).</p>
<p><img alt="Atari STe" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201705/atari-st-3d-500.png"></p>Tracé d'un cercle en BASIC sur VG5000µ2017-05-17T00:00:00+02:002017-05-17T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-05-17:/trace-dun-cercle-en-basic-sur-vg5000m.html<p>Maintenant que nous avons un algorithme pour <a href="https://www.triceraprog.fr/vg5000m-simplification-du-trace-du-cercle.html">tracer un cercle</a> ainsi que le moyen d'afficher un <a href="https://www.triceraprog.fr/apres-le-point-la-ligne.html">gros pixel</a> à l'écran, nous voilà prêts pour traduire tout cela en <strong>BASIC</strong>, comme cela a été fait auparavant avec le <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html">segment de droite</a>.</p>
<p>Pour rappel, voici l'implémentation en <strong>BASIC</strong> d'affichage d'un point à …</p><p>Maintenant que nous avons un algorithme pour <a href="https://www.triceraprog.fr/vg5000m-simplification-du-trace-du-cercle.html">tracer un cercle</a> ainsi que le moyen d'afficher un <a href="https://www.triceraprog.fr/apres-le-point-la-ligne.html">gros pixel</a> à l'écran, nous voilà prêts pour traduire tout cela en <strong>BASIC</strong>, comme cela a été fait auparavant avec le <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html">segment de droite</a>.</p>
<p>Pour rappel, voici l'implémentation en <strong>BASIC</strong> d'affichage d'un point à l'écran.</p>
<div class="highlight"><pre><span></span><code><span class="nl">100</span><span class="w"> </span><span class="c1">REM AFFICHE UN POINT DANS L'ESPACE SEMI-GRAPHIQUE</span>
<span class="nl">110</span><span class="w"> </span><span class="c1">REM X CONTIENT L'ABSCISSE ENTRE 0 et 79</span>
<span class="nl">120</span><span class="w"> </span><span class="c1">REM Y CONTIENT L'ABSCISSE ENTRE 0 et 74</span>
<span class="nl">130</span><span class="w"> </span><span class="vg">ZX</span><span class="o">=</span><span class="kr">INT</span><span class="p">(</span><span class="vg">X</span><span class="o">/</span><span class="il">2</span><span class="p">)</span><span class="o">:</span><span class="vg">ZY</span><span class="o">=</span><span class="kr">INT</span><span class="p">(</span><span class="vg">Y</span><span class="o">/</span><span class="il">3</span><span class="p">)</span>
<span class="nl">140</span><span class="w"> </span><span class="vg">RX</span><span class="o">=</span><span class="vg">X</span><span class="o">-</span><span class="vg">ZX</span><span class="o">*</span><span class="il">2</span><span class="o">:</span><span class="vg">RY</span><span class="o">=</span><span class="vg">Y</span><span class="o">-</span><span class="vg">ZY</span><span class="o">*</span><span class="il">3</span>
<span class="nl">150</span><span class="w"> </span><span class="vg">CH</span><span class="o">=</span><span class="il">2</span><span class="o">^</span><span class="p">(</span><span class="vg">RY</span><span class="o">*</span><span class="il">2</span><span class="o">+</span><span class="vg">RX</span><span class="p">)</span>
<span class="nl">160</span><span class="w"> </span><span class="vg">DI</span><span class="o">=&</span><span class="s2">"4000"</span><span class="o">+</span><span class="vg">ZY</span><span class="o">*</span><span class="il">80</span><span class="o">+</span><span class="vg">ZX</span><span class="o">*</span><span class="il">2</span>
<span class="nl">170</span><span class="w"> </span><span class="vg">AT</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">DI</span><span class="o">+</span><span class="il">1</span><span class="p">)</span>
<span class="nl">200</span><span class="w"> </span><span class="vg">OL</span><span class="o">=</span><span class="il">64</span><span class="o">:</span><span class="kr">IF</span><span class="w"> </span><span class="p">(</span><span class="vg">AT</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">128</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="il">128</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">OL</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">DI</span><span class="p">)</span>
<span class="nl">220</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="p">(</span><span class="vg">OL</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">128</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="il">128</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">OL</span><span class="o">=</span><span class="il">64</span>
<span class="nl">230</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="vg">DI</span><span class="p">,</span><span class="vg">OL</span><span class="w"> </span><span class="ow">OR</span><span class="w"> </span><span class="vg">CH</span>
<span class="nl">240</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="vg">DI</span><span class="o">+</span><span class="il">1</span><span class="p">,</span><span class="il">224</span>
<span class="nl">250</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<p>L'implémentation du <strong>tracé de ligne</strong> utilisait les lignes 300 à 600. Je vais placer l'implémentation du <strong>cercle</strong> suivant l'algorithme de Bresenham à partir de la <strong>ligne 800</strong>.</p>
<p>Cela permettra d'avoir l'implémentation du tracé de segments de droite et de cercles <strong>dans le même programme</strong>.</p>
<p>Passons maintenant à l'implémentation de l'algorithme que je rappelle ici :</p>
<ul>
<li>initialiser cx et cy avec les coordonnées du centre du cercle, r avec son rayon</li>
<li>initialiser (x, y) à (0, r), c'est à dire le point au sommet du cercle.</li>
<li>initialiser M à $5 - r$</li>
<li>tant que x ≤ y<ul>
<li>tracer le pixel(x + cx, y + cy) et ses sept symétries.</li>
<li>calculer $m = (x+1)^2 + (y-0.5)^2 - r^2$</li>
<li>si m ≥ 0 alors $y \leftarrow y - 1$, $M \leftarrow M - 8y_{i+1}$</li>
<li>$x \leftarrow x + 1$, $M \leftarrow M + 8x_{i+1} + 4$</li>
</ul>
</li>
<li>fin</li>
</ul>
<p><img alt="Cercles sur VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201705/vg5000-circles.jpeg"></p>
<h4>Implémentation</h4>
<p>L'algorithme prend en entrée les coordonnées <code>x</code> et <code>y</code> du centre du cercle ainsi que son rayon. Nous aurons besoin d'utiliser les variables <code>X</code> et <code>Y</code> pour appeler l'affichage d'un point et pour rappel, cette routine utilise aussi les variables temporaires <code>ZX</code>, <code>ZY</code>, <code>RX</code>, <code>RY</code>, <code>CH</code>, <code>DI</code>, <code>AT</code> et <code>OL</code>.</p>
<p>Je choisi d'utiliser les variables <code>CX</code> et <code>CY</code> pour les coordonnées du centre du cercle, et <code>R</code> pour son rayon. Je documente ça dans des remarques directement dans le programme en <strong>BASIC</strong>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">800</span><span class="w"> </span><span class="c1">REM AFFICHE UN CERCLE (BRESENHAM)</span>
<span class="nl">810</span><span class="w"> </span><span class="c1">REM CX ET CY CONTIENNENT LES COORDONNEES DU CENTRE</span>
<span class="nl">820</span><span class="w"> </span><span class="c1">REM R CONTIENT LE RAYON</span>
</code></pre></div>
<p>J'ai besoin ensuite de variables pour les <strong>coordonnées courantes</strong> du point à afficher et qui vont évoluer avec l'algorithme. Je choisi <code>XX</code> et <code>YY</code>. Pour la valeur de <code>m</code>, je choisi la variable <code>MM</code>. J'initialise tout cela.</p>
<div class="highlight"><pre><span></span><code><span class="nl">830</span><span class="w"> </span><span class="vg">XX</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">YY</span><span class="o">=</span><span class="nl">R:</span><span class="vg">MM</span><span class="o">=</span><span class="il">5-4</span><span class="o">*</span><span class="vg">R</span>
</code></pre></div>
<p>Vient ensuite la répétition du bloc d'instruction exécutées tant que <code>x</code> ≤ <code>y</code>. Il n'y a pas d'instruction <strong>tant que</strong> en <strong>BASIC</strong> VG5000µ. Mais c'est facile à implémenter avec les moyens du bord. Un <strong>tant que</strong> c'est une condition qui si elle n'est pas valide sort, via un <code>GOTO</code> hors de la boucle et qui sinon exécute une suite d'instructions se terminant par un <code>GOTO</code> vers la condition.</p>
<p><em>Aparté <code>GOTO</code></em> : il est possible, si vous avez déjà programmé, que vous ayez entendu de l'utilisation de l'instruction <code>GOTO</code> comme de quelque chose à bannir. C'est en partie vraie et pourra être le sujet d'un futur billet. Ici, dans un <strong>BASIC</strong> aussi simpliste, on se permet de l'utiliser, c'est le plus évident.</p>
<p>Voici la structure du <strong>tant que</strong> :</p>
<div class="highlight"><pre><span></span><code><span class="nl">840</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">XX</span><span class="o">></span><span class="vg">YY</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">970</span>
<span class="p">[</span><span class="w"> </span><span class="vg">des</span><span class="w"> </span><span class="vg">instructions</span><span class="w"> </span><span class="p">]</span>
<span class="nl">960</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">840</span>
<span class="nl">970</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<p>En ligne <strong>970</strong>, le <code>RETURN</code> indique la fin de la routine d'affichage du cercle, qui sera donc appelée via l'instruction <code>GOSUB</code>, comme dans le cas des segments de droite.</p>
<p>Quant aux instructions, elles consistent tout d'abord à affiche le pixel et ses symétries :</p>
<div class="highlight"><pre><span></span><code><span class="nl">850</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">+</span><span class="nl">XX:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">+</span><span class="nl">YY:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">860</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">+</span><span class="nl">YY:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">+</span><span class="nl">XX:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">870</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">-</span><span class="nl">XX:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">+</span><span class="nl">YY:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">880</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">-</span><span class="nl">YY:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">+</span><span class="nl">XX:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">890</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">+</span><span class="nl">XX:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">-</span><span class="nl">YY:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">900</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">+</span><span class="nl">YY:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">-</span><span class="nl">XX:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">910</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">-</span><span class="nl">XX:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">-</span><span class="nl">YY:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">920</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">-</span><span class="nl">YY:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">-</span><span class="nl">XX:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
</code></pre></div>
<p>Puis la sélection du prochain pixel est effectuée en fonction de la valeur de <code>MM</code>, qui est ajustée, ainsi que les coordonnées <code>XX</code> et <code>YY</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">930</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">MM</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">YY</span><span class="o">=</span><span class="vg">YY</span><span class="il">-1</span><span class="o">:</span><span class="vg">MM</span><span class="o">=</span><span class="vg">MM</span><span class="il">-8</span><span class="o">*</span><span class="vg">YY</span>
<span class="nl">940</span><span class="w"> </span><span class="vg">XX</span><span class="o">=</span><span class="vg">XX</span><span class="o">+</span><span class="il">1</span>
<span class="nl">950</span><span class="w"> </span><span class="vg">MM</span><span class="o">=</span><span class="vg">MM</span><span class="o">+</span><span class="il">8</span><span class="o">*</span><span class="vg">XX</span><span class="o">+</span><span class="il">4</span>
</code></pre></div>
<p>Ce qui donne au final :</p>
<div class="highlight"><pre><span></span><code><span class="nl">800</span><span class="w"> </span><span class="c1">REM AFFICHE UN CERCLE (BRESENHAM)</span>
<span class="nl">810</span><span class="w"> </span><span class="c1">REM CX ET CY CONTIENNENT LES COORDONNEES DU CENTRE</span>
<span class="nl">820</span><span class="w"> </span><span class="c1">REM R CONTIENT LE RAYON</span>
<span class="nl">830</span><span class="w"> </span><span class="vg">XX</span><span class="o">=</span><span class="il">0</span><span class="o">:</span><span class="vg">YY</span><span class="o">=</span><span class="nl">R:</span><span class="vg">MM</span><span class="o">=</span><span class="il">5-4</span><span class="o">*</span><span class="vg">R</span>
<span class="nl">840</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">XX</span><span class="o">></span><span class="vg">YY</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">970</span>
<span class="nl">850</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">+</span><span class="nl">XX:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">+</span><span class="nl">YY:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">860</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">+</span><span class="nl">YY:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">+</span><span class="nl">XX:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">870</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">-</span><span class="nl">XX:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">+</span><span class="nl">YY:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">880</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">-</span><span class="nl">YY:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">+</span><span class="nl">XX:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">890</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">+</span><span class="nl">XX:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">-</span><span class="nl">YY:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">900</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">+</span><span class="nl">YY:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">-</span><span class="nl">XX:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">910</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">-</span><span class="nl">XX:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">-</span><span class="nl">YY:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">920</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">CX</span><span class="o">-</span><span class="nl">YY:</span><span class="vg">Y</span><span class="o">=</span><span class="vg">CY</span><span class="o">-</span><span class="nl">XX:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">930</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">MM</span><span class="w"> </span><span class="o">></span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">YY</span><span class="o">=</span><span class="vg">YY</span><span class="il">-1</span><span class="o">:</span><span class="vg">MM</span><span class="o">=</span><span class="vg">MM</span><span class="il">-8</span><span class="o">*</span><span class="vg">YY</span>
<span class="nl">940</span><span class="w"> </span><span class="vg">XX</span><span class="o">=</span><span class="vg">XX</span><span class="o">+</span><span class="il">1</span>
<span class="nl">950</span><span class="w"> </span><span class="vg">MM</span><span class="o">=</span><span class="vg">MM</span><span class="o">+</span><span class="il">8</span><span class="o">*</span><span class="vg">XX</span><span class="o">+</span><span class="il">4</span>
<span class="nl">960</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">840</span>
<span class="nl">970</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<p>C'est une implémentation <strong>très simple</strong>, d'un algorithme, une fois travaillé, très simple. À vrai dire, le tracé du pixel en lui même est plus complexe que le tracé du cercle. S'il vous manque la signification de certaines instructions, retournez voir l'implémentation du <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html">segment de droite</a>, qui les détail.</p>
<h4>Le résultat</h4>
<p>Pour appeler la routine de tracé de cercle et afficher quelque chose, j'ajoute :</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">1000</span>
<span class="nl">1000</span><span class="w"> </span><span class="vg">INIT</span>
<span class="nl">1010</span><span class="w"> </span><span class="vg">CX</span><span class="o">=</span><span class="il">30</span><span class="o">:</span><span class="vg">CY</span><span class="o">=</span><span class="il">20</span><span class="o">:</span><span class="vg">R</span><span class="o">=</span><span class="il">10</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">800:</span><span class="vg">DISPLAY</span>
<span class="nl">1020</span><span class="w"> </span><span class="vg">CX</span><span class="o">=</span><span class="il">40</span><span class="o">:</span><span class="vg">CY</span><span class="o">=</span><span class="il">40</span><span class="o">:</span><span class="vg">R</span><span class="o">=</span><span class="il">25</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">800:</span><span class="vg">DISPLAY</span>
<span class="nl">1060</span><span class="w"> </span><span class="kr">END</span>
</code></pre></div>
<p>Tout d'abord un saut vers la ligne <code>1000</code>, qui efface l'écran avec <code>INIT</code> puis appelle l'affichage de deux cercles. Cela donne l'image en début de cette article, avec les deux cercles.</p>
<h4>Mais c'est toujours très lent !</h4>
<p>Étant donné que l'algorithme se repose sur une implémentation d'affichage de pixel <strong>très lente</strong>, l'affichage du cercle est lui-même très lent. Il aura fallu plusieurs minutes avant de voir l'image de la fin de l'article, et définie par l'utilisation suivante :</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">1500</span>
<span class="nl">1500</span><span class="w"> </span><span class="vg">INIT</span>
<span class="nl">1510</span><span class="w"> </span><span class="vg">CX</span><span class="o">=</span><span class="il">40</span><span class="o">:</span><span class="vg">CY</span><span class="o">=</span><span class="il">30</span>
<span class="nl">1520</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">R</span><span class="o">=</span><span class="il">25</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">30</span>
<span class="nl">1530</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">600</span>
<span class="nl">1540</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">R</span>
<span class="nl">1550</span><span class="w"> </span><span class="vg">CX</span><span class="o">=</span><span class="il">50</span><span class="o">:</span><span class="vg">CY</span><span class="o">=</span><span class="il">20</span>
<span class="nl">1560</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">R</span><span class="o">=</span><span class="il">1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">4</span>
<span class="nl">1570</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">600</span>
<span class="nl">1580</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">R</span>
<span class="nl">1590</span><span class="w"> </span><span class="vg">CX</span><span class="o">=</span><span class="il">30</span><span class="o">:</span><span class="vg">CY</span><span class="o">=</span><span class="il">20</span>
<span class="nl">1600</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">R</span><span class="o">=</span><span class="il">1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">4</span>
<span class="nl">1610</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">600</span>
<span class="nl">1620</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">R</span>
</code></pre></div>
<p>Le moment d'optimiser tout ça arrive, mais il reste quelques explorations en <strong>BASIC</strong> à faire avant.</p>
<p><img alt="Tête sur VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201705/vg5000-head.jpeg"></p>VG5000µ, simplification du tracé du cercle2017-05-14T00:00:00+02:002017-05-14T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-05-14:/vg5000m-simplification-du-trace-du-cercle.html<p>Avec de passer à l'implémentation en <strong>BASIC</strong> du cercle décrit dans un article précédent, passons un peu par une phase de <strong>simplification</strong>. Cet article va contenir quelques lignes de mathématiques.</p>
<p>L'outil mathématique le plus complexe utilisé est celui des factorisations et développements de carrés. Les identités remarquables seront d'un grand …</p><p>Avec de passer à l'implémentation en <strong>BASIC</strong> du cercle décrit dans un article précédent, passons un peu par une phase de <strong>simplification</strong>. Cet article va contenir quelques lignes de mathématiques.</p>
<p>L'outil mathématique le plus complexe utilisé est celui des factorisations et développements de carrés. Les identités remarquables seront d'un grand secours. Niveau de fin de collège je crois.</p>
<p><strong>Pour rappel</strong>, voici l'algorithme de tracé de cercle de Bresenham :</p>
<ul>
<li>initialiser cx et cy avec les coordonnées du centre du cercle, r avec son rayon</li>
<li>initialiser (x, y) à (0, r), c'est à dire le point au sommet du cercle.</li>
<li>tant que x ≤ y<ul>
<li>tracer le pixel(x + cx, y + cy) et ses sept symétries.</li>
<li>calculer $m = (x+1)^2 + (y-0.5)^2 - r^2$</li>
<li>si m ≥ 0 alors $x \leftarrow x + 1$ et $y \leftarrow y - 1$</li>
<li>sinon $x \leftarrow x + 1$</li>
</ul>
</li>
<li>fin</li>
</ul>
<p>On pourrait l'implémenter tel quel. Mais je veux d'abord attirer votre attention sur un <strong>détail</strong> assez simple. Dans les deux cas de la condition, que <code>m</code> soit ou non supérieur à zéro, <code>x</code> est augmenté de 1. En effet, si vous vous souvenez, quoi qu'il arrive, on trace les pixels en prenant toujours le suivant sur la colonne à sa droite.</p>
<p>On pourrait donc <strong>réécrire</strong> :</p>
<ul>
<li>initialiser cx et cy avec les coordonnées du centre du cercle, r avec son rayon</li>
<li>initialiser (x, y) à (0, r), c'est à dire le point au sommet du cercle.</li>
<li>tant que x ≤ y<ul>
<li>tracer le pixel(x + cx, y + cy) et ses sept symétries.</li>
<li>calculer $m = (x+1)^2 + (y-0.5)^2 - r^2$</li>
<li>si m ≥ 0 alors $y \leftarrow y - 1$</li>
<li>$x \leftarrow x + 1$</li>
</ul>
</li>
<li>fin</li>
</ul>
<p>Cela aura le même résultat, et un peu moins d'instructions.</p>
<p><em>Une petite pause ici</em> : avec des langages compilés et des outils de compilation <strong>récents</strong>, il est très probable que ce genre de simplification soit fait automatiquement par le compilateur. Ce qui n'empêche pas d'écrire un code plus simple, mais sans aller jusqu'à simplifier au point que le lecteur humain ne comprenne plus rien. Ici, dans le monde <strong>BASIC</strong> interprété des années 1980, c'est une simplification intéressante que l'on doit faire à la main.</p>
<p>Cette simplification fait partie des <strong>optimisations</strong>. Avec moins d'instructions, le programme ira probablement plus vite. Dans notre cas ici, le routine d'affichage de pixel est tellement lente que l'effet est négligeable. Mais tout de même.</p>
<h4>Simplifions encore</h4>
<p>Un autre type d'<strong>optimisation</strong> peut venir d'une <strong>simplification des calculs</strong> à effectuer. Le <strong>BASIC</strong> du VG5000µ n'est pas très rapide en calculs et on demande ici dans la boucle de tracé sept opérations dont trois multiplications. Et les multiplications générique, c'est <strong>lent</strong>.</p>
<p>Plutôt que de calculer <code>m</code> complètement à chaque fois, il est possible d'en calculer la valeur à partir de la <strong>valeur précédente</strong>. Attention, un peu de mathématiques.</p>
<p>Disons que $m_i$ est la valeur de <code>m</code> à l'itération <code>i</code>. Et $m_{i+1}$ la valeur à l'itération suivante. Avec la même notation pour <code>x</code> et <code>y</code> à chaque itération (le rayon <code>r</code> ne bouge pas), on peut poser : $m_i = (x_i+1)^2 + (y_i-0.5)^2 - r^2$.</p>
<p>Et pour le calcul de <code>m</code> à l'itération suivante : $m_{i+1} = (x_{i+1}+1)^2 + (y_{i+1}-0.5)^2 - r^2$</p>
<p>Il y a <strong>deux cas</strong> dans l'algorithme, <strong>d'abord</strong> celui où l'on sélectionne le <strong>pixel de droite</strong>. Dans ce cas là, <code>x</code> est augmenté de 1, et <code>y</code> ne change pas. Cela donne ces deux égalités : $x_{i+1} = x_i + 1$ et $y_{i+1} = y_i$.</p>
<p>Avec l'objectif de calculer $m_{i+1}$ à partir de $m_i$, remplaçons dans l'équation :</p>
<ul>
<li>$m_{i+1} = (x_{i+1}+1)^2 + (y_{i+1}-0.5)^2 - r^2$</li>
<li>$m_{i+1} = (x_{i+1}+1+1)^2 + (y_{i}-0.5)^2 - r^2$, (par substitution des termes à l'itération précédente)</li>
<li>$m_{i+1} = (x_i^2 + 4x_i + 4) + (y_{i}-0.5)^2 - r^2$, (par développement du premier carré)</li>
<li>$m_{i+1} = ((x_i^2 + 2x_i + 1) + (y_{i}-0.5)^2 - r^2) + 2x_i + 3$, (par commutativité et associativité)</li>
<li>$m_{i+1} = ((x_i + 1)^2 + (y_{i}-0.5)^2 - r^2) + 2x_i + 3$, (par factorisation)</li>
<li>$m_{i+1} = m_i + 2x_i + 3$, (par substitution du premier groupe qui est la définition de $m_i$)</li>
</ul>
<p>Ou bien, si l'on préfère calculer $m_{i+1}$ par rapport aux coordonnées à la même itération :</p>
<ul>
<li>$m_{i+1} = m_i + 2(x_{i+1} - 1) + 3$, (par substitution)</li>
<li>$m_{i+1} = m_i + 2x_{i+1} - 2 + 3$, (par développement)</li>
<li>$m_{i+1} = m_i + 2x_{i+1} + 1$, (par simplification)</li>
</ul>
<p>Dans le <strong>deuxième cas</strong> où l'on prend le pixel en <strong>diagonal</strong>, on a <code>x</code> toujours augmenté de 1, mais aussi <code>y</code> qui est réduit de 1. Cela donne ces deux égalités : $x_{i+1} = x_i + 1$ $y_{i+1} = y_i - 1$.</p>
<p>Remplaçons dans l'équation suivant le même principe :</p>
<ul>
<li>$m_{i+1} = (x_{i+1}+1)^2 + (y_{i+1}-0.5)^2 - r^2$</li>
<li>$m_{i+1} = (x_{i}+1+1)^2 + (y_{i}-1-0.5)^2 - r^2$, (par substitution des termes à l'itération précédente)</li>
<li>$m_{i+1} = (x_{i}+2)^2 + (y_{i}-\frac{3}{2})^2 - r^2$, (par simplification)</li>
<li>$m_{i+1} = (x_{i}^2+4x_{i}+4) + (y_{i}^2-3y_{i}+(\frac{3}{2})^2) - r^2$, (par développement des deux carrés)</li>
<li>$m_{i+1} = ((x_{i}^2+2x_{i}+1) + (y_{i}^2-y_{i}+(\frac{1}{2})^2) - r^2) + 2x_{i} + 3 - 2y_{i} + \frac{8}{4}$, (par commutativité et associativité)</li>
<li>$m_{i+1} = m_i + 2x_{i} + 3 - 2y_{i} + \frac{8}{4}$, (par substitution du premier groupe qui est la définition de $m_i$)</li>
<li>$m_{i+1} = m_i + 2x_{i} + 3 - 2y_{i} + 2$, (par simplification, mais pas trop, pour garder la forme du premier cas visible dans l'équation)</li>
</ul>
<p>Ou bien, si l'on préfère calculer $m_{i+1}$ par rapport aux coordonnées à la même itération :</p>
<ul>
<li>$m_{i+1} = m_i + 2(x_{i+1}-1) + 3 - 2(y_{i+1}+1) + 2$, (par substitution)</li>
<li>$m_{i+1} = m_i + 2x_{i+1} - 2 + 3 - 2y_{i+1} - 2 + 2$, (par développement)</li>
<li>$m_{i+1} = m_i + 2x_{i+1} + 1 - 2y_{i+1}$, (par simplification, mais pas trop, pour garder la forme du premier cas visible dans l'équation)</li>
</ul>
<p>Dans les deux cas, il s'agit donc d'<strong>ajouter à l'ancienne valeur</strong> de <code>m</code> un calcul qui <strong>dépend des nouvelles coordonnées</strong>. La multiplication est une multiplication par 2, ce qui est beaucoup plus rapide pour un ordinateur qu'une multiplication générale. Bon, pas en <strong>BASIC</strong> VG5000µ, mais je vous assure que dans le future, nous serons heureux d'avoir cette simplification.</p>
<p>Une autre <strong>étape intéressante</strong> peut-être de <strong>relier</strong> les deux <strong>équations</strong>, en cherchant à exprimer le résultat du choix du pixel en diagonal par le résultat lorsque le choix est celui du pixel à droite.</p>
<p>Voilà la raison de ne pas trop simplifier les équations à la fin des calculs. Le calcul final est :</p>
<ul>
<li>$m_{i+1} = m_i + 2x_{i+1} + 1 - D$, (D pour diagonale)</li>
<li>$D = 0$ si l'on choisi le point de droite,</li>
<li>$D = 2y_{i_+1}$ si l'on choisi le point en diagonale.</li>
</ul>
<h4>Point de départ</h4>
<p>Maintenant que l'on sait compter la valeur de <code>m</code> à une itération donnée par rapport à sa valeur précédente, il nous faut bien un <strong>point de départ</strong>. Le tout premier <code>m</code>, que l'on note $m_0$ est la valeur pour le premier pixel tracé. Pour rappel, dans l'algorithme, le premier pixel est celui <strong>en haut du cercle</strong>, c'est-à-dire aux coordonnées <code>x = 0</code> et <code>y = r</code>.</p>
<p>Il suffit de remplacer ces valeurs dans l'équation qui nous donne la valeur de <code>m</code> :</p>
<ul>
<li>$m = (x+1)^2 + (y-0.5)^2 - r^2$</li>
<li>$m = (0+1)^2 + (r-0.5)^2 - r^2$, (par substitution)</li>
<li>$m = 1^2 + r^2-r+(\frac{1}{4})^2 - r^2$, (par développement)</li>
<li>$m = \frac{5}{4} - r$, (par simplification)</li>
</ul>
<p>Aïe, une fraction. Une <strong>fraction</strong>, c'est très bien tant qu'on est dans le calcul. Par contre, une fois que l'on doit passer à l'implémentation, cela se corse. Mieux vaut garder des <strong>calculs sur des entiers</strong>, surtout sur des machines de cette époque.</p>
<p><strong>Heureusement</strong>, tout ce qui nous intéresse dans <code>m</code> pour choisir le pixel suivant est <strong>son signe</strong>. Est-ce un nombre positif ou négatif ? Cette information restera valide même si la valeur de <code>m</code> est <strong>multipliée</strong> par un nombre positif.</p>
<p>L'astuce ici sera donc, à l'implémentation, de ne pas calculer <code>m</code>, mais <code>4m</code>, c'est-à-dire quatre fois la valeur de <code>m</code>. L'algorithme reste valide, et l'on se débarrasse de la fraction. En gardant des multiplications par multiples de 2, on garde la facilité à multiplier des entiers rapidement.</p>
<ul>
<li>$4m_0 = 5 - r$</li>
</ul>
<h4>Conclusion</h4>
<p>Cet article est un passage presque entièrement dédié à des mathématiques. Celles-ci nous ont aidé à réduire les calculs nécessaires à la résolution d'un algorithme.</p>
<p><strong>Transformer</strong> un problème en un équivalent plus simple est une des <strong>bases de l'optimisation</strong>. Cela fait parfois appel aux <strong>mathématiques</strong>, parfois à la connaissance de la <strong>machine</strong> utilisée, parfois au <strong>langage</strong> de programmation en lui-même.</p>
<p>Au final, voici le nouvel algorithme, qui sera transformé en <strong>BASIC</strong> dans l'article suivant.</p>
<ul>
<li>initialiser cx et cy avec les coordonnées du centre du cercle, r avec son rayon</li>
<li>initialiser (x, y) à (0, r), c'est à dire le point au sommet du cercle.</li>
<li>initialiser M à $5 - r$</li>
<li>tant que x ≤ y<ul>
<li>tracer le pixel(x + cx, y + cy) et ses sept symétries.</li>
<li>calculer $m = (x+1)^2 + (y-0.5)^2 - r^2$</li>
<li>si m ≥ 0 alors $y \leftarrow y - 1$, $M \leftarrow M - 8y_{i+1}$</li>
<li>$x \leftarrow x + 1$, $M \leftarrow M + 8x_{i+1} + 4$</li>
</ul>
</li>
<li>fin</li>
</ul>Récréation 3D, Amstrad CPC 61282017-04-29T00:00:00+02:002017-04-29T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-04-29:/recreation-3d-amstrad-cpc-6128.html<p>L'ordinateur 8 bits que j'ai le plus décortiqué, sur lequel j'ai le plus programmé en son temps, est un CPC 6128. J'y ai débuté le Pascal et abordé légèrement le C. J'y ai aussi fait mes premiers essais en assembleur.</p>
<p>Un ordinateur élégant, sobre, avec un clavier très agréable, qui …</p><p>L'ordinateur 8 bits que j'ai le plus décortiqué, sur lequel j'ai le plus programmé en son temps, est un CPC 6128. J'y ai débuté le Pascal et abordé légèrement le C. J'y ai aussi fait mes premiers essais en assembleur.</p>
<p>Un ordinateur élégant, sobre, avec un clavier très agréable, qui pouvait servir dans de nombreux domaines. Son principal et historique défaut reste son format de disquettes, particulier, rare et cher.</p>
<p><img alt="Amstrad CPC 6128" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/cpc6128-3d-500.png"></p>Après la ligne, le cercle2017-04-28T00:00:00+02:002017-04-28T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-04-28:/apres-la-ligne-le-cercle.html<p>Après avoir <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html">tracé des segments de droite</a> à l'écran grâce au BASIC du VG5000µ, passons au <strong>cercle</strong>.</p>
<p>Si vous avez fait un peu de <strong>trigonométrie</strong> à l'école, vous devez savoir que, pour un angle <code>α</code> allant de <code>0</code> à <code>2π</code>, tracer des points aux coordonnées <code>(cos(α), sin(α))</code>. Ces …</p><p>Après avoir <a href="https://www.triceraprog.fr/vg5000m-tracer-une-ligne-en-basic.html">tracé des segments de droite</a> à l'écran grâce au BASIC du VG5000µ, passons au <strong>cercle</strong>.</p>
<p>Si vous avez fait un peu de <strong>trigonométrie</strong> à l'école, vous devez savoir que, pour un angle <code>α</code> allant de <code>0</code> à <code>2π</code>, tracer des points aux coordonnées <code>(cos(α), sin(α))</code>. Ces coordonnées sont multipliées par le rayon et déplacées au centre du cercle. Et c'est comme cela qu'il peut sembler <strong>de prime abord</strong> intéressant de tracer un cercle.</p>
<p>Cela ressemble à quelque chose comme ça.</p>
<ul>
<li>initialiser cx et cy avec les coordonnées du centre du cercle, r avec son rayon</li>
<li>initialiser a à 0</li>
<li>tant que $a < 2\pi$<ul>
<li>$x = cx + r.cos(a)$</li>
<li>$y = cy + r.sin(a)$</li>
<li>tracer un point en (x, y)</li>
<li>augmenter a</li>
</ul>
</li>
<li>fin</li>
</ul>
<p>Super simple.</p>
<p>Mais trop simple.</p>
<p>Dans l'algorithme ci-dessus, la ligne qui indique <code>augmenter a</code> n'indique pas de combien il faudrait augmenter <code>a</code>.</p>
<p>Lorsque l'on trace un cercle avec un <strong>crayon</strong> sur une <strong>feuille</strong>, on prend quelques points de références bien choisi, puis on interpole entre ces points pour tracer la courbe. Si on utilise un <strong>compas</strong>, c'est encore plus simple, il suffit de deux points de référence : le <strong>centre</strong> et un point du <strong>rayon</strong>.</p>
<p>Nous avons ici le même <strong>problème</strong> que lorsque l'on cherchait à tracer un <strong>segment de droite</strong> : l'espace de tracé des points à l'écran <strong>n'est pas continu</strong>. Il s'agit dans notre cas d'allumer des pixels de manière à provoquer <strong>l'illusion</strong> de la continuité.</p>
<p>S'ils sont trop tassés, c'est moche. S'ils sont trop espacés, ça fait des trous.</p>
<p>Prenons un exemple avec <code>cx = 6</code>, <code>cy = 5</code> et <code>r = 5</code>. Augmentons <code>a</code> d'un dixième de π à chaque fois. Voilà ce que cela donne :</p>
<table>
<thead>
<tr>
<th>a</th>
<th>0.pi/10</th>
<th>1.pi/10</th>
<th>2.pi/10</th>
<th>3.pi/10</th>
<th>4.pi/10</th>
<th>5.pi/10</th>
<th>6.pi/10</th>
<th>7.pi/10</th>
<th>8.pi/10</th>
<th>9.pi/10</th>
</tr>
</thead>
<tbody>
<tr>
<td>x</td>
<td>11</td>
<td>11</td>
<td>10</td>
<td>9</td>
<td>8</td>
<td>6</td>
<td>4</td>
<td>3</td>
<td>2</td>
<td>1</td>
</tr>
<tr>
<td>y</td>
<td>5</td>
<td>7</td>
<td>8</td>
<td>9</td>
<td>10</td>
<td>10</td>
<td>10</td>
<td>9</td>
<td>8</td>
<td>7</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>a</th>
<th>10.pi/10</th>
<th>11.pi/10</th>
<th>12.pi/10</th>
<th>13.pi/10</th>
<th>14.pi/10</th>
<th>15.pi/10</th>
<th>16.pi/10</th>
<th>17.pi/10</th>
<th>18.pi/10</th>
<th>19.pi/10</th>
</tr>
</thead>
<tbody>
<tr>
<td>x</td>
<td>1</td>
<td>1</td>
<td>2</td>
<td>3</td>
<td>4</td>
<td>6</td>
<td>8</td>
<td>9</td>
<td>10</td>
<td>11</td>
</tr>
<tr>
<td>y</td>
<td>5</td>
<td>3</td>
<td>2</td>
<td>1</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>1</td>
<td>2</td>
<td>3</td>
</tr>
</tbody>
</table>
<p>Traçons cela sur une grille.</p>
<p><img alt="Cercle 1" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/20170426-circle-01.png"></p>
<p>Tout comme avec le segment de droite au début, il y a des <strong>trous</strong>. Et si le rayon augmente, il y aura encore plus de trous. On peut se dire alors qu'on augmente <code>a</code> par de plus petites valeurs. Mais on voit sur le dessin qu'à certains endroits, les pixels sont <strong>adjacents</strong>. Cela signifierait qu'il faudrait, suivant les endroits, augmenter <code>a</code> différemment.</p>
<p><strong>Arrêtons-nous</strong> là pour deux raisons : la première est que l'on sent bien que cela va nous amener vers des choses vraiment <strong>complexes</strong>. La seconde est que <strong>calculer</strong> le cosinus et le sinus sur une machine ancienne de ce type, c'est <strong>lent</strong>, très lent. Certes, notre affichage de pixel est lent lui aussi, n'ajoutons pas du lent au lent.</p>
<h4>Mais alors que faire ?</h4>
<p><strong>Changer de stratégie</strong>, et surtout, <strong>d'équation</strong> de départ. L'équation d'un <strong>cercle</strong> est aussi celle-ci : $x^2 + y^2 - r^2 = 0$. Où <code>r</code> est le rayon. Un tel cercle est <strong>centré</strong> sur les coordonnées (0, 0) mais il suffit d'y ajouter les coordonnées du <strong>centre du cercle</strong> voulu pour le déplacer dans l'espace.</p>
<p><img alt="Équation sur le cercle" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/20170426-cercle.png"></p>
<p>La <strong>deuxième chose</strong> que l'on va faire est, comme avant pour le segment de droite, de <strong>simplifier le problème</strong> en utilisant des <strong>octants</strong>, c'est-à-dire une division en huit secteurs. Ces secteurs sont choisis pour leurs <strong>axes de symétries</strong> sur le cercle de manière à ce que, pour un octant donnée, nous puissions nous occuper d'un <strong>cas très simple</strong>.</p>
<p>Je vous invite à aller voir l'<strong>image plus bas</strong> pour repérer les axes de symétries utilisés.</p>
<h4>Une méthode</h4>
<p>En commençant par le point le <strong>plus haut</strong> du cercle et sur l'octant en haut à droite, on peut <strong>réduire</strong> le problème à une <strong>courbe</strong> qui part dans la <strong>direction générale</strong> entre la droite et la diagonale droite-bas. Dans ce secteur, un pixel aura pour voisin suivant soit le pixel <strong>juste à sa droite</strong>, soit le pixel juste <strong>au-dessous</strong> celui à sa droite.</p>
<p>Si on trouve comment tracer ce <strong>petit morceau</strong> de courbe, il suffit de tracer les huit <strong>courbes symétriques</strong> sur le cercle pour obtenir la forme complète.</p>
<p>Comment choisir si le <strong>pixel suivant</strong> le pixel courant doit être celui à sa droite ou celui en diagonal ? En utilisant la <strong>partie gauche de la formule</strong> ci-dessus pour évaluer la position du cercle parfait par raport à un point situé entre les <strong>deux points éligibles</strong>.</p>
<p>Si le résultat est inférieur à <code>0</code>, alors c'est que le point médian est à l'intérieur du cercle. On choisi alors le pixel <strong>à droite</strong> comme coordonnée suivante.</p>
<p>Si le résultat est supérieur à <code>0</code>, alors c'est que le point médian est à l'extérieur du cercle. On choisi alors le pixel <strong>en bas à droite</strong> comme coordonnée suivante.</p>
<p>Et ainsi de suite jusqu'à ce que la coordonnée <code>x</code> soit supérieure à la coordonnée <code>y</code>, ce qui signal la <strong>fin de l'octant</strong>, puisque nous aurons alors atteint la diagonale.</p>
<p>Reprenons :</p>
<ul>
<li>initialiser cx et cy avec les coordonnées du centre du cercle, r avec son rayon</li>
<li>initialiser (x, y) à (0, r), c'est à dire le point au sommet du cercle.</li>
<li>tant que x ≤ y<ul>
<li>tracer le pixel(x + cx, y + cy) et ses sept symétries.</li>
<li>calculer $m = (x+1)^2 + (y-0.5)^2 - r^2$</li>
<li>si m >= 0 alors $x \leftarrow x + 1$ et $y \leftarrow y - 1$</li>
<li>sinon $x \leftarrow x + 1$</li>
</ul>
</li>
<li>fin</li>
</ul>
<p>Voyons ce que cela donne avec les même entrées que précédemment :</p>
<table>
<thead>
<tr>
<th>x</th>
<th>6</th>
<th>7</th>
<th>8</th>
<th>9</th>
</tr>
</thead>
<tbody>
<tr>
<td>y</td>
<td>10</td>
<td>10</td>
<td>10</td>
<td>9</td>
</tr>
<tr>
<td>m</td>
<td>-3.75<br>(suivant à droite)</td>
<td>-0.75<br>(suivant à droite)</td>
<td>4.25<br>(suivant en bas à droite)</td>
<td>3.25</td>
</tr>
</tbody>
</table>
<p>Le cercle n'a <strong>plus de trous</strong>, les pixels sont voisins et forment une <strong>illusion de continuité</strong>. Ce qui donne sur du papier quadrillé, avec en noir les coordonnées ci-dessus et en gris les différents symétriques.</p>
<p><img alt="Cercle 2" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/20170426-circle-02.png"></p>
<p>Note : cet algorithme n'est <strong>pas le seul</strong> possible et nous verrons lors de son implémentation qu'il <strong>peut être simplifié</strong> au niveau de ses calculs. Mais ceci est une autre histoire.</p>
<p>Note 2 : le second algorithme présenté est l'algorithme de tracé de cercle de Bresenham, du nom de son auteur.</p>Récréation 3D, Commodore PET 20012017-04-25T00:00:00+02:002017-04-25T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-04-25:/recreation-3d-commodore-pet-2001.html<p>Ah les monoblocs bien lourds, mais au design tellement reconnaissable. Cet ordinateur a aussi la particularité d'avoir un clavier particulièrement... pas pratique. Les modèles suivant troqueront le lecteur/enregistreur à cassette par un clavier de nettement meilleur qualité.</p>
<p><img alt="Commodore PET 2001 en 3D" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pet-3d-500.png"></p>VG5000µ, tracer une ligne en BASIC2017-04-20T00:00:00+02:002017-04-20T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-04-20:/vg5000m-tracer-une-ligne-en-basic.html<p>Après avoir décrit un <a href="https://www.triceraprog.fr/apres-le-point-la-ligne.html">algorithme de tracé</a> de <strong>segment de droite</strong> de manière générique, voyons un peu comment traduire ça en BASIC sur le VG5000µ. Pour ce premier article, il s'agira d'une implémentation simpliste, qui servira de base.</p>
<p>Pour rappel, voici l'<strong>algorithme générique</strong> :</p>
<ul>
<li>En entrée, on a deux points …</li></ul><p>Après avoir décrit un <a href="https://www.triceraprog.fr/apres-le-point-la-ligne.html">algorithme de tracé</a> de <strong>segment de droite</strong> de manière générique, voyons un peu comment traduire ça en BASIC sur le VG5000µ. Pour ce premier article, il s'agira d'une implémentation simpliste, qui servira de base.</p>
<p>Pour rappel, voici l'<strong>algorithme générique</strong> :</p>
<ul>
<li>En entrée, on a deux points de coordonnées (x, y) et (x', y')</li>
<li>Si x' est plus petit que x, échanger les valeurs de <code>x</code> et <code>x'</code> ainsi que de <code>y</code> et <code>y'</code></li>
<li>Choix de l'octant en fonction de |y' - y| et de |x' - x|</li>
<li>Si on fait un balayage des x, alors<ul>
<li>Calculer la pente $a = \frac{(y' - y)}{(x' - x)}$</li>
<li>Calculer $b = \frac{x'y - xy'}{x'-x}$</li>
<li>Pour tous les <code>x''</code> de <code>x</code> à <code>x'</code>, calculer $y'' = ax'' + b$.</li>
<li>Tracer un pixel en <code>(x'', y'')</code>.</li>
</ul>
</li>
<li>Si on fait un balayage des y, alors<ul>
<li>Si y' est plus petit que y, échanger les valeurs de <code>x</code> et <code>x'</code> ainsi que de <code>y</code> et <code>y'</code></li>
<li>Calculer la pente $a = \frac{(x' - x)}{(y' - y)}$</li>
<li>Calculer $b = \frac{y'x - yx'}{y'-y}$</li>
<li>Pour tous les <code>y''</code> de <code>y</code> à <code>y'</code>, calculer $x'' = ay'' + b$</li>
<li>Tracer un pixel en <code>(x'', y'')</code></li>
</ul>
</li>
</ul>
<p>Il s'agit à présent de transformer ces instructions en instructions <strong>BASIC</strong>. Je me base sur <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-limplementation.html">l'implémentation de tracé de point</a> publié précédemment, implémenté entre les lignes 100 à 250.</p>
<p>C'est quelque chose de classique en <strong>BASIC</strong> de l'époque, les routines, c'est-à-dire des sous-programmes dédiés à une tâche, sont implémentées à des lignes précises. Dans le cas de mon tracé de point, la routine est re-logeable, il est possible de <strong>changer les numéros</strong> de ligne sans problème.</p>
<p>Cette routine utilise les variables <code>X</code> et <code>Y</code> en entrée. Elle utilise aussi en interne les variables <code>ZX</code>, <code>ZY</code>, <code>RX</code>, <code>RY</code>, <code>CH</code>, <code>DI</code>, <code>AT</code> et <code>OL</code>. Cela signifie que ces variables, si elles sont utilisées ailleurs dans le programme, verront leur contenu changer à l'appel de la routine.</p>
<p><note>C'est, j'en ai déjà parlé, une limitation gigantesque des <strong>BASIC</strong> de cet époque. Même si, ALGOL, un langage antérieur, avait introduit la portée lexicale des variables, la compartimentation des différentes parties d'un programme n'est pas un acquis lors de la création du <strong>BASIC</strong> et les versions disponibles sur les ordinateurs familiaux, par soucis d'accessibilité très certainement, ont gardé cette limitation.</note></p>
<p>Veillons donc juste à être prudent avec le nom des variables utilisées.</p>
<h4>Préambule</h4>
<p>Je vais implémenter la routine de segment de droite à partir de la <strong>ligne 300</strong>, soit après l'implémentation du point. Il y a une raison pour cela que j'expliquerai sûrement plus tard.</p>
<p>La routine commence par une <strong>petite documentation</strong> du fonctionnement. Ces commentaires sont complètement <strong>optionnels</strong> mais bien utiles pour les utilisateurs futurs. Voire pour moi, me rappeler plus tard ce que j'avais décidé.</p>
<div class="highlight"><pre><span></span><code><span class="nl">300</span><span class="w"> </span><span class="c1">REM AFFICHE UN SEGMENT DE DROITE DANS L'ESPACE SEMI-GRAPHIQUE</span>
<span class="nl">310</span><span class="w"> </span><span class="c1">REM X1 ET Y1 CONTIENNENT LES COORDONNEES DE DEPART</span>
<span class="nl">320</span><span class="w"> </span><span class="c1">REM X2 ET Y2 CONTIENNENT LES COORDONNEES D'ARRIVEE</span>
</code></pre></div>
<p>Puis vient l'échange des coordonnées. Comme le <strong>BASIC</strong> du VG5000µ n'a pas de commande pour inverser le contenu de deux variables (c'est une instruction plutôt rare), il me faut passer par une variable intermédiaire que je nomme <code>TT</code>. On reconnaîtra bien là les <strong>pâtés</strong> caractéristique des programmes <strong>BASIC</strong> de l'époque. Plein de lettres et de signes collés.</p>
<div class="highlight"><pre><span></span><code><span class="nl">330</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">X2</span><span class="o"><</span><span class="vg">X1</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">TT</span><span class="o">=</span><span class="nl">X1:</span><span class="vg">X1</span><span class="o">=</span><span class="nl">X2:</span><span class="vg">X2</span><span class="o">=</span><span class="nl">TT:</span><span class="vg">TT</span><span class="o">=</span><span class="nl">Y1:</span><span class="vg">Y1</span><span class="o">=</span><span class="nl">Y2:</span><span class="vg">Y2</span><span class="o">=</span><span class="vg">TT</span>
</code></pre></div>
<p>Il faut ensuite, pour décider de l'octant et donc du balayage, savoir si on est plus de la <strong>verticale</strong> que de l'<strong>horizontale</strong>. Pour cela, on peut calculer la <strong>valeur absolue</strong> de la différence des <code>X</code> (abscisses) et des <code>Y</code> (ordonnées).</p>
<div class="highlight"><pre><span></span><code><span class="nl">340</span><span class="w"> </span><span class="vg">AX</span><span class="o">=</span><span class="kr">ABS</span><span class="p">(</span><span class="vg">X2</span><span class="o">-</span><span class="vg">X1</span><span class="p">)</span><span class="o">:</span><span class="vg">AY</span><span class="o">=</span><span class="kr">ABS</span><span class="p">(</span><span class="vg">Y2</span><span class="o">-</span><span class="vg">Y1</span><span class="p">)</span>
</code></pre></div>
<p>Vient ensuite la <strong>sélection</strong> de la <strong>sous-routine</strong> à utiliser, en fonction de balayage utilisé. L'instruction conditionnelle <code>IF</code> du <strong>BASIC</strong> du VG5000µ n'a pas de clause <code>ELSE</code> comme sur beaucoup d'autres <strong>BASIC</strong>. Une solution (il y en a d'autres) est d'écrire les deux choix explicitement.</p>
<p>Si la condition indiquée après le <code>IF</code> est vérifiée, alors le programme continuera à la ligne indiquée après l'instruction <code>GOSUB</code>. Lorsque l'interpréteur BASIC rencontrera l'instruction <code>RETURN</code>, le déroulement reprendra après le <code>GOSUB</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">350</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">AX</span><span class="w"> </span><span class="o">>=</span><span class="w"> </span><span class="vg">AY</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">400</span>
<span class="nl">360</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">AX</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="vg">AY</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">500</span>
<span class="nl">370</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<h4>Balayages</h4>
<p>Reste à écrire les deux balayages différents. Commençons par les <code>X</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">400</span><span class="w"> </span><span class="vg">A</span><span class="o">=</span><span class="p">(</span><span class="vg">Y2</span><span class="o">-</span><span class="vg">Y1</span><span class="p">)</span><span class="o">/</span><span class="p">(</span><span class="vg">X2</span><span class="o">-</span><span class="vg">X1</span><span class="p">)</span>
<span class="nl">410</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">X1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="vg">X2</span>
<span class="nl">420</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="kr">INT</span><span class="p">(</span><span class="mf">0.5</span><span class="o">+</span><span class="vg">Y1</span><span class="o">+</span><span class="vg">A</span><span class="o">*</span><span class="p">(</span><span class="vg">X</span><span class="o">-</span><span class="vg">X1</span><span class="p">))</span>
<span class="nl">430</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">440</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">X</span>
<span class="nl">450</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<p>Il n'y a pas de fonction d'arrondi disponible. La fonction est émulée en prenant la partie entière, c'est-à-dire sans les chiffres après la virgule, du nombre augmenté de 0.5. La fonction pour obtenir la partie entière est <code>INT</code>.</p>
<p>La ligne <code>420</code> est une simplification du calcul de <code>b</code> dans l'équation de la droite, valide pour les valeurs de <code>x</code> du segment de droite. Je vous laisse faire le calcul.</p>
<p>Pour le balayage des <code>Y</code>, il faut commencer par échanger les coordonnées si nécessaire.</p>
<div class="highlight"><pre><span></span><code><span class="nl">500</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">Y2</span><span class="o"><</span><span class="vg">Y1</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">TT</span><span class="o">=</span><span class="nl">X1:</span><span class="vg">X1</span><span class="o">=</span><span class="nl">X2:</span><span class="vg">X2</span><span class="o">=</span><span class="nl">TT:</span><span class="vg">TT</span><span class="o">=</span><span class="nl">Y1:</span><span class="vg">Y1</span><span class="o">=</span><span class="nl">Y2:</span><span class="vg">Y2</span><span class="o">=</span><span class="vg">TT</span>
</code></pre></div>
<p>Puis le balayage en lui-même est effectué.</p>
<div class="highlight"><pre><span></span><code><span class="nl">510</span><span class="w"> </span><span class="vg">A</span><span class="o">=</span><span class="p">(</span><span class="vg">X2</span><span class="o">-</span><span class="vg">X1</span><span class="p">)</span><span class="o">/</span><span class="p">(</span><span class="vg">Y2</span><span class="o">-</span><span class="vg">Y1</span><span class="p">)</span>
<span class="nl">520</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="vg">Y1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="vg">Y2</span>
<span class="nl">530</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="kr">INT</span><span class="p">(</span><span class="mf">0.5</span><span class="o">+</span><span class="vg">X1</span><span class="o">+</span><span class="vg">A</span><span class="o">*</span><span class="p">(</span><span class="vg">Y</span><span class="o">-</span><span class="vg">Y1</span><span class="p">))</span>
<span class="nl">540</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">550</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">Y</span>
<span class="nl">560</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<p>La ligne <code>530</code> comme la <code>420</code> est une simplification des calculs.</p>
<p>Et voilà. Une routine de tracé de segment de droite avec les moyens du bord, en <strong>BASIC</strong> VG5000µ.</p>
<p>Petite modification avant le récapitulatif, les lignes <code>500</code> et <code>330</code> font toutes les deux l'échange des coordonnées. Se répéter en programmation est mauvais signe dans une grande partie des cas. Déplaçons cette partie dans une sous-routine.</p>
<div class="highlight"><pre><span></span><code><span class="nl">330</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">X2</span><span class="o"><</span><span class="vg">X1</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">270</span>
<span class="nl">500</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">Y2</span><span class="o"><</span><span class="vg">Y1</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">270</span>
<span class="nl">270</span><span class="w"> </span><span class="vg">TT</span><span class="o">=</span><span class="nl">X1:</span><span class="vg">X1</span><span class="o">=</span><span class="nl">X2:</span><span class="vg">X2</span><span class="o">=</span><span class="nl">TT:</span><span class="vg">TT</span><span class="o">=</span><span class="nl">Y1:</span><span class="vg">Y1</span><span class="o">=</span><span class="nl">Y2:</span><span class="vg">Y2</span><span class="o">=</span><span class="vg">TT</span>
<span class="nl">280</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<h4>Récapitulatif</h4>
<div class="highlight"><pre><span></span><code><span class="nl">300</span><span class="w"> </span><span class="c1">REM AFFICHE UN SEGMENT DE DROITE DANS L'ESPACE SEMI-GRAPHIQUE</span>
<span class="nl">310</span><span class="w"> </span><span class="c1">REM X1 ET Y1 CONTIENNENT LES COORDONNEES DE DEPART</span>
<span class="nl">320</span><span class="w"> </span><span class="c1">REM X2 ET Y2 CONTIENNENT LES COORDONNEES D'ARRIVEE</span>
<span class="nl">330</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">X2</span><span class="o"><</span><span class="vg">X1</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">380</span>
<span class="nl">340</span><span class="w"> </span><span class="vg">AX</span><span class="o">=</span><span class="kr">ABS</span><span class="p">(</span><span class="vg">X2</span><span class="o">-</span><span class="vg">X1</span><span class="p">)</span><span class="o">:</span><span class="vg">AY</span><span class="o">=</span><span class="kr">ABS</span><span class="p">(</span><span class="vg">Y2</span><span class="o">-</span><span class="vg">Y1</span><span class="p">)</span>
<span class="nl">350</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">AX</span><span class="w"> </span><span class="o">>=</span><span class="w"> </span><span class="vg">AY</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">400</span>
<span class="nl">360</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">AX</span><span class="w"> </span><span class="o"><</span><span class="w"> </span><span class="vg">AY</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">500</span>
<span class="nl">370</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">380</span><span class="w"> </span><span class="vg">TT</span><span class="o">=</span><span class="nl">X1:</span><span class="vg">X1</span><span class="o">=</span><span class="nl">X2:</span><span class="vg">X2</span><span class="o">=</span><span class="nl">TT:</span><span class="vg">TT</span><span class="o">=</span><span class="nl">Y1:</span><span class="vg">Y1</span><span class="o">=</span><span class="nl">Y2:</span><span class="vg">Y2</span><span class="o">=</span><span class="vg">TT</span>
<span class="nl">390</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">400</span><span class="w"> </span><span class="vg">A</span><span class="o">=</span><span class="p">(</span><span class="vg">Y2</span><span class="o">-</span><span class="vg">Y1</span><span class="p">)</span><span class="o">/</span><span class="p">(</span><span class="vg">X2</span><span class="o">-</span><span class="vg">X1</span><span class="p">)</span>
<span class="nl">410</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="vg">X1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="vg">X2</span>
<span class="nl">420</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="kr">INT</span><span class="p">(</span><span class="mf">0.5</span><span class="o">+</span><span class="vg">Y1</span><span class="o">+</span><span class="vg">A</span><span class="o">*</span><span class="p">(</span><span class="vg">X</span><span class="o">-</span><span class="vg">X1</span><span class="p">))</span>
<span class="nl">430</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">440</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">X</span>
<span class="nl">450</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">500</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">Y2</span><span class="o"><</span><span class="vg">Y1</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">380</span>
<span class="nl">510</span><span class="w"> </span><span class="vg">A</span><span class="o">=</span><span class="p">(</span><span class="vg">X2</span><span class="o">-</span><span class="vg">X1</span><span class="p">)</span><span class="o">/</span><span class="p">(</span><span class="vg">Y2</span><span class="o">-</span><span class="vg">Y1</span><span class="p">)</span>
<span class="nl">520</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">Y</span><span class="o">=</span><span class="vg">Y1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="vg">Y2</span>
<span class="nl">530</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="kr">INT</span><span class="p">(</span><span class="mf">0.5</span><span class="o">+</span><span class="vg">X1</span><span class="o">+</span><span class="vg">A</span><span class="o">*</span><span class="p">(</span><span class="vg">Y</span><span class="o">-</span><span class="vg">Y1</span><span class="p">))</span>
<span class="nl">540</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">550</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">Y</span>
<span class="nl">560</span><span class="w"> </span><span class="kr">RETURN</span>
</code></pre></div>
<h4>Utilisation</h4>
<p>Voici par exemple une utilisation du programme pour tracé un dessin. Sur chacune des lignes, les coordonnées des lignes sont mentionnées puis la sous-routine est appelée. La commande <code>DISPLAY</code> exécutée ensuite sert à forcer le rafraîchissement de l'écran après chaque ligne.</p>
<p>Comme indiqué précédemment, c'est long, très long. Il faut environ 17 secondes sur la machine pour contempler le résultat. Le VG5000µ nous laisse peu d'options en restant dans le BASIC pour améliorer cela. Nous essaierons cependant une prochaine fois.</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="kr">GOTO</span><span class="w"> </span><span class="nl">1000</span>
<span class="nl">1000</span><span class="w"> </span><span class="vg">INIT</span>
<span class="nl">1010</span><span class="w"> </span><span class="vg">X1</span><span class="o">=</span><span class="il">10</span><span class="o">:</span><span class="vg">Y1</span><span class="o">=</span><span class="il">15</span><span class="o">:</span><span class="vg">X2</span><span class="o">=</span><span class="il">35</span><span class="o">:</span><span class="vg">Y2</span><span class="o">=</span><span class="il">5</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300:</span><span class="vg">DISPLAY</span>
<span class="nl">1020</span><span class="w"> </span><span class="vg">X1</span><span class="o">=</span><span class="il">60</span><span class="o">:</span><span class="vg">Y1</span><span class="o">=</span><span class="il">15</span><span class="o">:</span><span class="vg">X2</span><span class="o">=</span><span class="il">35</span><span class="o">:</span><span class="vg">Y2</span><span class="o">=</span><span class="il">5</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300:</span><span class="vg">DISPLAY</span>
<span class="nl">1030</span><span class="w"> </span><span class="vg">X1</span><span class="o">=</span><span class="il">10</span><span class="o">:</span><span class="vg">Y1</span><span class="o">=</span><span class="il">15</span><span class="o">:</span><span class="vg">X2</span><span class="o">=</span><span class="il">35</span><span class="o">:</span><span class="vg">Y2</span><span class="o">=</span><span class="il">60</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300:</span><span class="vg">DISPLAY</span>
<span class="nl">1040</span><span class="w"> </span><span class="vg">X1</span><span class="o">=</span><span class="il">60</span><span class="o">:</span><span class="vg">Y1</span><span class="o">=</span><span class="il">15</span><span class="o">:</span><span class="vg">X2</span><span class="o">=</span><span class="il">35</span><span class="o">:</span><span class="vg">Y2</span><span class="o">=</span><span class="il">60</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300:</span><span class="vg">DISPLAY</span>
<span class="nl">1050</span><span class="w"> </span><span class="vg">X1</span><span class="o">=</span><span class="il">60</span><span class="o">:</span><span class="vg">Y1</span><span class="o">=</span><span class="il">15</span><span class="o">:</span><span class="vg">X2</span><span class="o">=</span><span class="il">10</span><span class="o">:</span><span class="vg">Y2</span><span class="o">=</span><span class="il">15</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">300:</span><span class="vg">DISPLAY</span>
<span class="nl">1060</span><span class="w"> </span><span class="kr">END</span>
</code></pre></div>
<p><img alt="Résultat de tracé de segments" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/vg5000-lignes-diamant.jpeg"></p>Une autre récréation 3D2017-04-19T00:00:00+02:002017-04-19T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-04-19:/une-autre-recreation-3d.html<p>Je continue sur ma lancée avec une évocation d'Altaïr 8800, un ordinateur de 1975, vendu en kit. Si vous avez un peu de temps, une <a href="http://altairclone.com/altair_experience.htm">série de vidéos</a> explique son utilisation, depuis les bases. C'est un clone récent qui est utilisé.</p>
<p><img alt="Altaïr en 3D" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/altair-3d-500.png"></p>Récréation 3D2017-04-17T00:00:00+02:002017-04-17T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-04-17:/recreation-3d.html<p>Aujourd'hui, j'ai préparé quelques illustrations sous forme d'évocations de machines connues. Voici une première image, qui j'espère évoque assez bien la machine dont j'ai parlé jusqu'à maintenant sur ce site.</p>
<p><img alt="VG5000µ en 3D" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/vg5000-3d-500.png"></p>Visite au Pixel Museum2017-04-16T00:00:00+02:002017-04-16T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-04-16:/visite-au-pixel-museum.html<p>« <a href="http://pixel-museum.fr">Pixel Museum</a> » est un musée consacré aux jeux vidéo ouvert depuis le 25 février 2017, à Shiltigheim, au nord de Strasbourg. C'est donc un musée quasi neuf que je suis allé visiter.</p>
<p><img alt="La façade du musée" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pixel_museum/20170413-pixelmuseum-facade.jpeg"></p>
<h3>Le thème</h3>
<p>La bannière à l'entrée du musée annonce la couleur : il s'agit d'un musée dédié aux jeux …</p><p>« <a href="http://pixel-museum.fr">Pixel Museum</a> » est un musée consacré aux jeux vidéo ouvert depuis le 25 février 2017, à Shiltigheim, au nord de Strasbourg. C'est donc un musée quasi neuf que je suis allé visiter.</p>
<p><img alt="La façade du musée" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pixel_museum/20170413-pixelmuseum-facade.jpeg"></p>
<h3>Le thème</h3>
<p>La bannière à l'entrée du musée annonce la couleur : il s'agit d'un musée dédié aux jeux vidéo. Le cheminement à travers les salles est chronologique si l'on suit les panneaux commençant par « Start ». Cependant, l'agencement des salles et la présence d'un groupe (un groupe, ça bouche toujours un musée) fera que c'est dans un semi désordre que j'ai parcouru les salles. Mais peu importe.</p>
<p>Dans la première salle, un « <strong>Tennis for two</strong> » (malheureusement indiqué comme étant en réparation). On commence bien par les débuts. Pas de grosses pièces comme un Pong arcade au d'évocation de jeu vidéo sur Minis, comme au <strong>Computer Spiel Museum</strong> de Berlin. Ici, on parle jeu vidéo dans le contexte familial.</p>
<p>C'est donc logiquement que la seconde pièce présente une foison de console de l'époque Pong, Odyssée et autres. De très belles pièces, très diverses, montrant aussi que l'objet ludique électronique est tout juste en train de naître et n'est pas encore très dissocié d'autres jeux électroniques ou... de l'électroménager.</p>
<p><img alt="Simon et Merlin" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pixel_museum/20170413-pixelmuseum-simon.jpeg"></p>
<p>La présence plus loin d'un « <strong>Simon</strong> » ou d'un « <strong>Merlin</strong> » est une manière d'ancrer l'époque sur un thème débordant un peu du jeu vidéo au sens strict, et c'est une signature du musée qui nous suit tout au long de la visite.</p>
<h3>Les époques</h3>
<p>Ce que l'on peut dire, c'est que le musée est à jour et couvre une période allant des tous débuts du jeu vidéo jusqu'à... aujourd'hui. La présence d'une <strong>Nintendo Switch</strong> l'atteste.</p>
<p><img alt="Switch" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pixel_museum/20170413-pixelmuseum-switch.jpeg"></p>
<p>La Switch accompagnée de son coffret Zelda collector est aussi une signature du musée : les pièces sont souvent présentées dans une version <strong>collector</strong>, ou tout simplement <strong>rare</strong>. Et toujours en très bon état esthétique.</p>
<p><img alt="Quête des anneaux" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pixel_museum/20170413-pixelmuseum-quete.jpeg"></p>
<h3>Les ordinateurs</h3>
<p>Le musée dédie le plus gros de sa surface aux consoles de jeux. Une vitrine seulement est dédiée aux ordinateurs familiaux et celle-ci n'est pas la mieux mise en valeur, les machines sont un peu les unes sur les autres et un CBM 2001 Series est même coincé sur le côté (certes, par rapport aux autres pièces, il s'agit là d'un titan qui étouffe un peu dans la vitrine).</p>
<p>Allez, on pardonne, il y a un <strong>VG5000µ</strong>.</p>
<p><img alt="VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pixel_museum/20170413-pixelmuseum-vg5000.jpeg"></p>
<p>En fait, cette section, d'après sa disposition et son contenu, semble plus se ranger dans les « à côté » de l'exposition, tout comme le « <strong>Simon</strong> ». En effet, le VG5000µ, le Alice ou le Portfolio ne sont pas vraiment des machines réputées pour leur catalogue de jeux vidéo.</p>
<p>L'<strong>Amiga</strong> à côté, coincé à côté d'un <strong>VIC-20</strong> (pas de Commodore 64 ?) ou les deux <strong>Amstrad CPC</strong> ne sont pas vraiment mis en valeur.</p>
<p><img alt="VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pixel_museum/20170413-pixelmuseum-alice.jpeg"></p>
<p>Quelques machines japonaises, comme le <strong>Sord M5</strong> ou le <strong>Family Computer</strong> ont droit a un plus bel espace dans la salle la plus spacieuse.</p>
<p>Ce n'est pas le point fort de Pixel Museum aujourd'hui, donc. Le musée est principalement focalisé sur l'objet exclusivement dédié au jeu.</p>
<h3>L'interactivité</h3>
<p>Dans un musée de jeux vidéo, on s'attend à pouvoir <strong>jouer</strong>. Et c'est le cas au Pixel Museum. Quelques bornes de console sont en libre accès</p>
<p><img alt="Vectrex et jeux d'Arcades" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pixel_museum/20170413-pixelmuseum-arcades-et-vectrex.jpeg"></p>
<p>Une salle tout en longueur présente aussi quelques bornes d'arcade et un flipper, malheureusement lui aussi en panne lors de mon passage. Les titres présentés en <strong>Arcade</strong> ne sont pas forcément les plus emblématiques, reste tout de même l'<strong>expérience</strong> de jeu sur borne.</p>
<p>Côté vieilles consoles, une <strong>Vectrex</strong> et un <strong>Videopac</strong> permettent de goûter au jeu <strong>à l'ancienne</strong>. Côté plus récent, <strong>Dreamcast</strong>, <strong>Gamecube</strong>, <strong>Gameboy</strong> ou encore <strong>Wii</strong>. Je ne me souviens plus de toutes, ce n'était pas ce qui m'intéressait le plus.</p>
<p>Rien de fonctionnel du côté de la génération Pong, la table-vidéo au centre de la pièce n'était pas en fonctionnement. Il y avait cependant une évocation du jeu, ce qui nous amène aux re-créations présentes dans le musée.</p>
<h3>Les re-créations</h3>
<p>Au long du parcours, le musée nous propose des <strong>re-créations</strong> ou évocations, sur matériel récent, de jeux. Le premier est un <strong>Pong</strong> qui évolue graphiquement au fur et à mesure que les joueurs marquent des points. D'un <strong>Pong</strong> tout vide, on passe à la couleur, au tennis, à la possibilité de bouger sur l'axe horizontal en plus du vertical, pour être accompagné, dans les derniers points, par une débauche de couleurs.</p>
<p>L'<strong>idée est bonne</strong>. La réalisation pas autant. La manipulation se fait avec stick arcade, ce qui a l'avantage d'être solide mais l'inconvénient de ne pas être fidèle et de présenter trop de boutons pour un Pong. Le mouvement des raquettes et de la balle est extrêmement saccadé, alors que la particularité d'un Pong est d'être très fluide, de par sa nature. Et la plâtrée de couleurs à la fin n'est pas vraiment du meilleur goût.</p>
<p>Bref, <strong>pas convaincu</strong>.</p>
<p>Autre création peu convaincante que celle qui prend une bonne partie du hall d'entrée et met face à face deux joueurs pour un jeu de combat à base de formes géométriques. Là encore, la réalisation <strong>gâche l'expérience</strong> et j'ai pu observer les joueurs passer, tenter de comprendre, et s'en aller en faisant la moue. La plupart n'ayant pas réussi à lancer une partie.</p>
<p>J'y ai joué, j'ai réussi avec peine à lancer une partie et je n'ai pas vraiment compris ce qu'essayait d'évoquer ou de provoquer ce jeu. Pour moi, c'est une <strong>erreur</strong>.</p>
<p>Enfin, <strong>la réussite</strong> est dans la petite pièce consacrée aux jeux électroniques portatifs et à sa <strong>Game & Watch</strong> géante. Le rendu et le feeling est fidèle, juste en grand. La réalisation physique est propre. C'est une re-création réussie et amusante.</p>
<p><img alt="Game & Watch géante" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pixel_museum/20170413-pixelmuseum-recreation.jpeg"></p>
<h3>Les locaux</h3>
<p>Le musée, qui est <strong>facile à trouver</strong>, est situé dans de beaux locaux. Il ne faut cependant pas se fier à l'extérieur, les pièces sont situées principalement au <strong>sous-sol</strong>. Cependant, l'agencement est bon, la <strong>décoration</strong> plutôt <strong>réussie</strong> et de belles fresques nous accompagnent dans les escaliers.</p>
<p>La plus vaste des pièces est malheureusement située en bout de cave, sans autre sortie que l'entrée, ce qui pour des raisons de <strong>sécurité</strong> compréhensibles la <strong>limite en visiteurs</strong> simultanés à 19 personnes. En tant normal, j'imagine que cela est bien suffisant, mais il est possible que vous ayez à attendre votre tour pour y entrer. Et comme c'est la pièce la plus longue à visiter...</p>
<p>Au rez-de-chaussée, mis à part la pièce de la Game & Watch et celle des Pong, on trouve une salle qui met en compétition <strong>PS4</strong> et <strong>X1</strong>, mais pas sur les mêmes jeux, et une salle avec quatre <strong>PCs gamers</strong>, avec tout le matériel adéquat.</p>
<p>Le hall central, malgré la position centrale de la création que je n'ai pas aimée, est consacrée aux expositions temporels. Lors de ma visite, <strong>The Legend of Zelda</strong> et <strong>Mario Kart</strong> étaient à l'honneur, avec de belles pièces retraçant l'histoire de ces séries.</p>
<p>Des toilettes et un mini-magasin complète le tout.</p>
<p><strong>Curiosité</strong>, dans la salle des PC, cette machine <strong>Kienzle</strong> sur lequel j'imagine assez bien qu'aucun jeu n'a jamais tourné, mais je peux me tromper. Je ne comprends pas bien la présence dans la machine dans ce musée, mais cela faisait plaisir à voir tout de même.</p>
<p><img alt="Kienzle 2000" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/pixel_museum/20170413-pixelmuseum-kienzle.jpeg"></p>
<h3>Les autres activités</h3>
<p>Le site indique, en plus d'<strong>expositions thématiques</strong>, la présence d'<strong>ateliers</strong> de création de jeux, la possibilité de <strong>privatiser</strong> pour un événement, un (ou des ?) espace(s) de travail.</p>
<p>Ce ne sont pas des activités auxquelles j'ai participé, mais qui montrent le <strong>dynamisme</strong> de l'équipe du musée et la volonté de faire de Pixel Museum un <strong>espace vivant</strong>.</p>
<h3>Conclusion</h3>
<p>Je ressors très <strong>content</strong> de cette visite. Ce musée est tout jeune mais commence fort. La collection est intéressante, variée, globalement bien mise en valeur. Les vitrines sont réfléchies, lisibles.</p>
<p>N'hésitez pas à consacrer <strong>au moins un heure</strong> pour cette visite, qui pourra vite s'allonger si vous avez envie de jouer un peu, ou de discuter un peu vos souvenirs devant les vitrines si vous êtes dans un groupe.</p>Après le point, la ligne2017-04-04T00:00:00+02:002017-04-04T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-04-04:/apres-le-point-la-ligne.html<p>Après avoir vu le point, si nous passions à la ligne ? Ou plus exactement, au <strong>segment de droite</strong>.</p>
<p>Sur une feuille de papier, <strong>tracer</strong> un segment de droite entre <strong>deux points</strong> est une chose
simple : un crayon, un règle, on positionne la règle sur les deux points et l'on trace …</p><p>Après avoir vu le point, si nous passions à la ligne ? Ou plus exactement, au <strong>segment de droite</strong>.</p>
<p>Sur une feuille de papier, <strong>tracer</strong> un segment de droite entre <strong>deux points</strong> est une chose
simple : un crayon, un règle, on positionne la règle sur les deux points et l'on trace un
trait en tenant fermement cette dernière.</p>
<p>Le trait a alors une <strong>allure continue</strong> ; du moment que l'on n'utilise pas un microscope pour
en détailler la composition. Et c'est une représentation suffisante,
dans la plupart des cas, de l'objet mathématique sous-jacent.</p>
<p>Sur un <strong>écran</strong> d'ordinateur, c'est un <strong>tout autre problème</strong>. En effet, un écran traditionnel
est constitué d'une matrice de petits carreaux, les <strong>pixels</strong>. Les pixels des écrans récents
sont très petits et malgré cela, tracer un beau segment de droite n'est pas si simple.
Alors imaginez avec les <strong>gros carreaux</strong> formant les affichages d'anciens ordinateurs ?</p>
<p>Plutôt que de l'imaginer, vous pouvez même faire une expérience.</p>
<p>Prenez une feuille à <strong>petits carreaux</strong>, choisissez un point de départ que vous <strong>coloriez</strong> en
noir, un point d'arrivée que vous coloriez en noir. L'exercice consiste alors à colorier
les carreaux intermédiaires afin de tracer un segment de droite.</p>
<p>Si la réalisation est très simple pour des segments de droite horizontaux, vertiaux ou
diagonaux, les droites plus quelconques, elles, demandent un peu plus d'attention afin
d'obtenir un beau tracé, équilibré.</p>
<p><img alt="Lignes à carreaux" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/201704-lignes-01.png"></p>
<p>Une stratégie peut être de prendre une règle, de tracer un <strong>trait fin</strong> entre les milieux des
deux cases d'extrémités et de colorier ensuite les <strong>cases traversées</strong> par le trait.</p>
<p><img alt="Lignes à problèmes" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/201704-lignes-02.png"></p>
<p>Il y a de l'idée, mais la droite, avec certaines orientations, est <strong>épaisse</strong> par
endroit. Avec des gros pixels, cela ne va pas bien rendre.</p>
<p>Une autre idée est d'utiliser la <strong>forme mathématique</strong> d'une droite : $y = ax + b$. En
ajustant correctement <code>a</code> et <code>b</code> et faisant varier <code>x</code> sur les coordonnées de l'écran, on
devrait trouver <code>y</code>.</p>
<p><img alt="Coordonnées" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/201704-lignes-04.png"></p>
<table>
<thead>
<tr>
<th>x</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
<th>7</th>
</tr>
</thead>
<tbody>
<tr>
<td>y</td>
<td>1</td>
<td>1,5</td>
<td>2</td>
<td>2,5</td>
<td>3</td>
<td>3,5</td>
<td>4</td>
<td>4,5</td>
</tr>
</tbody>
</table>
<p>La solution fonctionne bien... jusqu'à ce que <code>a</code> soit supérieur à 1. Au-delà de 1, le tracé
du segment à l'écran n'est plus continu, mais <strong>comporte des trous</strong> !</p>
<p><img alt="Des trous" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/201704-lignes-03.png"></p>
<p>Et c'est sans parler du cas où l'on voudrait tracer un <strong>segment vertical</strong>. En effet, avec
l'idée de faire varier les x, pour un segment vertical, x ne prend qu'une valeur. Et donc
y une seule valeure aussi. Ce qui donne... <strong>un beau point</strong>. C'est normal puisqu'à une valeur de <code>x</code>
on associe <strong>une et une seule valeur</strong> de <code>y</code>.</p>
<p>Cette manière de tracer des segments de droite semble <strong>ne pas fonctionner</strong>, et pourtant,
c'est première idée n'est pas si mauvaise. Elle nécessite juste un petit <strong>ajustement</strong>.</p>
<p>Puisque la méthode de <strong>balayage des <code>x</code></strong> fonctionne sur les segments de droites allant d'une
<strong>pente nulle</strong>, l'horizontale, à une <strong>pente de 1</strong>, la diagonale, peut-être faut-il changer un
peu d'angle pour les segments de droite allant de la diagonale à la verticale ?</p>
<p>Pour cette partie-là, est-ce qu'il ne suffirait pas de poser : $x = a'y + b'$ ? C'est-à-dire
<strong>balayer les <code>y</code></strong> pour obtenir les <code>x</code>, avec des coefficients ajustés en conséquence.</p>
<p><img alt="Coordonnées" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/201704-lignes-05.png"></p>
<table>
<thead>
<tr>
<th>x</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
<th>6</th>
</tr>
</thead>
<tbody>
<tr>
<td>y</td>
<td>2</td>
<td>2 + 1/3</td>
<td>2 + 2/3</td>
<td>3</td>
<td>3 + 1/3</td>
<td>3 + 2/3</td>
<td>4</td>
</tr>
</tbody>
</table>
<p><img alt="Plus de trous" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/201704-lignes-08.png"></p>
<p>Ça fonctionne plutôt bien.</p>
<p>Par un jeu de <strong>symétries</strong>, le problème peut être résolu en séparant l'espace autour du point
de départ en <strong>huit espaces</strong> qui induiront l'utilisation d'un balayage des <code>x</code> ou des <code>y</code>. Ces
secteurs étant au nombre de huit, on les appelle des <strong>octants</strong>.</p>
<p>Pour simplifier le problème, on peut <strong>limiter</strong> les cas à <strong>deux</strong> d'entres eux en prenant pour
extrémité de départ le point le plus à gauche sur l'axe des <code>x</code>, par exemple. Il reste alors à considérer
dans lequel des quatre octants à la droite de ce point le segment va être tracé, et
choisir entre le balayage des <code>x</code> ou le balayage des <code>y</code>.</p>
<p><img alt="Octants" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/201704-lignes-06.png"></p>
<p>Pour décider de la <strong>méthode à appliquer</strong>, on peut commencer par calculer la <strong>pente de la droite</strong>.
Si les deux points sont <code>(x, y)</code> et <code>(x', y')</code>, alors la pente de la droite est $\frac{(y' - y)}{(x' - x)}$, le <code>a</code>
dans $y = ax + b$.</p>
<p>Cependant, en cas de <strong>droite verticale</strong>, on se retrouve avec une <strong>division</strong> par <code>0</code> (x' étant égal à x).
Si la droite est presque verticale, on peut aussi se retrouver avec des valeurs assez grandes et des
problèmes de capacités de calcul de la machine.</p>
<p>En fait, il n'est <strong>pas nécessaire</strong> de calculer la pente, car ce que l'on cherche à savoir
où se situe la droite portant le segment <strong>par rapport</strong> à une horizontale, une verticale et une
diagonale.</p>
<p>Et pour cela, il suffit d'une part de <strong>comparer les valeurs absolues</strong> de $(y' - y)$ et de $(x' -
x)$. Si dans le cas d'un balayage des <code>y</code>, on part du point le plus bas (tout comme on part du point
le plus à gauche pour le balayage des <code>x</code>), alors les <strong>secteurs</strong> sont <strong>utilisés de cette manière</strong> :</p>
<p><img alt="Décision" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201704/201704-lignes-07.png"></p>
<h4>Un premier algorithme</h4>
<p>J'obtiens donc une première idée d'algorithme qui ressemble grossièrement à ça :</p>
<ul>
<li>En entrée, on a deux points de coordonnées (x, y) et (x', y')</li>
<li>Si x' est plus petit que x, échanger les valeurs de <code>x</code> et <code>x'</code> ainsi que de <code>y</code> et <code>y'</code></li>
<li>Choix de l'octant en fonction de |y' - y| et de |x' - x|</li>
<li>Si on fait un balayage des x, alors<ul>
<li>Calculer la pente $a = \frac{(y' - y)}{(x' - x)}$</li>
<li>Calculer $b = \frac{x'y - xy'}{x'-x}$</li>
<li>Pour tous les <code>x''</code> de <code>x</code> à <code>x'</code>, calculer $y'' = ax'' + b$.</li>
<li>Tracer un pixel en <code>(x'', y'')</code>.</li>
</ul>
</li>
<li>Si on fait un balayage des y, alors<ul>
<li>Si y' est plus petit que y, échanger les valeurs de <code>x</code> et <code>x'</code> ainsi que de <code>y</code> et <code>y'</code></li>
<li>Calculer la pente $a = \frac{(x' - x)}{(y' - y)}$</li>
<li>Calculer $b = \frac{y'x - yx'}{y'-y}$</li>
<li>Pour tous les <code>y''</code> de <code>y</code> à <code>y'</code>, calculer $x'' = ay'' + b$</li>
<li>Tracer un pixel en <code>(x'', y'')</code></li>
</ul>
</li>
</ul>
<p>Voici un algorithme de tracé de segment de droite qui <strong>fonctionne</strong>. Mais qui est aussi extrêmement lent.
Dans un prochain article, nous verrons comment rendre ça sur le VG5000 en BASIC.</p>VG5000µ, SetPoint en BASIC, l'implémentation2017-03-28T00:00:00+02:002017-03-28T00:00:00+02:00Sylvain Glaizetag:www.triceraprog.fr,2017-03-28:/vg5000m-setpoint-en-basic-limplementation.html<p>Ça y est, après avoir vu la <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">structure des caractères semi-graphiques</a>, <a href="https://www.triceraprog.fr/vg5000m-arrangement-de-la-memoire-video.html">l'arrangement en mémoire</a> des valeurs d'affichage et fait un <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-manipulation-de-gros-pixels.html">essai de manipulation de ces valeurs</a>, <strong>nous voici prêts</strong> pour écrire un morceau de programme qui peut prendre en entrée des coordonnées et allumer ou éteindre un <strong>point à l'écran …</strong></p><p>Ça y est, après avoir vu la <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">structure des caractères semi-graphiques</a>, <a href="https://www.triceraprog.fr/vg5000m-arrangement-de-la-memoire-video.html">l'arrangement en mémoire</a> des valeurs d'affichage et fait un <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-manipulation-de-gros-pixels.html">essai de manipulation de ces valeurs</a>, <strong>nous voici prêts</strong> pour écrire un morceau de programme qui peut prendre en entrée des coordonnées et allumer ou éteindre un <strong>point à l'écran</strong>.</p>
<p>Le <strong>BASIC</strong> est un langage, dans les versions de l'époque de cette machine, <strong>assez limité</strong>. Il n'y a entre autre pas de notion de portée lexicale des variables. La <strong>portée lexicale</strong> d'une variable, c'est l'ensemble des parties d'un code source depuis lesquelles une variable, ou plus généralement un symbole, est accessible. En <strong>BASIC</strong> sur VG5000µ, comme sur les BASIC d'autres machines de cette période, c'est simple, les variables simples sont accessibles de partout à partir du moment où elles ont été définies.</p>
<p>À cela s'ajoute le fait que seuls les deux premiers caractères d'une variable sont significatifs. <code>ABC</code> et <code>ABD</code> désignent les mêmes variables. Cela limite fortement la possibilité d'utiliser des noms de variables intéressants.</p>
<p><img alt="Deux caractères significatifs pour les variables" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/vg5000-longueur-de-variable.jpg"></p>
<p>Cette aparté terminée, revenons à la définition du problème.</p>
<h4>Modélisation du problème</h4>
<p><strong>En entrée</strong>, nous avons : des coordonnées X et Y, comprises entre <code>0</code> et <code>79</code> pour X et <code>0</code> et <code>74</code> pour Y.</p>
<p><strong>En effet de bord</strong>, c'est-à-dire en modification de l'état de la machine, nous voulons : le point correspondant à l'écran qui prend la couleur d'encre définie.</p>
<p>Dans un premier temps, la procédure ne prendra pas d'information de couleur, je me contenterai d'utiliser la couleur d'encre <code>0</code> (noir) sur fond <code>6</code> (bleu), qui est la combinaison à l'initialisation de la machine.</p>
<p>Les <strong>étapes</strong>, d'après les articles précédents, sont donc :</p>
<ul>
<li>À partir de X et Y, trouver les coordonnées du caractère à modifier à l'écran</li>
<li>À partir de X et Y, trouver les coordonnées à l’intérieur du caractère semi-graphique</li>
<li>À partir de coordonnées du caractère, calculer l'adresse mémoire écran correspondante</li>
<li>Récupérer les valeurs pour la paire d'adresse mémoire</li>
<li>Si le caractère présent n'était pas un caractère semi-graphique standard, considérer qu'il était complètement éteint (valeur <code>0</code> pour le caractère)</li>
<li>Modifier la valeur du caractère récupéré en fonction des coordonnées à l'intérieur du caractère semi-graphique</li>
<li>Modifier la mémoire écran avec les nouvelles valeurs</li>
</ul>
<p>Prenons <strong>un exemple</strong> avec les coordonnées <code>(2, 7)</code> que j'avais déjà utilisées dans l'article sur les changements de repère. Les coordonnées du caractères sont <code>(0, 2)</code> et les coordonnées dans le caractères sont (0, 1).</p>
<p>À partir des coordonnées <code>(0, 2)</code>, je trouve que l'adresse mémoire est <code>&"4000"+160</code>. Une paire de <code>PEEK</code> me donne les valeurs déjà présentes <code>128</code> et <code>230</code>. Cela signifie qu'il y a un caractère graphique étendu à cet endroit. Je le considère donc comme un caractère éteint, de valeur 0.</p>
<p>À partir des coordonnées (0, 1), j'obtiens pour valeur de caractère <code>64+4 = 68</code>, qui sera donc la première valeur.</p>
<p>La seconde valeur, en fonction des couleurs sera <code>224</code>. Une paire de <code>POKE</code> avec ces valeurs allumera le bon point.</p>
<p><img alt="Des peeks" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/vg5000-peek.jpg"></p>
<p>Maintenant, que se passe-t-il si un caractère graphique est déjà présent ? Pour vérifier cela, je vais allumer le point aux coordonnées <code>(3, 7)</code>. Cela donne la paire de coordonnées <code>(0, 2)</code> et <code>(1, 1)</code>. C'est-à-dire les mêmes emplacements mémoires mais avec un point différent dans le caractère semi-graphique.</p>
<p>Les valeurs récupérées sont donc <code>68</code> et <code>224</code>, celles que je viens de calculer et mettre en mémoire avec <code>POKE</code>. <code>224</code> reste identique, puisque les couleurs sont fixes. Le numéro de caractère, lui, <strong>doit être changé</strong>. Le nouveau gros pixel à allumer est celui de valeur <code>8</code>. Je peux donc ajouter <code>8</code> à <code>68</code> pour donner <code>76</code>. Et cela marcherait.</p>
<p>Mais <strong>c'est faux dans le cas général</strong>.</p>
<p>L'addition ne fonctionne pas. Si j'allume deux fois le même gros pixel, il ne faut pas ajouter le numéro de ce pixel, puisqu'il est <strong>déjà pris en compte</strong>.</p>
<p>L'opération qu'il faut utiliser ici est <strong>une opération <code>OU</code></strong>. Je ne m'étendrai pas dessus ici. Retenez juste que faire un <code>OU</code> entre le numéro du caractère actuel et le <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">numéro associé au pixel</a> revient à ajouter ce numéro associé <strong>uniquement s'il n'est déjà pas pris en compte</strong> par le caractère.</p>
<p>En BASIC, le <code>OU</code> est écrit <code>OR</code>. <code>68 OR 8</code> donne <code>76</code>. Parfait.</p>
<p><img alt="Des pokes" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/vg5000-poke.jpg"></p>
<p>Voici le programme en BASIC, commenté, qui reprend ces opérations.</p>
<div class="highlight"><pre><span></span><code><span class="nl">100</span><span class="w"> </span><span class="c1">REM AFFICHE UN POINT DANS L'ESPACE SEMI-GRAPHIQUE</span>
<span class="nl">110</span><span class="w"> </span><span class="c1">REM X CONTIENT L'ABSCISSE ENTRE 0 et 79</span>
<span class="nl">120</span><span class="w"> </span><span class="c1">REM Y CONTIENT L'ABSCISSE ENTRE 0 et 74</span>
<span class="nl">130</span><span class="w"> </span><span class="vg">ZX</span><span class="o">=</span><span class="kr">INT</span><span class="p">(</span><span class="vg">X</span><span class="o">/</span><span class="il">2</span><span class="p">)</span><span class="o">:</span><span class="vg">ZY</span><span class="o">=</span><span class="kr">INT</span><span class="p">(</span><span class="vg">Y</span><span class="o">/</span><span class="il">3</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Calcul</span><span class="w"> </span><span class="vg">des</span><span class="w"> </span><span class="vg">coordonnées</span><span class="w"> </span><span class="vg">écran</span><span class="w"> </span><span class="p">(</span><span class="vg">ZX</span><span class="p">,</span><span class="w"> </span><span class="vg">ZY</span><span class="p">)</span>
<span class="nl">140</span><span class="w"> </span><span class="vg">RX</span><span class="o">=</span><span class="vg">X</span><span class="o">-</span><span class="vg">ZX</span><span class="o">*</span><span class="il">2</span><span class="o">:</span><span class="vg">RY</span><span class="o">=</span><span class="vg">Y</span><span class="o">-</span><span class="vg">ZY</span><span class="o">*</span><span class="il">3</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Calcul</span><span class="w"> </span><span class="vg">des</span><span class="w"> </span><span class="vg">coordonnées</span><span class="w"> </span><span class="vg">dans</span><span class="w"> </span><span class="vg">le</span><span class="w"> </span><span class="vg">caractère</span><span class="w"> </span><span class="p">(</span><span class="vg">RX</span><span class="p">,</span><span class="w"> </span><span class="vg">RY</span><span class="p">)</span>
<span class="nl">150</span><span class="w"> </span><span class="vg">CH</span><span class="o">=</span><span class="il">2</span><span class="o">^</span><span class="p">(</span><span class="vg">RY</span><span class="o">*</span><span class="il">2</span><span class="o">+</span><span class="vg">RX</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Calcul</span><span class="w"> </span><span class="vg">du</span><span class="w"> </span><span class="vg">numéro</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">pixel</span><span class="w"> </span><span class="vg">associé</span>
<span class="nl">160</span><span class="w"> </span><span class="vg">DI</span><span class="o">=&</span><span class="s2">"4000"</span><span class="o">+</span><span class="vg">ZY</span><span class="o">*</span><span class="il">80</span><span class="o">+</span><span class="vg">ZX</span><span class="o">*</span><span class="il">2</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Calcul</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">l</span><span class="c1">'emplacement mémoire écran</span>
<span class="nl">170</span><span class="w"> </span><span class="vg">AT</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">DI</span><span class="o">+</span><span class="il">1</span><span class="p">)</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Récupération</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">valeur</span><span class="w"> </span><span class="vg">d</span><span class="c1">'attribut</span>
<span class="nl">180</span><span class="w"> </span><span class="c1">REM Si c'est un caractère graphique, la valeur du caractère est récupérée dans OL</span>
<span class="nl">190</span><span class="w"> </span><span class="c1">REM Sinon, OL reste à 64</span>
<span class="nl">200</span><span class="w"> </span><span class="vg">OL</span><span class="o">=</span><span class="il">64</span><span class="o">:</span><span class="kr">IF</span><span class="w"> </span><span class="p">(</span><span class="vg">AT</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">128</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="il">128</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">OL</span><span class="o">=</span><span class="kr">PEEK</span><span class="p">(</span><span class="vg">DI</span><span class="p">)</span>
<span class="nl">210</span><span class="w"> </span><span class="c1">REM Si le caractère était étendu, OL est remis à 64</span>
<span class="nl">220</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="p">(</span><span class="vg">OL</span><span class="w"> </span><span class="ow">AND</span><span class="w"> </span><span class="il">128</span><span class="p">)</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="il">128</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="vg">OL</span><span class="o">=</span><span class="il">64</span>
<span class="nl">230</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="vg">DI</span><span class="p">,</span><span class="vg">OL</span><span class="w"> </span><span class="ow">OR</span><span class="w"> </span><span class="vg">CH</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Le</span><span class="w"> </span><span class="vg">caractère</span><span class="w"> </span><span class="vg">est</span><span class="w"> </span><span class="vg">augmenté</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">nouvelle</span><span class="w"> </span><span class="vg">valeur</span>
<span class="nl">240</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="vg">DI</span><span class="o">+</span><span class="il">1</span><span class="p">,</span><span class="il">224</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Les</span><span class="w"> </span><span class="vg">couleurs</span><span class="w"> </span><span class="vg">sont</span><span class="w"> </span><span class="vg">fixes</span>
<span class="nl">250</span><span class="w"> </span><span class="kr">RETURN</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Fin</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">routine</span><span class="p">,</span><span class="w"> </span><span class="vg">retour</span><span class="w"> </span><span class="vg">à</span><span class="w"> </span><span class="vg">l</span><span class="c1">'appelant</span>
</code></pre></div>
<p>Je ne m'étendrai pas non plus ici sur le <code>AND</code> en ligne <code>200</code>. Retenez que cette opération vérifie si le <strong>bit</strong> de rang 7, qui décrit <strong>si le caractère est semi-graphique</strong>, est à 1. De même sur la ligne <code>200</code>, où je vérifie si le <strong>bit</strong> de rang 7, qui décrit si le caractère est <strong>étendu</strong>, est à 1.</p>
<p>Et voici comment peut-être utilisé ce programme. X et Y sont initialisés avec les coordonnées, puis la routine en ligne 100 est appelée avec <code>GOSUB</code>.</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="vg">INIT</span>
<span class="nl">20</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">20</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="il">10</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">21</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">21</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="il">10</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">22</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">19</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="il">11</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">23</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">19</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="il">12</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">24</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">22</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="il">11</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">25</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">22</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="il">12</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">26</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">20</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="il">13</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">27</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">21</span><span class="o">:</span><span class="vg">Y</span><span class="o">=</span><span class="il">13</span><span class="o">:</span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">100</span>
<span class="nl">30</span><span class="w"> </span><span class="kr">END</span>
</code></pre></div>
<p>Tracer des images, ou tout simplement des objets géométriques de cette façon serait très fastidieux. On pourra améliorer ça dans un futur article.</p>
<p><img alt="Le résultat" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/vg5000-set-pixels.jpg"></p>
<p><img alt="Le listing" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/vg5000-set-pixels-listing.jpg"></p>VG5000µ, SetPoint en BASIC, manipulation de gros pixels2017-03-22T00:00:00+01:002017-03-22T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-03-22:/vg5000m-setpoint-en-basic-manipulation-de-gros-pixels.html<p>Dans <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-changements-de-reperes.html">l'épisode précédent</a>, j'avais décrit l'arrangement en mémoire vidéo des caractères semi-graphiques et un moyen d'accéder directement en mémoire à celui qui nous intéresse.</p>
<p>Le but est toujours de pouvoir allumer un élément constitutif d'un caractère semi-graphique, que je nomme « gros pixel ». Et l'article précédent s'arrêtait au moment où je …</p><p>Dans <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-changements-de-reperes.html">l'épisode précédent</a>, j'avais décrit l'arrangement en mémoire vidéo des caractères semi-graphiques et un moyen d'accéder directement en mémoire à celui qui nous intéresse.</p>
<p>Le but est toujours de pouvoir allumer un élément constitutif d'un caractère semi-graphique, que je nomme « gros pixel ». Et l'article précédent s'arrêtait au moment où je pouvais récupérer, via une paire d'appels à commande <code>PEEK</code>, les deux valeurs décrivant le caractère affiché à l'écran.</p>
<p>Premier test, après avoir effacé l'écran avec <code>INIT</code>, j'affiche les deux valeurs correspondantes à la colonne 1, ligne 0.</p>
<p><img alt="Peek écran" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/vg5000-peek-video.jpg"></p>
<p>Les deux valeurs obtenues et visibles sur la capture d'écran sont <code>79</code> et <code>0</code>. Pour comprendre ce qu'ils signifient, je reprends ici un morceau de l'article sur <a href="https://www.triceraprog.fr/vg5000m-arrangement-de-la-memoire-video.html">l'arrangement de la mémoire vidéo</a> qui décrit la structure de ces deux valeurs.</p>
<p>On y lit que la première valeur a cette structure :</p>
<table>
<thead>
<tr>
<th>Bit</th>
<th>7</th>
<th>6 / 5 / 4 / 3 / 2 / 1 / 0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Contenu</td>
<td>N (0)/E (1)</td>
<td>numéro du caractère</td>
</tr>
</tbody>
</table>
<p>Et que la seconde a celle-ci :</p>
<table>
<thead>
<tr>
<th>Bit</th>
<th>7</th>
<th>6</th>
<th>5</th>
<th>4</th>
<th>3</th>
<th>2 / 1 / 0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Contenu</td>
<td>TX (0)</td>
<td>Inverse vidéo</td>
<td>Largeur x2</td>
<td>Hauteur x2</td>
<td>Clignotement</td>
<td>Couleur</td>
</tr>
<tr>
<td>Contenu</td>
<td>GR (1)</td>
<td>Couleur ...</td>
<td>... de ...</td>
<td>... fond</td>
<td>Clignotement</td>
<td>Couleur</td>
</tr>
</tbody>
</table>
<h4>Compter jusqu'à deux</h4>
<p>Pour comprendre ce que ces tableaux signifient, il faut savoir et comprendre que les valeurs que j'ai obtenues et affichées le sont en <strong>base 10</strong>, c'est-à-dire sous la forme sous laquelle nous comptons usuellement dans la vie de tous les jours. En base 10, il y a 10 chiffres, et 0 à 9, et les nombres sont composés de ces chiffres. <code>79</code> est composé du chiffre <code>7</code> en position des dizaines et <code>9</code> en position des unités.</p>
<p>C'est d'ailleurs au programme du primaire à l'école, où l'on apprends que <code>79</code>, c'est 7 dizaines et 9 unités. Autrement dit, <code>7 × 10 + 9</code>. De même, <code>0</code> est composé de <code>0</code> unité.</p>
<p>Ces 10 chiffres sont <strong>arbitraires</strong>. On aurait pu, et d'autres civilisations l'ont fait, avoir un système de comptage avec <strong>20 chiffres</strong>. Pourquoi pas. En programmation, il est usuel de compter en base 10, bien entendu, mais aussi en <strong>base 8</strong>, donc en ne prenant que les chiffres de 0 à 7, en <strong>base 16</strong>, où nous avons besoins de chiffres supplémentaires au-delà du 9 (qui sont notés A, B, C, D, E et F) et... en <strong>base 2</strong>, où <strong>seuls les chiffres 0 et 1</strong> sont utilisés.</p>
<p><em>Le pourquoi de ces bases sort du cadre de cet article. J'y reviendrai probablement une autre fois. Je ne vais pas non plus expliquer ici les conversions d'écritures entre ces bases.</em></p>
<p>Ces chiffres, <code>0</code> et <code>1</code> dont on va se servir se nomment <strong>chiffres binaires</strong>, en anglais <strong>Binary Digits</strong>, raccourcis en <strong>bit</strong>. C'est ce <strong>bit</strong> qui est mentionné dans les tableaux ci-dessus.</p>
<p>Les valeurs que nous avons obtenues sont, de part l'organisation de l'ordinateur, des <strong>valeurs entre 0 et 255</strong>, et il se trouve que ces valeurs peuvent toutes s'écrire avec 8 chiffres binaires. Cette organisation en groupe de 8 bits s'appelle en <strong>octet</strong>.</p>
<h4>On reprend son souffle</h4>
<p>Cela fait beaucoup de vocabulaire d'un coup. Alors reprenons. Les valeurs que nous avons obtenues avec <code>PEEK</code> :</p>
<ul>
<li>Sont toujours comprises entre <code>0</code> et <code>255</code>,</li>
<li>Peuvent s'écrire en <strong>base 2</strong> avec 8 bits (chiffres binaires) maximum,</li>
<li>Ces groupements de 8 bits, s'appellent des <strong>octets</strong>.</li>
</ul>
<p>À quoi ressemblent ces deux valeurs écrites en base 2 ? Les voici :</p>
<ul>
<li><code>79</code> (en base 10), s'écrit <code>01001111</code> (en base 2)</li>
<li><code>0</code> (en base 10), s'écrit <code>00000000</code> (en base 2)</li>
</ul>
<p>Tout comme en base 10, le 0 en tête du <code>01001111</code> est optionnel, mais je les garde ici pour bien faire la différence entre les deux notations, et pour montrer que ces deux valeurs écrites en binaires s'associent parfaitement aux cases des tableaux ci-dessus.</p>
<p><em>Dernier petit détail, dans les tableaux, le rang du chiffre binaire commence par le rang 0, à droite, puis va jusqu`au rang 7, à gauche. Cela donne bien 8 chiffres/colonnes.</em></p>
<h4>Décodage, enfin</h4>
<p>On a maintenant tous les éléments pour comprendre les deux valeurs récupérées au début de cet article. Le premier octet, de valeur <code>79</code>, écrit en base 2 et remis en place dans le tableau de décodage, donne ceci :</p>
<table>
<thead>
<tr>
<th>Bit</th>
<th>7</th>
<th>6 / 5 / 4 / 3 / 2 / 1 / 0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Contenu</td>
<td>0</td>
<td>1001111</td>
</tr>
</tbody>
</table>
<p>Le second octet, de valeur <code>0</code>, mis en base 2 et placé dans le tableau de décodage donne ceci :</p>
<table>
<thead>
<tr>
<th>Bit</th>
<th>7</th>
<th>6</th>
<th>5</th>
<th>4</th>
<th>3</th>
<th>2 / 1 / 0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Contenu</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>0</td>
<td>000</td>
</tr>
</tbody>
</table>
<p>Ce qui se lit :</p>
<ul>
<li><strong>Première valeur</strong> : il s'agit d'un caractère non étendu (bit 7 vaut 0) dont le numéro est 79</li>
<li><strong>Seconde valeur</strong> : il s'agit d'un caractère textuel, de hauteur et largeur normale, sans clignotement, et de couleur 0 (noir)</li>
</ul>
<p>Un rapide coup d’œil sur la table de caractère nous renseigne que le <strong>caractère normal</strong> (non étendu) de rang <code>79</code> est le caractère <code>O</code>. Nous avons bien lu le <code>O</code> de <code>Ok!</code> en haut de l'écran.</p>
<h4>Et maintenant, l'encodage</h4>
<p>La commande pour modifier la valeur d'un emplacement mémoire, c'est-à-dire l'inverse de <code>PEEK</code>, est <code>POKE</code>. Cette commande prend un numéro d'<strong>emplacement mémoire</strong> et une <strong>valeur</strong> pour se charger de mettre cette valeur à cet emplacement mémoire.</p>
<p>Exemple rapide en faisant <code>POKE &"4000"+2,80</code> pour voir que le <code>O</code> de <code>Ok!</code> se transforme en <code>P</code>.</p>
<p><img alt="Poke écran" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/vg5000-poke-video.jpg"></p>
<p>Ce que je voudrais maintenant, c'est utiliser des caractères semi-graphiques pour tracer une <strong>ligne horizontale</strong> au milieu de l'écran. En reprenant la méthode décrite dans l'article sur la <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">constitution des caractères semi-graphiques</a>, je calcule que les deux pixels du milieu allumés donnent le caractère numéro 76, ce que je peux vérifier sur le tableau des caractères semi-graphiques.</p>
<p>D'après le calcul pour trouver <a href="https://www.triceraprog.fr/vg5000m-setpoint-en-basic-changements-de-reperes.html">l'emplacement mémoire</a> du milieu de l'écran, ligne 12, colonne 0, j'obtiens <code>960</code>, que je devrai ajouter à l'adresse de début de la mémoire vidéo <code>&"4000"</code>.</p>
<p>Dans les 20 paires de valeurs à partir de cet emplacement, je dois placer le caractère semi-graphique numéro <code>76</code> (<code>1001100</code> en binaire sur 7 bits) non étendu, sans clignotement, avec comme couleur de fond la couleur <code>6</code> (<code>110</code> en binaire sur 3 bits) et la couleur d'encre <code>0</code> (<code>000</code> en binaire sur 3 bits), qui sont les couleurs par défaut à l'allumage du VG5000µ (<code>INIT 6</code>).</p>
<p>Cela donne dans les tableaux d'encodage (qui sont toujours les mêmes) :</p>
<table>
<thead>
<tr>
<th>Bit</th>
<th>7</th>
<th>6 / 5 / 4 / 3 / 2 / 1 / 0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Contenu</td>
<td>0</td>
<td>1001100</td>
</tr>
</tbody>
</table>
<table>
<thead>
<tr>
<th>Bit</th>
<th>7</th>
<th>6 / 5 / 4</th>
<th>3</th>
<th>2 / 1 / 0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Contenu</td>
<td>1</td>
<td>110</td>
<td>0</td>
<td>000</td>
</tr>
</tbody>
</table>
<p>Soit, une fois convertis en base 10, les valeurs <code>76</code> et <code>224</code>.</p>
<h4>Et enfin, le programme</h4>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="vg">INIT</span><span class="w"> </span><span class="il">6</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">On</span><span class="w"> </span><span class="vg">efface</span><span class="w"> </span><span class="vg">l</span><span class="c1">'écran</span>
<span class="nl">20</span><span class="w"> </span><span class="vg">P</span><span class="o">=&</span><span class="s2">"4000"</span><span class="o">+</span><span class="il">960</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">La</span><span class="w"> </span><span class="vg">variable</span><span class="w"> </span><span class="vg">P</span><span class="w"> </span><span class="vg">est</span><span class="w"> </span><span class="vg">positionnée</span><span class="w"> </span><span class="vg">en</span><span class="w"> </span><span class="vg">début</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">ligne</span><span class="w"> </span><span class="il">12</span>
<span class="nl">30</span><span class="w"> </span><span class="kr">FOR</span><span class="w"> </span><span class="vg">X</span><span class="o">=</span><span class="il">1</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">40</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">On</span><span class="w"> </span><span class="vg">effectue</span><span class="w"> </span><span class="il">40</span><span class="w"> </span><span class="vg">fois</span><span class="w"> </span><span class="vg">les</span><span class="w"> </span><span class="vg">instructions</span><span class="w"> </span><span class="vg">suivantes</span>
<span class="nl">40</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="vg">P</span><span class="p">,</span><span class="il">76</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">La</span><span class="w"> </span><span class="vg">première</span><span class="w"> </span><span class="vg">valeur</span><span class="w"> </span><span class="vg">est</span><span class="w"> </span><span class="il">76</span>
<span class="nl">50</span><span class="w"> </span><span class="kr">POKE</span><span class="w"> </span><span class="vg">P</span><span class="o">+</span><span class="il">1</span><span class="p">,</span><span class="il">224</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">La</span><span class="w"> </span><span class="vg">valeur</span><span class="w"> </span><span class="vg">suivante</span><span class="w"> </span><span class="vg">est</span><span class="w"> </span><span class="il">224</span>
<span class="nl">60</span><span class="w"> </span><span class="vg">P</span><span class="o">=</span><span class="vg">P</span><span class="o">+</span><span class="il">2</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">La</span><span class="w"> </span><span class="vg">variable</span><span class="w"> </span><span class="vg">P</span><span class="w"> </span><span class="vg">est</span><span class="w"> </span><span class="vg">avancée</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="il">2</span><span class="p">,</span><span class="w"> </span><span class="vg">afin</span><span class="w"> </span><span class="vg">de</span><span class="w"> </span><span class="vg">traiter</span><span class="w"> </span><span class="vg">la</span><span class="w"> </span><span class="vg">paire</span><span class="w"> </span><span class="vg">suivante</span>
<span class="nl">70</span><span class="w"> </span><span class="kr">NEXT</span><span class="w"> </span><span class="vg">X</span><span class="w"> </span><span class="o">:</span><span class="w"> </span><span class="kr">REM</span><span class="w"> </span><span class="vg">Fin</span><span class="w"> </span><span class="vg">des</span><span class="w"> </span><span class="vg">instructions</span><span class="w"> </span><span class="vg">à</span><span class="w"> </span><span class="vg">répéter</span><span class="w"> </span><span class="il">40</span><span class="w"> </span><span class="vg">fois</span>
</code></pre></div>
<p>Voici une belle ligne horizontale à base d'un caractère semi graphique choisi et positionné grâce à une série de calculs. Il n'y a plus long avant de pouvoir choisir directement quel point de l'écran afficher !</p>
<p><img alt="Poke écran" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/vg5000-ligne-horizontale.jpg"></p>VG5000µ, SetPoint en BASIC, changements de repères2017-03-15T00:00:00+01:002017-03-15T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-03-15:/vg5000m-setpoint-en-basic-changements-de-reperes.html<p>Après avoir fait un mini tour des <a href="https://www.triceraprog.fr/les-commandes-graphiques-en-basic-du-vg5000m.html">commandes graphiques</a>, puis étudié la structure des <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">caractères semi-graphiques</a> du VG5000µ, il est temps d'implémenter la possibilité d'allumer un point particulier de l'écran.</p>
<p>Comme je vais utiliser les <strong>caractères semi-graphiques</strong>, ce « point » sera sur la grille de résolution de 80 par 75. En …</p><p>Après avoir fait un mini tour des <a href="https://www.triceraprog.fr/les-commandes-graphiques-en-basic-du-vg5000m.html">commandes graphiques</a>, puis étudié la structure des <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">caractères semi-graphiques</a> du VG5000µ, il est temps d'implémenter la possibilité d'allumer un point particulier de l'écran.</p>
<p>Comme je vais utiliser les <strong>caractères semi-graphiques</strong>, ce « point » sera sur la grille de résolution de 80 par 75. En effet, il y a 40 caractères par ligne qui ont chacun une largeur de deux éléments, et 25 caractères par colonne qui ont chacun trois éléments en hauteur.</p>
<p>L'idée pour allumer ou éteindre un point est donc d'abord de <strong>trouver quelle position</strong> de caractère semi-graphique <strong>est influencée</strong>, puis de modifier le caractère à cette position. Pour modifier le caractère, il faudra lire celui déjà présent, calculer le nouveau, puis <strong>écrire le nouveau</strong>.</p>
<h4>Transformation de coordonnées</h4>
<p>Pour visualiser la transformation de coordonnées, voici un tableau.</p>
<div class="highlight"><pre><span></span><code>|-------------|-------------|-------------|-------------|-------------|
| (0,0) (1,0) | (1,0) (2,0) | (3,0) (4,0) | (5,0) (6,0) | ......
| (0,1) (1,1) | (1,1) (2,1) | (3,1) (4,1) | (5,1) (6,1) | ..... etc.
| (0,2) (1,2) | (1,2) (2,2) | (3,2) (4,2) | (5,2) (6,2) | ....
| [0,0] | [1,0] | [2,0] | [3,0] | ....
|-------------|-------------|-------------|-------------|------
| (0,3) (1,3) | (1,3) (2,3) | (3,3) (4,3) | (5,3) (6,3) |
| (0,4) (1,4) | (1,4) (2,4) | (3,4) (4,4) | (5,4) (6,4) |
| (0,5) (1,5) | (1,5) (2,5) | (3,5) (4,5) | (5,5) (6,5) |
| [0,1] | [1,1] | [2,1] | [3,1] |
|-------------|-------------|-------------|-------------|---
| (0,6) (1,6) | (1,6) (2,6) | (3,6) (4,6) | (5,6) (6,6) |
| (0,7) (1,7) | (1,7) (2,7) | (3,7) (4,7) | (5,7) (6,7) |
| (0,8) (1,8) | (1,8) (2,8) | (3,8) (4,8) | (5,8) (6,8) |
| [0,2] | [1,2] | [2,2] | [3,2] |
|-------------|-------------|-------------|-------------|-
| . . | . . | . . | . . |
| . . | . . | . . | .
| . . | . . | . .
</code></pre></div>
<p>Entre parenthèse, j'ai placé tous les couples de coordonnées qui nous intéressent. Entre crochet, j'ai placé les coordonnées du caractère correspondant.</p>
<p>Pour passer du premier système de coordonnées à l'autre, nous utilisons un <strong>outil mathématique</strong> très simple : <strong>la division</strong>. Prenez une coordonnée « gros pixel », divisez par 2 sa coordonnées X, divisez par 3 sa coordonnée Y (en arrondissant à l'entier inférieur), et vous obtenez les coordonnées du caractère semi-graphique à modifier.</p>
<p>Par exemple : <code>(2, 7)</code> devient <code>(2÷2, 7÷3)</code> ce qui fait <code>(0, 2)</code>. Sept divisé par trois de manière entière faisant 2.</p>
<p>À présent que l'on sait trouver les coordonnées du caractère semi-graphique, il nous faut trouver <strong>lequel des six éléments est à modifier</strong>. Et pour cela, nous utilisons encore la division entière, mais cette fois en prenant <strong>son reste</strong>.</p>
<p>En continuant sur le même exemple : <code>(2, 7)</code> devient <code>(reste de 2÷2, reste de 7÷3)</code>, ce qui fait <code>(0, 1)</code>. C'est-à-dire l'élément de la colonne de gauche (0), sur la ligne du centre (1).</p>
<p>Pour transformer l’élément semi-graphique aux coordonnées (2, 7), il faudra donc allumer l'élément semi graphique de la <strong>colonne de gauche</strong>, <strong>ligne du centre</strong>, du caractère positionné aux coordonnées (0, 2).</p>
<p>Ce que nous venons de faire est un <strong>changement de repère</strong>.</p>
<p>Voici le même tableau avec l'élément exemple mis en évidence :</p>
<div class="highlight"><pre><span></span><code>|-------------|-------------|-------------|-------------|-------------|
| (0,0) (1,0) | (1,0) (2,0) | (3,0) (4,0) | (5,0) (6,0) | ......
| (0,1) (1,1) | (1,1) (2,1) | (3,1) (4,1) | (5,1) (6,1) | ..... etc.
| (0,2) (1,2) | (1,2) (2,2) | (3,2) (4,2) | (5,2) (6,2) | ....
| [0,0] | [1,0] | [2,0] | [3,0] | ....
|-------------|-------------|-------------|-------------|------
| (0,3) (1,3) | (1,3) (2,3) | (3,3) (4,3) | (5,3) (6,3) |
| (0,4) (1,4) | (1,4) (2,4) | (3,4) (4,4) | (5,4) (6,4) |
| (0,5) (1,5) | (1,5) (2,5) | (3,5) (4,5) | (5,5) (6,5) |
| [0,1] | [1,1] | [2,1] | [3,1] |
|=============|-------------|-------------|-------------|---
I I (1,6) (2,6) | (3,6) (4,6) | (5,6) (6,6) |
I (1,7) I (1,7) (2,7) | (3,7) (4,7) | (5,7) (6,7) |
I I (1,8) (2,8) | (3,8) (4,8) | (5,8) (6,8) |
I [0,2] I [1,2] | [2,2] | [3,2] |
|=============|-------------|-------------|-------------|-
| . . | . . | . . | . . |
| . . | . . | . . | .
| . . | . . | . .
</code></pre></div>
<h4>Transformation du caractère</h4>
<p>À présent que l'on connaît les coordonnées du <strong>caractère à modifier</strong>, il faut trouver un moyen de savoir quelle est sa <strong>valeur actuelle</strong>. Et là-dessus, le <strong>BASIC</strong> du VG5000µ ne nous aide pas. Il y bien une commande d'affichage à l'écran, <code>PRINT</code>, mais pas de commande spécifique pour connaître le caractère actuellement affiché à l'écran pour une position donnée.</p>
<p>Heureusement, <a href="https://www.triceraprog.fr/vg5000m-arrangement-de-la-memoire-video.html">cet article</a> décrit l'arrangement en mémoire des informations d'affichage telles que gérées par le <strong>BASIC</strong>. Il est donc possible, moyennant un autre changement de coordonnées et de l'instruction <code>PEEK</code> du BASIC, d'obtenir cette information.</p>
<p>L'instruction <code>PEEK</code> prend en paramètre une adresse mémoire et renvoie son contenu sous la forme d'un octet, c'est-à-dire, d'un entier allant de 0 à 255. C'est une instruction, avec son pendant <code>POKE</code> qui permet de modifier le contenu d'une adresse mémoire, qui permet au <strong>BASIC</strong> d'interagir avec la machine au delà des instructions toutes faites. Nous y reviendrons dans d'autres articles.</p>
<p>En lisant l'article sur l'arrangement mémoire des informations écran, il est facile de convertir les coordonnées d'un caractère à l'écran en adresse mémoire. Chaque ligne prend 80 octets, il faut donc <strong>multiplier</strong> le numéro de la <strong>ligne</strong> par 80. Et chaque caractère prend deux octets sur une ligne, il faut donc <strong>multiplier</strong> le numéro de la <strong>colonne</strong> par 2.</p>
<p>Dans notre exemple, <code>(0, 2)</code> donne <code>(0×2)+(2×80) = 160</code>. L'emplacement est donc le 160ième depuis le début de la mémoire vidéo. Plus exactement 160ième et 161ième puisque chaque caractère prend deux octets. Encore un <strong>changement de repère</strong>.</p>
<p>Il reste donc à décoder cette information, la modifier et la replacer en mémoire. Ceci sera pour la prochaine fois.</p>
<p><img alt="Repère cartésien" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/vg5000-repere.png"></p>VG5000µ, Arrangement de la mémoire vidéo2017-03-08T00:00:00+01:002017-03-08T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-03-08:/vg5000m-arrangement-de-la-memoire-video.html<p>Cet article est la version française de <a href="https://www.triceraprog.fr/en/vg5000m-arrangement-de-la-memoire-video.html">cet autre</a> que j'ai écrit suite à une demande d'aide dans le <a href="http://forum.system-cfg.com/viewtopic.php?f=25&t=7902&p=126481">forum System.cfg forum</a>. Je me suis dit que c'était le moment de mettre à plat mes notes, même si du coup cela cassait un peu la progression des articles en …</p><p>Cet article est la version française de <a href="https://www.triceraprog.fr/en/vg5000m-arrangement-de-la-memoire-video.html">cet autre</a> que j'ai écrit suite à une demande d'aide dans le <a href="http://forum.system-cfg.com/viewtopic.php?f=25&t=7902&p=126481">forum System.cfg forum</a>. Je me suis dit que c'était le moment de mettre à plat mes notes, même si du coup cela cassait un peu la progression des articles en français sur l'affichage.</p>
<p>Cela donne un article un peu plus austère que d'habitude, et un peu plus dense.</p>
<p>Pour commencer, le processeur vidéo du VG5000µ est un <strong>EF9345</strong>. On peut y accéder de deux façons, soit en discutant directement, soit à travers la RAM lorsqu'on utilise le BASIC embarqué dans la machine. La manière directe est plus puissante, la seconde est plus simple.</p>
<p>Le signal de début d'affichage (Blank Start) du processeur vidéo est connecté à la broche d'interruption du Z80. Cela provoque une interruption <strong>IRQ</strong> qui, dans le mode dans lequel le BASIC initialise le Z80, fait un appel à l'adresse <code>0x0038</code> en tant qu'interruption.</p>
<p>La routine IRQ utilises des drapeaux (flags) pour décider si le contenu de la RAM doit être envoyé au EF9345. Pour cela, le moniteur regarde les valeurs en <code>0x47FA</code> (compteur décrémenté jusqu'à 0), <code>0x47FB</code> (force un rafraîchissement si différent de 0) et <code>0x47FC</code> (valeur de référence pour le compteur en 0x47FA). En BASIC, cela correspond à la commande <code>DISPLAY</code>.</p>
<p>Si le rafraîchissement est enclenché, la <strong>mémoire vidéo est décodée</strong> et transférée au EF9345. Pour avoir une idée de comment est effectué cette opération, sur une version 1.1 du BASIC, cela commence à l'adresse <code>0x0056</code>, puis un peu plus loin en <code>0x00bc</code> et se termine en <code>0x018b</code>. Le registre <code>ix</code> est toujours à la valeur <code>0x47fa</code> lorsque le BASIC est en route. Il se sert de ce registre d'index pour repérer les variables d'affichage (ne touchez pas à <code>ix</code> si vous laissez le système BASIC gérer la vidéo, ou alors en masquant les interruptions).</p>
<p>La mémoire vidéo est de la <strong>mémoire RAM standard</strong>. Elle n'est pas branchée d'une manière spécifique et si vous voulez contrôler l'affichage vous même, vous pouvez utiliser n'importe quel endroit disponible et le format qui vous chante. L'arrangement utilisé par le BASIC en ROM, lui, commence en 0x4000. L'arrangement ligne/colonne est comme suit :</p>
<table>
<thead>
<tr>
<th>Offset (decimal)</th>
<th>Colonne 0</th>
<th>Colonne 1</th>
<th>Colonne 2</th>
<th>...</th>
<th>Colonne 38</th>
<th>Colonne 39</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ligne 0</td>
<td>+0/+1</td>
<td>+2/+3</td>
<td>+4/+5</td>
<td>...</td>
<td>+76/+77</td>
<td>+78/+79</td>
</tr>
<tr>
<td>Ligne 1</td>
<td>+80/+81</td>
<td>+82/+83</td>
<td>+84/+85</td>
<td>...</td>
<td>+156/+157</td>
<td>+158/+159</td>
</tr>
<tr>
<td>Ligne 2</td>
<td>+160/+161</td>
<td>+162/+163</td>
<td>+164/+165</td>
<td>...</td>
<td>+236/+237</td>
<td>+238/+239</td>
</tr>
<tr>
<td>...</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Ligne 24</td>
<td>+1840/+1841</td>
<td>+1842/+1843</td>
<td>+1844/+1845</td>
<td>...</td>
<td>+1916/+1917</td>
<td>+1918/+1919</td>
</tr>
<tr>
<td>Ligne 25</td>
<td>+1920/+1921</td>
<td>+1922/+1923</td>
<td>+1924/+1925</td>
<td>...</td>
<td>+1996/+1997</td>
<td>+1998/+1999</td>
</tr>
</tbody>
</table>
<p>Chaque <strong>unité affichable</strong> est codée sur une <strong>paire d'octets</strong>. Le premier octet contient principalement le numéro du caractère. Le second octet contient principalement ses attributs. La page de caractères est sélectionnée avec 1 bit dans chaque octet.</p>
<p>Le premier octet de la paire se présente comme suit :</p>
<table>
<thead>
<tr>
<th>Bit</th>
<th>7</th>
<th>6 / 5 / 4 / 3 / 2 / 1 / 0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Contenu</td>
<td>N (0)/E (1)</td>
<td>numéro du caractère</td>
</tr>
</tbody>
</table>
<p>Le second octet de la paire se présente comme suit :</p>
<table>
<thead>
<tr>
<th>Bit</th>
<th>7</th>
<th>6</th>
<th>5</th>
<th>4</th>
<th>3</th>
<th>2 / 1 / 0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Contenu</td>
<td>TX (0)</td>
<td>Inverse vidéo</td>
<td>Largeur x2</td>
<td>Hauteur x2</td>
<td>Clignotement</td>
<td>Couleur</td>
</tr>
<tr>
<td>Contenu</td>
<td>GR (1)</td>
<td>Couleur ...</td>
<td>... de ...</td>
<td>... fond</td>
<td>Clignotement</td>
<td>Couleur</td>
</tr>
</tbody>
</table>
<p>Grâce à <strong>N/E</strong> (Normal/Étendu) et <strong>TX/GR</strong>, la page de caractères est choisie. Cela correspond aux diverses sélection de caractères en BASIC. TX/Normal est <code>TX</code>, TX/Étendu est <code>ET</code>, GR/Normal est <code>GR</code>, GR/Étendu est <code>EG</code>. Le mode étendu permet de redéfinir les caractères. Le texte normal est ASCII, et le graphique normal contient les <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">caractères semi-graphiques</a>.</p>
<p>Comme vous pouvez le voir, il n'y a pas de moyen pour <strong>spécifier une couleur de fond en mode texte</strong>. La couleur de fond utilisée est la couleur de fond fixée par le dernier caractère graphique précédent sur la même ligne. C'est pour cela que lorsqu'on utilise la commande <code>INIT</code> du BASIC pour effacer l'écran, la colonne 0 est remplie avec un caractère graphique invisible ayant pour couleur de fond la couleur spécifiée. Et c'est aussi pour ça que le BASIC n'utilise pas la première colonne.</p>
<p><strong>Un dernier mot</strong>: le EF9345 est beaucoup plus puissant que ce qui est induit par son utilisation par le VG5000µ. Vous pouvez trouver sur Internet des ressources pour l'utiliser en 80 colonnes, ou bien avec plus de 7 couleurs. Cela nécessite plus de travail car il faut alors communiquer directement avec le <strong>EF9345</strong> à travers les ports I/O 0x8F et 0xCF du Z80.</p>VG5000µ, Video Memory Layout2017-03-08T00:00:00+01:002017-03-08T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-03-08:/en/vg5000m-arrangement-de-la-memoire-video.html<p>This post is in English following request for assistance on <a href="http://forum.system-cfg.com/viewtopic.php?f=25&t=7902&p=126481">System.cfg forum</a> for details on how to access the Video system on the VG5000µ. As most of the reference is in French, here's an explanation of the video memory layout when running the BASIC System. A French version of …</p><p>This post is in English following request for assistance on <a href="http://forum.system-cfg.com/viewtopic.php?f=25&t=7902&p=126481">System.cfg forum</a> for details on how to access the Video system on the VG5000µ. As most of the reference is in French, here's an explanation of the video memory layout when running the BASIC System. A French version of this article will follow shortly.</p>
<p>For starters, the video processor on the VG5000µ is <strong>EF9345</strong>. There's a possibility to access it directly to leverage all its power, but the simplest way, when under the BASIC System, is the work with the RAM.</p>
<p>The Blank Start signal of the video processor is connected to the interrupt pin of the Z80, thus triggering an <strong>IRQ</strong> which, under the interrupt mode the BASIC initializes the Z80, calls address <code>0x0038</code> as an interrupt.</p>
<p>The IRQ routine uses flags to decide is the RAM content should be sent to the EF9345 considering <code>0x47FA</code> (counter down to 0), <code>0x47FB</code> (force refresh if not 0), <code>0x47FC</code> (reference value for 0x47FA). In BASIC, this corresponds to the <code>DISPLAY</code> command.</p>
<p>If the refresh is triggered, then the <strong>Video Memory is decoded</strong> and transferred to the EF9345. To check how it's done, on a version 1.1 BASIC, this really starts at <code>0x0056</code>, jumping to 0x00bc and ending in <code>0x018b</code>. Register <code>ix</code> is always <code>0x47fa</code> when running the BASIC, this is to know to understand where the display variables are fetch from (don't temper with <code>ix</code> if you want to let the BASIC system handle video, or do it with interruption disabled).</p>
<p>Video RAM is <strong>standard RAM</strong> not wired in any particular way, if you want to take control of the display and put it anywhere and in any format you want. The layout used by the BASIC ROM starts at 0x4000. The line/column layout is as follow:</p>
<table>
<thead>
<tr>
<th>Offset (decimal)</th>
<th>Column 0</th>
<th>Column 1</th>
<th>Column 2</th>
<th>...</th>
<th>Column 38</th>
<th>Column 39</th>
</tr>
</thead>
<tbody>
<tr>
<td>Ligne 0</td>
<td>+0/+1</td>
<td>+2/+3</td>
<td>+4/+5</td>
<td>...</td>
<td>+76/+77</td>
<td>+78/+79</td>
</tr>
<tr>
<td>Ligne 1</td>
<td>+80/+81</td>
<td>+82/+83</td>
<td>+84/+85</td>
<td>...</td>
<td>+156/+157</td>
<td>+158/+159</td>
</tr>
<tr>
<td>Ligne 2</td>
<td>+160/+161</td>
<td>+162/+163</td>
<td>+164/+165</td>
<td>...</td>
<td>+236/+237</td>
<td>+238/+239</td>
</tr>
<tr>
<td>...</td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
<td></td>
</tr>
<tr>
<td>Ligne 24</td>
<td>+1840/+1841</td>
<td>+1842/+1843</td>
<td>+1844/+1845</td>
<td>...</td>
<td>+1916/+1917</td>
<td>+1918/+1919</td>
</tr>
<tr>
<td>Ligne 25</td>
<td>+1920/+1921</td>
<td>+1922/+1923</td>
<td>+1924/+1925</td>
<td>...</td>
<td>+1996/+1997</td>
<td>+1998/+1999</td>
</tr>
</tbody>
</table>
<p>Every <strong>displayable unit</strong> is encoded into a <strong>pair of bytes</strong>. The first byte is mainly the character code, the second byte contains mainly the attributes. Character set is selected by a bit in each byte.</p>
<p>First byte of the pair has this layout:</p>
<table>
<thead>
<tr>
<th>Bit</th>
<th>7</th>
<th>6 / 5 / 4 / 3 / 2 / 1 / 0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Content</td>
<td>N (0)/E (1)</td>
<td>character number</td>
</tr>
</tbody>
</table>
<p>The second byte of the pair has this layout:</p>
<table>
<thead>
<tr>
<th>Bit</th>
<th>7</th>
<th>6</th>
<th>5</th>
<th>4</th>
<th>3</th>
<th>2 / 1 / 0</th>
</tr>
</thead>
<tbody>
<tr>
<td>Content</td>
<td>TX (0)</td>
<td>Reverse</td>
<td>Width x2</td>
<td>Height x2</td>
<td>Blink</td>
<td>Color</td>
</tr>
<tr>
<td>Content</td>
<td>GR (1)</td>
<td>Background ...</td>
<td>...</td>
<td>... color</td>
<td>Blink</td>
<td>Foreground Color</td>
</tr>
</tbody>
</table>
<p>With <strong>N/E</strong> (Normal/Extended) and <strong>TX/GR</strong>, the selection of the character set is made. This corresponds to the various graphic selections in BASIC. Normal TX is <code>TX</code>, Extended TX is <code>ET</code>, Normal GR is <code>GR</code>, Extended GR is <code>EG</code>. Extended mode allows character redefinition. Normal text is ASCII, normal graphics is the Semi-Graphic character set.</p>
<p>As you can see, there's no way to <strong>specify a background</strong> color attribute <strong>in text mode</strong>. The background color is the latest background color as fixed by the previous graphic character attribute on the same line. That's why, when using the BASIC <code>INIT</code> command to clear the screen, every character in column 0 is set to an invisible graphic character with the clear color. And that's why the BASIC monitor doesn't use the first column.</p>
<p><strong>Last note</strong>: EF9345 has much more capabilities than the layout implied by the BASIC Monitor. You can find on Internet resources that handles 80 column mode, or mode that the 7 colors. That needs a bit more work, as it needs to communicates completely with the <strong>EF9345</strong> through 0x8F and 0xCF I/O ports.</p>Human Resource Machine, le jeu assembleur2017-03-04T00:00:00+01:002017-03-04T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-03-04:/human-resource-machine-le-jeu-assembleur.html<p><a href="http://tomorrowcorporation.com/humanresourcemachine">Human Resource Machine</a> est un jeu de <strong>Tomorrow Corporation</strong> de type résolution de défi. Ces défis se présentent tous sous la même formes : une série de chiffres et/ou lettres à transformer en une autre série de chiffres et/ou lettres, en fonction de directives.</p>
<p>Et pour cela, le joueur …</p><p><a href="http://tomorrowcorporation.com/humanresourcemachine">Human Resource Machine</a> est un jeu de <strong>Tomorrow Corporation</strong> de type résolution de défi. Ces défis se présentent tous sous la même formes : une série de chiffres et/ou lettres à transformer en une autre série de chiffres et/ou lettres, en fonction de directives.</p>
<p>Et pour cela, le joueur a à sa disposition une <strong>série de commandes</strong>. Au début, très peu, leur nombre s'étoffe au fur et à mesure des 42 niveaux de la ligne principale. Ces commandes sont exécutées par l'avatar du joueur, qui va courir d'un bout à l'autre d'une pièce pour prendre des données, les transformer, les ajoutes, les comparer, les stocker...</p>
<p>C'est ici que cela m'intéresse dans le cadre de Triceraprog. Le personnage que contrôle le joueur simule le fonctionnement d'un ordinateur (en parti). Le joueur au fur et à mesure de la résolution des puzzle apprend « tout simplement » un <strong>langage d'assemblage</strong>.</p>
<p>Un langage d'assemblage, ou langage assembleur, est un langage très proche des opérations effectuées par un processeur d'ordinateur. Résoudre des problèmes avec ce seul langage est assez fastidieux et c'est sur ce ressort que joue Human Resource Machine.</p>
<p>Le joueur a la possibilité de copier/coller les programmes du jeu, et ce qui est intéressant est que ce qui est sauvé dans le presse-papier ressemble à un vrai langage d'assemblage pour une machine inventée pour l'occasion. Voici un <strong>exemple de résolution</strong> d'un des premiers niveaux du jeu, copié dans un éditeur de texte :</p>
<div class="highlight"><pre><span></span><code>-- HUMAN RESOURCE MACHINE PROGRAM --
a:
INBOX
COPYTO 0
b:
c:
INBOX
JUMPZ e
SUB 0
JUMPN d
JUMP c
d:
ADD 0
COPYTO 0
JUMP b
e:
COPYFROM 0
OUTBOX
JUMP a
</code></pre></div>
<p>En termes plus clairs (attention soyez attentif !) :</p>
<ol>
<li>le programme indique qu'il faut prendre une donnée dans celle qui se présentent en entrée (INBOX)</li>
<li>la copier dans une case mémoire nommée 0 (COPYTO 0)</li>
<li>puis prendre une autre donnée</li>
<li>aller à la ligne indiquer e: si cette donnée est égale à zéro (JUMPZ).</li>
<li>Dans le cas contraire, retrancher le contenu de la case 0 à la donnée courante (SUB 0)</li>
<li>Si le résultat est négatif, allez en d:</li>
<li>
<p>Sinon revenir en c:</p>
</li>
<li>
<p>Dans le cas où le résultat était négatif et que l'on est maintenant en d:, cela signifie que la nouvelle donnée était plus petite que celle dans la case 0.</p>
</li>
<li>La nouvelle donnée est rétablie (ADD 0... on ajoute ce qu'on avait retranché) et est mise dans la case 0. </li>
<li>
<p>On revient en b: pour lire une nouvelle donnée</p>
</li>
<li>
<p>Dans le cas où la donnée était égale à 0, on arrive en e:</p>
</li>
<li>La donnée présente dans la case 0 est copiée dans la sortie (COPYFROM 0 puis OUTBOX)</li>
<li>On reprend tout le depuis le début.</li>
</ol>
<p>Plus clair ? Mouais... <strong>Et en français</strong> ? Voici : <em>en entrée se trouvent des suites de nombres qui sont séparées par des 0 (zéro). Pour chaque série, le plus petit des nombres est recopié en sortie</em>. Ce qui est exactement l'énoncé du défi.</p>
<h3>Apprentissage ?</h3>
<p>Je ne suis pas entièrement persuadé que le jeu soit didactique au point d'enseigner la programmation en assembleur. Cependant, il présente une manière de décrire des problèmes et fait ressentir ce qu'est la programmation de ce type.</p>
<p>Pour un programmeur déjà habitué à l'exercice, le jeu n'est <strong>pas très long</strong>. Je l'ai terminé en moins de 3 heures, à l'exception du dernier des puzzles optionnels, qui aurait été vraiment long à mettre au point, je n'en avais pas vraiment envie.</p>
<p>Je me demande cependant, pour quelqu'un qui n'est pas programmeur, si le jeu est intéressant et à quel point il est complexe.</p>
<p>Au final, un jeu sympathique avec une petite histoire amusante en fil rouge. Si vous voulez <strong>jouer à programmer en assembleur</strong>, ce jeu est fait pour vous.</p>
<p><img alt="Aperçu du jeu Human Resource Machine" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/hrm-image-jeu.jpeg"></p>Tramage basse définition pour le VG5000µ2017-03-02T00:00:00+01:002017-03-02T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-03-02:/tramage-basse-definition-pour-le-vg5000m.html<p>La <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">dernière fois</a>, j'avais présenté le début d'une idée pour accéder à des pixels « basse définition » sur le VG5000µ en se servant de caractères semi-graphiques.</p>
<p>Avant de continuer sur l'idée, je me demandais ce que donnerais, sans trop de traitement, une <strong>image de synthèse</strong> en niveau de gris avec une …</p><p>La <a href="https://www.triceraprog.fr/les-caracteres-semi-graphiques-du-vg5000m.html">dernière fois</a>, j'avais présenté le début d'une idée pour accéder à des pixels « basse définition » sur le VG5000µ en se servant de caractères semi-graphiques.</p>
<p>Avant de continuer sur l'idée, je me demandais ce que donnerais, sans trop de traitement, une <strong>image de synthèse</strong> en niveau de gris avec une telle technique.</p>
<p>Afin de transformer cette image en niveaux de gris vers du noir et blanc, je fais appel à une technique appelée « Dithering » ou « <strong>tramage</strong> » en français. Il s'agit de transformer le niveau de lumière en série de points plus ou moins dense.</p>
<p>Plus la résolution est dense, plus le résultat est bon. Forcément, en <strong>basse résolution</strong> semi-graphique, il ne faut pas s'attendre à des merveilles.</p>
<p>Voici donc deux couples d'images. En premier le rendu en niveau de gris d'une image de synthèse assez simple. Puis un rendu avec du tramage. Il existe de nombreuses techniques de tramages, et j'en utilise une parmi d'autre, pour me donner une idée grossière de ce que cela peut donner. Il ne s'agit pas là de rendu faits sur un VG5000µ, cela sera l'objectif suivant.</p>
<p><img alt="Cube et Sphère d'origine" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/cube_et_sphere.png">
<img alt="Cube et Sphère d'origine, dithered low" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/cube_et_sphere_dithered_low.png"></p>
<p><img alt="Maison d'origine" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/maison.png">
<img alt="Maison d'origine, dithered low" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/maison-dithered-low.png"></p>
<p>Un second but sera de faire sortir au VG5000µ une image en plus <strong>haute résolution</strong>. Mais pour cela, il faudra aller au-delà des caractères semi-graphiques.</p>
<p>Cela pourrait donner quelque chose comme ça.</p>
<p><img alt="Cube et Sphère d'origine, dithered high" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/cube_et_sphere_dithered.png">
<img alt="Maison d'origine, dithered high" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201703/maison-dithered.png"></p>Livre - Commodore VIC-20, a Visual History2017-02-25T00:00:00+01:002017-02-25T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-02-25:/livre-commodore-vic-20-a-visual-history.html<p>Après une première campagne Kickstarter manquée, <strong>Giacomo M. Vernoni</strong> n'a pas baissé les bras et lancé une nouvelle campagne pour l'édition d'un livre entièrement consacré au <strong>VIC-20</strong> de Commodore.</p>
<p>Le VIC-20, c'est un peu le grand frêre du beaucoup plus connu <strong>Commodore 64</strong>, un grand frêre aux capacités bien plus …</p><p>Après une première campagne Kickstarter manquée, <strong>Giacomo M. Vernoni</strong> n'a pas baissé les bras et lancé une nouvelle campagne pour l'édition d'un livre entièrement consacré au <strong>VIC-20</strong> de Commodore.</p>
<p>Le VIC-20, c'est un peu le grand frêre du beaucoup plus connu <strong>Commodore 64</strong>, un grand frêre aux capacités bien plus limitées. Mais c'est avant tout pour moi le premier de mes ordinateurs. Pas le premier que j'ai utilisé, ni celui sur lequel j'ai commencé la programmation, mais le premier qui était vraiment <em>à moi</em>.</p>
<p>Alors forcément, la promesse d'un livre consacré à la machine m'a fait de l'oeil, et j'ai embarqué dans ce projet. Quelques mois après, je peux parcourir ce livre compact mais très fourni en détails et illustrations. C'est une <strong>très belle édition</strong> et je félicite son auteur pour le boulot accompli. Le livre respire le travail bien fait mêlé à la passion.</p>
<p>Comme je ne suis pas toujours dans le détail les campagnes Kickstarter, j'ai été agréablement surpris de voir joint au livre des <strong>mini-posters</strong>, disons de grandes illustrations, d'un schéma de la machine, de photos des différentes cartes mère, et même un aimant aux couleurs de la marque.</p>
<p>Je ne sais pas si mon VIC-20 fonctionne toujours, cela fait bien longtemps qu'il n'a pas été allumé. Je l'espère, et j'espère pouvoir ainsi en parler plus longement ici dans le futur.</p>
<p>En attendant, je vous laisse avec une photo de la couverture du livre et d'un des posters.</p>
<p><img alt="Image de la couverture du livre et d'un poster" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201702/commodore-vic-20-a-visual-history-book.jpg"></p>Les caractères semi-graphiques du VG5000µ2017-02-18T00:00:00+01:002017-02-18T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-02-18:/les-caracteres-semi-graphiques-du-vg5000m.html<p>Lorsque l'on regarde de près la structure des caractères semi-graphiques du VG5000µ, on voit qu'elle a été réfléchie pour être facile à manipuler. Voici quelques observations :</p>
<ol>
<li>les caractères sont divisés en deux plages, l'une numérotée de 0 à 63, l'autre de 64 à 127.</li>
<li>si on prend un caractère de …</li></ol><p>Lorsque l'on regarde de près la structure des caractères semi-graphiques du VG5000µ, on voit qu'elle a été réfléchie pour être facile à manipuler. Voici quelques observations :</p>
<ol>
<li>les caractères sont divisés en deux plages, l'une numérotée de 0 à 63, l'autre de 64 à 127.</li>
<li>si on prend un caractère de la première plage, contenant les caractères « quadrillés » et que l'on ajoute 64 à son numéro, on obtient le même caractère en format « plein »</li>
<li>de manière similaire, si on soustrait 64 au numéro d'un caractère plein, on obtient sa forme quadrillée.</li>
</ol>
<p></p>
<p>Le caractère 127, par exemple, est le caractère complètement rempli. Le caractère 127 - 64, c'est-à-dire 63, est le caractère rempli, quadrillé.</p>
<p><img alt="Tous les caractères semi-graphiques du VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201702/vg5000_tableau_semi_graphiques.jpg"></p>
<p>Une observation supplémentaire est plus visible si l'on réarrange les blocs de chaque caractère semi-graphique sur une seule ligne. Pour cela, je prends les deux blocs du haut, puis j'y ajoute les deux blocs du milieu, et enfin les deux blocs du bas.</p>
<p>En remplaçant les blocs noirs par des « X » et les blocs blancs par des « . », voici ce que cela donne :</p>
<div class="highlight"><pre><span></span><code><span class="na">......</span><span class="w"></span>
<span class="nf">X.....</span><span class="w"></span>
<span class="na">.X....</span><span class="w"></span>
<span class="nf">XX....</span><span class="w"></span>
<span class="na">..X...</span><span class="w"></span>
<span class="nf">X.X...</span><span class="w"></span>
<span class="na">.XX...</span><span class="w"></span>
<span class="nf">XXX...</span><span class="w"></span>
<span class="na">...X..</span><span class="w"></span>
<span class="nf">X..X..</span><span class="w"></span>
<span class="na">.X.X..</span><span class="w"></span>
<span class="nf">XX.X..</span><span class="w"></span>
<span class="na">..XX..</span><span class="w"></span>
<span class="nf">X.XX..</span><span class="w"></span>
<span class="na">.XXX..</span><span class="w"></span>
<span class="nf">XXXX..</span><span class="w"></span>
<span class="na">....X.</span><span class="w"></span>
<span class="nf">Et</span><span class="w"> </span><span class="no">ainsi</span><span class="w"> </span><span class="no">de</span><span class="w"> </span><span class="no">suite</span><span class="w"> </span><span class="no">pour</span><span class="w"> </span><span class="no">les</span><span class="w"> </span><span class="mi">64</span><span class="w"> </span><span class="no">possibilités.</span><span class="w"></span>
</code></pre></div>
<p>Ceux qui sont à l'aise avec le comptage en <strong>base binaire</strong> auront probablement déjà repéré la structure. Pour les autres, essayez ceci :</p>
<ol>
<li>À partir d'un caractère donné regardez la position la plus à gauche</li>
<li>Si la position contient un point, placez un X, puis allez à l'étape 5</li>
<li>Sinon, c'est que la position contient un X. Remplacez le par un point</li>
<li>Considérez à présent la position juste à droite du point que vous venez de placer, et retournez à l'étape 2.</li>
<li>Terminé.</li>
</ol>
<p></p>
<p><em>Au passage, cette série d'instructions donnant des étapes à suivre pour effectuer une opération s'appelle un </em><em>algorithme</em><em>, un terme que nous croiserons à nouveau.</em></p>
<p>Félicitations, vous savez compter en binaire ! Plus traditionnellement, les « X » sont notés « 1 » et les « . » sont notés « 0 », mais le principe est le même.</p>
<p>Le grand avantage d'utiliser cette structure, où des nombres binaires forment les motifs des caractères semi-graphiques, c'est qu'il est assez facile de trouver le numéro d'ordre d'un caractère en fonction du ou des blocs que l'on veut allumer.</p>
<p>Pour cela, il suffit <strong>d'associer à chaque position un numéro</strong>, suivant ce schéma :</p>
<table>
<thead>
<tr>
<th>Position</th>
<th>0</th>
<th>1</th>
<th>2</th>
<th>3</th>
<th>4</th>
<th>5</th>
</tr>
</thead>
<tbody>
<tr>
<td>Numéro associé</td>
<td>1</td>
<td>2</td>
<td>4</td>
<td>8</td>
<td>16</td>
<td>32</td>
</tr>
</tbody>
</table>
<p>L'association est la suivante : multipliez le chiffre 2 par 2 autant de fois que la position de la colonne. On considère que multiplier 0 fois donne 1. Par exemple, pour la position 5, cela donne : 2 * 2 * 2 * 2 * 2 = 32. Je vous laisse vérifier les autres colonnes.</p>
<p><em>Je laisse la partie mathématique de l'histoire de côté, il y a de quoi faire un article complet dessus, et il y en a déjà beaucoup sur Internet.</em></p>
<p>Essayons maintenant. Je voudrais le caractère avec le <strong>bloc du milieu à droite</strong> allumé. Ce bloc, si on réarrange le caractère en ligne, est à la position 3 (rappelez-vous qu'on commence à numéroter à zéro) :</p>
<div class="highlight"><pre><span></span><code><span class="gh">..</span>
<span class="gh">.X donne ...X..</span>
<span class="gh">..</span>
</code></pre></div>
<p>En se référant au tableau ci-dessus, on trouve que la colonne 3 est associée au nombre 8. Je cherche le caractère dans sa forme pleine, j'ajoute donc 64 à 8 pour obtenir 72. Je vérifie dans le tableau du manuel... <strong>gagné</strong> !</p>
<p>Comment faire maintenant si l'on veut un caractère semi-graphique avec plusieurs blocs allumé ? Simple, il suffit de <strong>faire la somme</strong> de tous les numéros associés aux colonnes. Par exemple :</p>
<div class="highlight"><pre><span></span><code><span class="nf">X.</span><span class="w"></span>
<span class="na">.X</span><span class="w"> </span><span class="no">donne</span><span class="w"> </span><span class="no">X..XX.</span><span class="w"></span>
<span class="nf">X.</span><span class="w"></span>
</code></pre></div>
<p>Les blocs allumés sont donc aux positions 0, 3 et 4. Les nombres associés à ces positions sont 1, 8 et 16. Je fais la somme 1 + 8 + 16 = 25. Je veux les caractères pleins, j'ajoute donc 64. 64 + 25 = 89. Je vérifie dans le manuel le caractère 89. <strong>Encore gagné</strong> !</p>
<p>Nous voilà donc avec un algorithme très simple pour trouver un caractère semi-graphique en fonction d'un ensemble de points que l'on veut allumer. Il reste à afficher ce caractère au bon endroit à l'écran, et nous pourrons obtenir de quoi allumer et éteindre des blocs de pixels individuellement.</p>Les commandes graphiques en BASIC du VG5000µ2017-02-07T00:00:00+01:002017-02-07T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-02-07:/les-commandes-graphiques-en-basic-du-vg5000m.html<p>Une fois le VG5000µ démarré, on pourrait imaginer afficher des formes à l'écran en allumant des pixels avec des couleurs prises dans une palette donnée. Au même moment, c'est ce que font d'autres ordinateurs sur le marché. Avec de grandes limitations, certes, mais des commandes en BASIC sont fournies à …</p><p>Une fois le VG5000µ démarré, on pourrait imaginer afficher des formes à l'écran en allumant des pixels avec des couleurs prises dans une palette donnée. Au même moment, c'est ce que font d'autres ordinateurs sur le marché. Avec de grandes limitations, certes, mais des commandes en BASIC sont fournies à l'utilisateur pour afficher des points, voires même tracer des lignes.</p>
<p>Il n'en est rien.</p>
<p>Les commandes en <strong>BASIC</strong> méritent d'être étudiées avant de se lancer. La manuel d'utilisation consacre quatre pages d'explications à l'affichage graphique et auront pu, je pense, décourager tous ceux qui ne cherchaient pas un minimum à conduire quelques expériences. Par chance, avoir un ordinateur à cette époque, c'est avoir un peu l'esprit curieux.</p>
<p>Deux commandes, <strong>CURSORX</strong> et <strong>CURSORY</strong>, suivies d'une nombre, permettent de placer la position courante d'affichage sur l'espace à l'écran, divisé en 25 lignes de 40 colonnes. Les caractères affichés avec <strong>PRINT</strong> qui suivent une changement de coordonnée commenceront à l'endroit indiqué. Tout va bien.</p>
<p>La commande d'effacement d'écran, qui s'occupe aussi de choisir une couleur de fond et une couleur d'encre, se nomme <strong>INIT</strong>. C'est l'équivalent d'un CLS sur d'autres machines.</p>
<p>Une commande permet de geler le défilement d'écran et une autre de le reprendre (<strong>PAGE</strong> et <strong>SCROLL</strong>). L'une permet d'empêcher le rafraichissement de l'écran et l'autre de le reprendre (<strong>STORE</strong> et <strong>SCREEN</strong>). Une autre permet de contrôler le taux de rafraichissement de l'écran (<strong>DISPLAY</strong>). Ça fait beaucoup de commandes de contrôle de la surface d'affichage. Ce sont de toutes petites commandes qui ne prennent pas beaucoup de place dans le système, mais était-ce bien utile d'avoir tout ce monde ?</p>
<p>À vrai dire, la seule commande d'affichage est <strong>PRINT</strong>. <strong>PRINT</strong> est implémentée de manière assez simple : une fois les paramètres évalués, chaque caractère à traiter est pris un par un et affiché à l'emplacement du <strong>CURSOR</strong>. Puis le curseur est avancé d'une position.</p>
<p>L'implémentation vérifie au préalable passage que le caractère est bien affichable, ou bien s'il mérite un traitement spécial, ou bien si le caractère n'est pas affichable ; auquel cas ce dernier est remplacé par le caractère d'espacement.</p>
<p>Les caractères affichables de <strong>PRINT</strong> peuvent être séléctionnés dans quatre fontes différentes. La première contient les lettres, chiffres, ponctuations et autre caractères textuels. La seconde contient des caractères semi-graphique. Les deux autres fontes sont entièrement modifiables par l'utilisateur.</p>
<p>Ces caractères semi-graphiques sont une première solution pour tracer des images. Les pixels sont grossiers et arrangés sur une grille de 2 par 3, ce qui amène une résolution d'écran de 80 par 75. Pas fameux.</p>
<p><img alt="Caractères semi-graphiques du VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201702/vg5000_semi_graphiques.jpg"></p>
<p>L'utilisation des caractères programmables est la seconde manière d'afficher des pixels à ĺ'écran. Cette méthode est présentée rapidement dans le manuel et est similaire à celle que l'on retrouve sur l'Amstrad CPC par exemple. Grace à un encodage d'une grille de 8 pixels par 10, un caractère peut être redéfini, puis affiché avec <strong>PRINT</strong>, après sélection de la bonne fonte.</p>
<p>Ce type d'encodage sort du cadre de cet article, mais voilà ce que cela donne :</p>
<div class="highlight"><pre><span></span><code><span class="w"> </span><span class="nt">XXXX</span><span class="w"> </span><span class="nt">4</span><span class="o">+</span><span class="nt">8</span><span class="o">+</span><span class="nt">16</span><span class="o">+</span><span class="nt">32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">60</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">3C</span><span class="w"></span>
<span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">2</span><span class="o">+</span><span class="nt">64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">66</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">42</span><span class="w"></span>
<span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">1</span><span class="o">+</span><span class="nt">4</span><span class="o">+</span><span class="nt">32</span><span class="o">+</span><span class="nt">128</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">165</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">A5</span><span class="w"></span>
<span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">1</span><span class="o">+</span><span class="nt">128</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">129</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">81</span><span class="w"></span>
<span class="nt">X</span><span class="w"> </span><span class="nt">XX</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">1</span><span class="o">+</span><span class="nt">8</span><span class="o">+</span><span class="nt">16</span><span class="o">+</span><span class="nt">128</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">153</span><span class="w"> </span><span class="nt">-</span><span class="w"> </span><span class="nt">99</span><span class="w"></span>
<span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">1</span><span class="o">+</span><span class="nt">4</span><span class="o">+</span><span class="nt">32</span><span class="o">+</span><span class="nt">128</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">165</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">A5</span><span class="w"></span>
<span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">1</span><span class="o">+</span><span class="nt">128</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">129</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">81</span><span class="w"></span>
<span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">2</span><span class="o">+</span><span class="nt">64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">66</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">42</span><span class="w"></span>
<span class="w"> </span><span class="nt">XXXX</span><span class="w"> </span><span class="nt">4</span><span class="o">+</span><span class="nt">8</span><span class="o">+</span><span class="nt">16</span><span class="o">+</span><span class="nt">32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">60</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">3C</span><span class="w"></span>
<span class="w"> </span><span class="nt">0</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">0</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">00</span><span class="w"></span>
<span class="nt">SETEG</span><span class="w"> </span><span class="nt">65</span><span class="o">,</span><span class="s2">"3C54A58199A581423C00"</span><span class="w"></span>
<span class="nt">EG</span><span class="w"> </span><span class="nt">0</span><span class="o">,</span><span class="nt">6</span><span class="o">,</span><span class="nt">0</span><span class="p">:</span><span class="nd">PRINT</span><span class="s2">"A"</span><span class="o">;</span><span class="w"></span>
<span class="w"> </span><span class="nt">XXXX</span><span class="w"> </span><span class="nt">4</span><span class="o">+</span><span class="nt">8</span><span class="o">+</span><span class="nt">16</span><span class="o">+</span><span class="nt">32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">60</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">3C</span><span class="w"></span>
<span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">2</span><span class="o">+</span><span class="nt">64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">66</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">42</span><span class="w"></span>
<span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">1</span><span class="o">+</span><span class="nt">4</span><span class="o">+</span><span class="nt">32</span><span class="o">+</span><span class="nt">128</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">165</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">A5</span><span class="w"></span>
<span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">1</span><span class="o">+</span><span class="nt">128</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">129</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">81</span><span class="w"></span>
<span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">1</span><span class="o">+</span><span class="nt">4</span><span class="o">+</span><span class="nt">32</span><span class="o">+</span><span class="nt">128</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">165</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">A5</span><span class="w"></span>
<span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">1</span><span class="o">+</span><span class="nt">4</span><span class="o">+</span><span class="nt">32</span><span class="o">+</span><span class="nt">128</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">165</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">A5</span><span class="w"></span>
<span class="nt">X</span><span class="w"> </span><span class="nt">XX</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">1</span><span class="o">+</span><span class="nt">8</span><span class="o">+</span><span class="nt">16</span><span class="o">+</span><span class="nt">128</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">153</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">99</span><span class="w"></span>
<span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">X</span><span class="w"> </span><span class="nt">2</span><span class="o">+</span><span class="nt">64</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">66</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">42</span><span class="w"></span>
<span class="w"> </span><span class="nt">XXXX</span><span class="w"> </span><span class="nt">4</span><span class="o">+</span><span class="nt">8</span><span class="o">+</span><span class="nt">16</span><span class="o">+</span><span class="nt">32</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">60</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">3C</span><span class="w"></span>
<span class="w"> </span><span class="nt">0</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">0</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="nt">00</span><span class="w"></span>
<span class="nt">SETEG</span><span class="w"> </span><span class="nt">66</span><span class="o">,</span><span class="s2">"3C42A581A5A599423C00"</span><span class="w"></span>
<span class="nt">EG</span><span class="w"> </span><span class="nt">0</span><span class="o">,</span><span class="nt">6</span><span class="o">,</span><span class="nt">0</span><span class="p">:</span><span class="nd">PRINT</span><span class="s2">"B"</span><span class="o">;</span><span class="w"></span>
</code></pre></div>
<p><img alt="Smileys avec le VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201702/vg5000_smileys.png"></p>
<p>Voilà avec quoi devait jouer un utilisateur du VG5000µ n'ayant sous les yeux que le seul manuel fourni avec l'ordinateur. Avec des caractères de 8 pixels par 10, on atteint une résolution potentielle de 320 par 250... mais avec de grosses limitations.</p>
<p>Est-ce qu'il serait néanmoins possible de tracer des lignes ? Oui, surement, nous verrons ça plus tard.</p>Les bases du BASIC2017-02-04T00:00:00+01:002017-02-04T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-02-04:/les-bases-du-basic.html<p>Le <strong>BASIC</strong> est LE langage de programmation phare de l’époque de l'informatique personnelle des années 1980. Quasi tous les ordinateurs de la génération dite « 8 bits » démarrent sous un environnement immédiatement programmable en BASIC. Les magasines spécialisés enseignent le <strong>BASIC</strong> et la majorité des programmes diffusés par ce moyen …</p><p>Le <strong>BASIC</strong> est LE langage de programmation phare de l’époque de l'informatique personnelle des années 1980. Quasi tous les ordinateurs de la génération dite « 8 bits » démarrent sous un environnement immédiatement programmable en BASIC. Les magasines spécialisés enseignent le <strong>BASIC</strong> et la majorité des programmes diffusés par ce moyen sont en BASIC, au moins dans un premier temps et lorsqu'il y a une dimension pédagogique. On trouve aussi de nombreux livres ayant pour contenu des programmes en <strong>BASIC</strong> à recopier sur sa machine.</p>
<p>Au moment de cette diffusion massive (toute proportion gardée, la micro informatique reste un objet de curiosité), le <strong>BASIC</strong> à déjà quelques années d'activité, sa création datant du milieu des années 60.</p>
<p>À sa création, le <strong>BASIC</strong> se situe dans la classe des langages de programmation à vocation mathématiques. C'est un langage destiné à résoudre des problèmes numériques. L'autre grande classe de langages de programmation à cette époque étant ceux plutôt destinés au données d'entreprises : gestion de stock, gestion de personnel,...</p>
<p>C'est aussi un langage interactif : il est possible d'entrer des commandes pour les voir s'exécuter immédiatement, ainsi que des programmes pour stocker des suites d'instructions à exécuter plus tard. Cela peut sembler naturel en 2017, mais à cette époque, beaucoup d'ordinateurs exécutent exclusivement des programmes stockés, sur des cartes perforées par exemple (BASIC peut d'ailleurs être aussi utilisé de cette manière, mais perd alors son côté interactif).</p>
<p>Dernier point de contexte : le <strong>BASIC</strong> est crée 10 ans avant l'émergence de la micro informatique personnelle.</p>
<p><img alt="Couverture du livre 40 Programmes de Jeux en BASIC" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201702/40programmes-basic-mo5-to770.jpg" title="C'est en BASIC, c'est clair !"></p>
<h4>À quoi ça ressemble ?</h4>
<p>Voici un programme tiré du manuel de <strong>BASIC</strong> de 1964</p>
<div class="highlight"><pre><span></span><code><span class="nl">10</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="s2">"A"</span><span class="p">,</span><span class="w"> </span><span class="s2">"B"</span><span class="p">,</span><span class="w"> </span><span class="s2">"C"</span><span class="p">,</span><span class="w"> </span><span class="s2">"GCD"</span>
<span class="nl">20</span><span class="w"> </span><span class="kr">READ</span><span class="w"> </span><span class="vg">A</span><span class="p">,</span><span class="w"> </span><span class="vg">B</span><span class="p">,</span><span class="w"> </span><span class="vg">C</span>
<span class="nl">30</span><span class="w"> </span><span class="kd">LET</span><span class="w"> </span><span class="vg">X</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="vg">A</span>
<span class="nl">40</span><span class="w"> </span><span class="kd">LET</span><span class="w"> </span><span class="vg">Y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="vg">B</span>
<span class="nl">50</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">200</span>
<span class="nl">60</span><span class="w"> </span><span class="kd">LET</span><span class="w"> </span><span class="vg">X</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="vg">G</span>
<span class="nl">70</span><span class="w"> </span><span class="kd">LET</span><span class="w"> </span><span class="vg">Y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="vg">C</span>
<span class="nl">80</span><span class="w"> </span><span class="kr">GOSUB</span><span class="w"> </span><span class="nl">200</span>
<span class="nl">90</span><span class="w"> </span><span class="kr">PRINT</span><span class="w"> </span><span class="vg">A</span><span class="p">,</span><span class="w"> </span><span class="vg">B</span><span class="p">,</span><span class="w"> </span><span class="vg">C</span><span class="p">,</span><span class="w"> </span><span class="vg">G</span>
<span class="nl">100</span><span class="w"> </span><span class="vg">GO</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">20</span>
<span class="nl">110</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">60</span><span class="p">,</span><span class="w"> </span><span class="il">90</span><span class="p">,</span><span class="w"> </span><span class="il">120</span>
<span class="nl">120</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">38456</span><span class="p">,</span><span class="w"> </span><span class="il">64872</span><span class="p">,</span><span class="w"> </span><span class="il">98765</span>
<span class="nl">130</span><span class="w"> </span><span class="kd">DATA</span><span class="w"> </span><span class="il">32</span><span class="p">,</span><span class="w"> </span><span class="il">384</span><span class="p">,</span><span class="w"> </span><span class="il">72</span>
<span class="nl">200</span><span class="w"> </span><span class="kd">LET</span><span class="w"> </span><span class="vg">Q</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="kr">INT</span><span class="p">(</span><span class="vg">X</span><span class="o">/</span><span class="vg">Y</span><span class="p">)</span>
<span class="nl">210</span><span class="w"> </span><span class="kd">LET</span><span class="w"> </span><span class="vg">R</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="vg">X</span><span class="w"> </span><span class="o">-</span><span class="w"> </span><span class="vg">Q</span><span class="o">*</span><span class="vg">Y</span>
<span class="nl">220</span><span class="w"> </span><span class="kr">IF</span><span class="w"> </span><span class="vg">R</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="il">0</span><span class="w"> </span><span class="kr">THEN</span><span class="w"> </span><span class="il">300</span>
<span class="nl">230</span><span class="w"> </span><span class="kd">LET</span><span class="w"> </span><span class="vg">X</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="vg">Y</span>
<span class="nl">240</span><span class="w"> </span><span class="kd">LET</span><span class="w"> </span><span class="vg">Y</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="vg">R</span>
<span class="nl">250</span><span class="w"> </span><span class="vg">GO</span><span class="w"> </span><span class="k">TO</span><span class="w"> </span><span class="il">200</span>
<span class="nl">300</span><span class="w"> </span><span class="kd">LET</span><span class="w"> </span><span class="vg">G</span><span class="w"> </span><span class="o">=</span><span class="w"> </span><span class="vg">Y</span>
<span class="nl">310</span><span class="w"> </span><span class="kr">RETURN</span>
<span class="nl">999</span><span class="w"> </span><span class="kr">END</span>
</code></pre></div>
<p>Qui affiche le plus grand commun diviseur (PGCD) d'une série de nombres, groupés par trois.</p>
<div class="highlight"><pre><span></span><code>A B C GCD
60 90 120 30
38456 64872 98765 1
32 384 72 8
</code></pre></div>
<p>Un programme en <strong>BASIC</strong> est exécuté par numéro de ligne croissante, en débutant par le numéro le plus bas. Il est donc assez aisé, lorsque l'on connaît la signification des instructions, de suivre le déroulement.</p>
<p>C'est une des forces du BASIC, l'écriture et la lecture d'un programme est très simple. L'exécution séquentielle permet de comprendre les programmes et de la mettre au point sans de grandes connaissances.</p>
<p>Devenant un standard de fait, les programmes en <strong>BASIC</strong> sont assez faciles à porter d'une machine à une autre. Le programme ci-dessus, par exemple, fonctionne tel quel sur un <a href="https://www.triceraprog.fr/vg5000m.html">VG5000µ</a>. L'affichage 40 colonnes fait que la sortie texte devrait être un peu adaptée, mais ça fonctionne. Sur un MO5, le même programme demande une retouche mineure : enlever le mot clé LET.</p>
<p>Dans sa forme de 1964, le <strong>BASIC</strong> est très limité. Il n'y a pas par exemple pas d'instruction pour accéder au matériel de la machine, ni à son environnement. Les commandes pour sauver et rappeler un programme ne peuvent être entrées qu'en mode direct. Le scénario typique est celui d'un étudiant qui s'installe sur un des 35 terminaux de la machine, reprend un travail en cours ou en débute un autre, note ses résultats puis laisse la place à quelqu'un d'autre.</p>
<p>D'autres limitations : les sous-programmes, appelés par GOSUB, ne peuvent pas eux-même appeler d'autres sous-programmes. Cela limite fortement la structuration des programmes. Le nombre de données introduites par DATA est assez limité, ainsi que le nombre de boucles FOR, d'éléments de tableaux...</p>
<p>L'évolution de <strong>BASIC</strong> lui enlèvera peu à peu ces limitations. Sur les ordinateurs des années 80, le <strong>BASIC</strong> s'est enrichi d'instructions pour accéder au matériel, dessiner à l'écran, jouer de la musique, demander des données à l'utilisateur,... Il restera cependant notoirement lent, extrêmement lent.</p>
<h4>La descendance</h4>
<p>L'offre des ordinateurs a évolué et ceux-ci n'ont plus été livré de base avec un environnement en BASIC. D'ailleurs de plus en plus sans environnement de programmation du tout.</p>
<p>Mais du fait de sa simplicité et du nombre de personnes qui ont été exposé à ce langage de programmation, il est resté très longtemps populaire et a évolué en intégrant de nouveaux concepts, comme la programmation objet, les exceptions. Il a servi et sert toujours dans le Web, ou pour étendre des applications comme LibreOffice.</p>
<p><img alt="Quelques mots-clé d'un BASIC IBM" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201702/mots_cle_basic_ibm.jpg"></p>Le BASIC du VG5000µ, démarrage2017-01-30T00:00:00+01:002017-01-30T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-01-30:/le-basic-du-vg5000m-demarrage.html<p>Lorsqu'on allume un ordinateur personnel dans les années 80, il y a de bonnes chances de se retrouver dans un environnement BASIC. La machine présente, après quelques lignes d'introduction, une invite de commande à partir de laquelle l'utilisateur peut diriger les opérations. Ces opérations sont alors de deux types : lancer …</p><p>Lorsqu'on allume un ordinateur personnel dans les années 80, il y a de bonnes chances de se retrouver dans un environnement BASIC. La machine présente, après quelques lignes d'introduction, une invite de commande à partir de laquelle l'utilisateur peut diriger les opérations. Ces opérations sont alors de deux types : lancer un programme, ou bien programmer (voire lancer un programme pour programmer).</p>
<p>Il y a plusieurs exceptions à ce schéma. Le TO7, par exemple, a besoin d'une cartouche pour démarrer son environnement ; celui livré avec la machine est cependant un BASIC. Le Jupiter Ace quant à lui démarre avec un environnement en FORTH.</p>
<p>Le VG5000µ fait parti de la catégorie des ordinateurs avec BASIC intégré. Dès l'allumage, on peut se lancer dans de l'expérimentation ou de la programmation.</p>
<p><img alt="Démarrage du VG5000µ avec extention mémoire" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201701/VG5000ExtStart.jpg"></p>
<p>Dire qu'un ordinateur fonctionne avec BASIC n'est qu'une partie de la définition. Il n'y a pas un BASIC, mais plusieurs dialectes du même langage. Si les instructions principales restent les mêmes et fidèles au BASIC tel que créé en 1964, chaque machine possède ses particularités, même sur des versions produites par la même entreprise.</p>
<p>Le dialecte du VG 5000 BASIC est assez classique. Les structures de contrôles IF/FOR/GOTO/GOSUB sont là, ainsi que les plus avancées ON GOTO et ON GOSUB. Des fonctions mathématiques classiques ainsi que de manipulations de chaînes de caractères sont présentes. Les fonctions d'éditions AUTO, mais surtout RENUM, sont toujours les bienvenues.</p>
<p>Question périphériques, des commandes sont disponibles pour la manipulation des fichiers sur K7 (le VG 5000 n'a pas eu droit à son lecteur de disquette), le son, deux manettes et l'affichage. Nous verrons cela plus en détails plus tard.</p>
<p>La manuel du VG5000µ est assez court avec sa centaine de pages. Comme il se doit pour un manuel d'époque, la partie programmation occupe la majeure partie de l'ouvrage en commençant dès la page 13 et continuant jusqu'à la presque fin. La programmation y est envisagé en BASIC, et seules quelques références à l'assembleur sont présentes.</p>
<p>Je vous laisse avec le début du Chapitre 11, long de deux pages, expliquant la démarche d'écriture d'un programme.</p>
<p><img alt="Extrait du manuel VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201701/vg5000-manuel-ch11.jpeg"></p>VG5000µ2017-01-28T00:00:00+01:002017-01-28T00:00:00+01:00Sylvain Glaizetag:www.triceraprog.fr,2017-01-28:/vg5000m.html<p>La machine à laquelle je vais m'intéresser est le VG5000µ, de Philips. La plupart du temps, le µ n'est pas précisé. C'est une machine qui n'a pas vraiment de secret, ses nombreux fans l'ayant depuis longtemps passée au peigne fin et la documentation technique est abondante.</p>
<p>Ce n'est cependant pas …</p><p>La machine à laquelle je vais m'intéresser est le VG5000µ, de Philips. La plupart du temps, le µ n'est pas précisé. C'est une machine qui n'a pas vraiment de secret, ses nombreux fans l'ayant depuis longtemps passée au peigne fin et la documentation technique est abondante.</p>
<p>Ce n'est cependant pas une machine si populaire. Je pense avoir passé les années 80 sans vraiment en entendre parler, ni en croiser sur mon chemin. J'ai donc découvert cette machine plutôt récemment, début 2016.</p>
<p>C'est une machine extrêmement simple, autour d'un processeur central, d'un processeur vidéo et d'un clavier. Le tout dans un boîtier léger est plutôt aéré, pour ne pas dire plein de vide. Chose très appréciable, du moins en France, l'appareil est munie d'une prise Péritel, ce qui évite les jonglages pour sortir une image sur un écran pas trop ancien.</p>
<p>Le clavier n'est pas extrêmement pratique, mais pas affreusement inconfortable. Il est presque envisageable de travailler directement dessus en 2017. Les inscriptions sur le clavier montrent aussi sans équivoque l'orientation de l'ordinateur vers la programmation en BASIC, avec des raccourcis qui inscrivent à l'écran les commandes.</p>
<p>Je n'ai jamais trouvé ces raccourcis vraiment pratiques. Elles servent en sorte de documentation pour connaître les particularités du dialecte du BASIC utilisé par la machine, mais à l'époque et sur des machines similaires utilisant le même principe, j'ai toujours trouvé ça inutile dans le meilleur des cas.</p>
<p>Avant de vous laisser avec une photo partielle du clavier de la machine que j'utilise, voici quelques sites consacrés au VG5000µ :</p>
<ul>
<li><a href="http://vg5k.free.fr/">La page des fans du VG5000µ</a>, avec un panorama du matériel, des logiciels et de la documentation.</li>
<li><a href="http://vg5000.webnode.fr/">VG5000</a>, qui contient des informations sur la programmation du processeur vidéo.</li>
<li><a href="http://vg5000bazar.free.fr/">VG5000 Bazar</a>, qui contient des logiciels récents, des articles de fond et un recensement des machines toujours en circulation.</li>
<li><a href="http://dcvg5k.free.fr/">DCVG5K</a>, l'émulateur VG5000µ de Daniel Coulom.</li>
<li><a href="http://vg5000.free.fr/">My VG5000 :)</a>, site de Carl Hervier avec de nombreuses documentations, informations et projets.</li>
</ul>
<p><img alt="Clavier du VG5000µ" class="img-responsive center-block img-rounded" src="https://www.triceraprog.fr/images/201701/vg5000-clavier.jpg"></p>