@nataliapc/mcp-openmsx 1.1.15 → 1.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (216) hide show
  1. package/README.md +84 -37
  2. package/dist/server.js +101 -40
  3. package/dist/utils.js +42 -2
  4. package/dist/vectordb.js +61 -0
  5. package/package.json +8 -3
  6. package/resources/audio/msx-midi.md +872 -0
  7. package/resources/audio/psg_registers.md +281 -0
  8. package/resources/audio/sound_cartridge_scc.md +123 -0
  9. package/resources/audio/sound_cartridge_scci.md +250 -0
  10. package/resources/audio/toc.json +8 -4
  11. package/resources/book--msx2-technical-handbook/toc.json +1 -1
  12. package/resources/msx-dos/MSX-DOS_2_Environment_Variables.md +1368 -0
  13. package/resources/msx-dos/MSX-DOS_File_extensions.md +154 -0
  14. package/resources/msx-dos/toc.json +13 -0
  15. package/resources/msx-unapi/toc.json +2 -2
  16. package/resources/others/keyboard_matrices.md +243 -0
  17. package/resources/others/toc.json +6 -0
  18. package/resources/programming/asm_callbios.md +79 -0
  19. package/resources/programming/asm_docopy.md +115 -0
  20. package/resources/programming/asm_fast_loops.md +200 -0
  21. package/resources/programming/asm_getslot.md +143 -0
  22. package/resources/programming/asm_interrupts.md +202 -0
  23. package/resources/programming/asm_load_screen.md +240 -0
  24. package/resources/programming/asm_mult_div_shifts.md +487 -0
  25. package/resources/programming/asm_raminpage1.md +56 -0
  26. package/resources/programming/asm_vdp_detection.md +78 -0
  27. package/resources/programming/asm_vdp_routines.md +343 -0
  28. package/resources/programming/asm_z80_routines_collection.md +810 -0
  29. package/resources/programming/basic_wiki/ABS().md +36 -0
  30. package/resources/programming/basic_wiki/AND.md +71 -0
  31. package/resources/programming/basic_wiki/ASC().md +38 -0
  32. package/resources/programming/basic_wiki/ATN().md +36 -0
  33. package/resources/programming/basic_wiki/AUTO.md +39 -0
  34. package/resources/programming/basic_wiki/BASE().md +147 -0
  35. package/resources/programming/basic_wiki/BEEP.md +27 -0
  36. package/resources/programming/basic_wiki/BIN$().md +36 -0
  37. package/resources/programming/basic_wiki/BLOAD.md +63 -0
  38. package/resources/programming/basic_wiki/BSAVE.md +61 -0
  39. package/resources/programming/basic_wiki/CALL.md +391 -0
  40. package/resources/programming/basic_wiki/CALL_ADJUST.md +40 -0
  41. package/resources/programming/basic_wiki/CALL_IMPOSE.md +28 -0
  42. package/resources/programming/basic_wiki/CALL_OPTIONS.md +26 -0
  43. package/resources/programming/basic_wiki/CALL_PAUSE.md +119 -0
  44. package/resources/programming/basic_wiki/CALL_PCMPLAY.md +60 -0
  45. package/resources/programming/basic_wiki/CALL_PCMREC.md +70 -0
  46. package/resources/programming/basic_wiki/CDBL().md +36 -0
  47. package/resources/programming/basic_wiki/CHR$().md +51 -0
  48. package/resources/programming/basic_wiki/CINT().md +36 -0
  49. package/resources/programming/basic_wiki/CIRCLE.md +51 -0
  50. package/resources/programming/basic_wiki/CLEAR.md +39 -0
  51. package/resources/programming/basic_wiki/CLOAD.md +27 -0
  52. package/resources/programming/basic_wiki/CLOAD?.md +31 -0
  53. package/resources/programming/basic_wiki/CLOSE.md +44 -0
  54. package/resources/programming/basic_wiki/CLS.md +51 -0
  55. package/resources/programming/basic_wiki/COLOR.md +143 -0
  56. package/resources/programming/basic_wiki/COLOR=.md +93 -0
  57. package/resources/programming/basic_wiki/COLOR_SPRITE$().md +83 -0
  58. package/resources/programming/basic_wiki/COLOR_SPRITE().md +85 -0
  59. package/resources/programming/basic_wiki/CONT.md +23 -0
  60. package/resources/programming/basic_wiki/COPY.md +215 -0
  61. package/resources/programming/basic_wiki/COPY_SCREEN.md +61 -0
  62. package/resources/programming/basic_wiki/COS().md +37 -0
  63. package/resources/programming/basic_wiki/CSAVE.md +35 -0
  64. package/resources/programming/basic_wiki/CSNG().md +36 -0
  65. package/resources/programming/basic_wiki/CSRLIN.md +33 -0
  66. package/resources/programming/basic_wiki/DATA.md +47 -0
  67. package/resources/programming/basic_wiki/DEFDBL.md +40 -0
  68. package/resources/programming/basic_wiki/DEFINT.md +40 -0
  69. package/resources/programming/basic_wiki/DEFSNG.md +40 -0
  70. package/resources/programming/basic_wiki/DEFSTR.md +40 -0
  71. package/resources/programming/basic_wiki/DEF_FN.md +49 -0
  72. package/resources/programming/basic_wiki/DEF_USR.md +33 -0
  73. package/resources/programming/basic_wiki/DELETE.md +49 -0
  74. package/resources/programming/basic_wiki/DIM.md +59 -0
  75. package/resources/programming/basic_wiki/DRAW.md +77 -0
  76. package/resources/programming/basic_wiki/ELSE.md +45 -0
  77. package/resources/programming/basic_wiki/END.md +32 -0
  78. package/resources/programming/basic_wiki/EOF().md +36 -0
  79. package/resources/programming/basic_wiki/EQV.md +76 -0
  80. package/resources/programming/basic_wiki/ERASE.md +35 -0
  81. package/resources/programming/basic_wiki/ERL.md +34 -0
  82. package/resources/programming/basic_wiki/ERR.md +143 -0
  83. package/resources/programming/basic_wiki/ERROR.md +145 -0
  84. package/resources/programming/basic_wiki/EXP().md +38 -0
  85. package/resources/programming/basic_wiki/FIELD.md +48 -0
  86. package/resources/programming/basic_wiki/FIX().md +44 -0
  87. package/resources/programming/basic_wiki/FN.md +61 -0
  88. package/resources/programming/basic_wiki/FOR...NEXT.md +80 -0
  89. package/resources/programming/basic_wiki/FRE().md +66 -0
  90. package/resources/programming/basic_wiki/GET_DATE.md +60 -0
  91. package/resources/programming/basic_wiki/GET_TIME.md +34 -0
  92. package/resources/programming/basic_wiki/GOSUB.md +41 -0
  93. package/resources/programming/basic_wiki/GOTO.md +41 -0
  94. package/resources/programming/basic_wiki/HEX$().md +36 -0
  95. package/resources/programming/basic_wiki/IF...GOTO...ELSE.md +55 -0
  96. package/resources/programming/basic_wiki/IF...THEN...ELSE.md +50 -0
  97. package/resources/programming/basic_wiki/IMP.md +83 -0
  98. package/resources/programming/basic_wiki/INKEY$.md +65 -0
  99. package/resources/programming/basic_wiki/INP().md +33 -0
  100. package/resources/programming/basic_wiki/INPUT$().md +51 -0
  101. package/resources/programming/basic_wiki/INPUT.md +93 -0
  102. package/resources/programming/basic_wiki/INSTR().md +44 -0
  103. package/resources/programming/basic_wiki/INT().md +44 -0
  104. package/resources/programming/basic_wiki/INTERVAL.md +57 -0
  105. package/resources/programming/basic_wiki/KEY().md +51 -0
  106. package/resources/programming/basic_wiki/KEY.md +254 -0
  107. package/resources/programming/basic_wiki/LEFT$().md +39 -0
  108. package/resources/programming/basic_wiki/LEN().md +36 -0
  109. package/resources/programming/basic_wiki/LET.md +68 -0
  110. package/resources/programming/basic_wiki/LINE.md +74 -0
  111. package/resources/programming/basic_wiki/LINE_INPUT.md +79 -0
  112. package/resources/programming/basic_wiki/LIST.md +58 -0
  113. package/resources/programming/basic_wiki/LLIST.md +43 -0
  114. package/resources/programming/basic_wiki/LOAD.md +56 -0
  115. package/resources/programming/basic_wiki/LOCATE.md +67 -0
  116. package/resources/programming/basic_wiki/LOG().md +36 -0
  117. package/resources/programming/basic_wiki/LPOS().md +31 -0
  118. package/resources/programming/basic_wiki/LPRINT.md +46 -0
  119. package/resources/programming/basic_wiki/MAXFILES.md +39 -0
  120. package/resources/programming/basic_wiki/MERGE.md +54 -0
  121. package/resources/programming/basic_wiki/MID$().md +72 -0
  122. package/resources/programming/basic_wiki/MOD.md +39 -0
  123. package/resources/programming/basic_wiki/MOTOR.md +46 -0
  124. package/resources/programming/basic_wiki/NEW.md +27 -0
  125. package/resources/programming/basic_wiki/NOT.md +61 -0
  126. package/resources/programming/basic_wiki/OCT$().md +36 -0
  127. package/resources/programming/basic_wiki/ON...GOSUB.md +45 -0
  128. package/resources/programming/basic_wiki/ON...GOTO.md +42 -0
  129. package/resources/programming/basic_wiki/ON_ERROR_GOTO.md +61 -0
  130. package/resources/programming/basic_wiki/ON_INTERVAL_GOSUB.md +54 -0
  131. package/resources/programming/basic_wiki/ON_KEY_GOSUB.md +56 -0
  132. package/resources/programming/basic_wiki/ON_SPRITE_GOSUB.md +41 -0
  133. package/resources/programming/basic_wiki/ON_STOP_GOSUB.md +56 -0
  134. package/resources/programming/basic_wiki/ON_STRIG_GOSUB.md +70 -0
  135. package/resources/programming/basic_wiki/OPEN.md +103 -0
  136. package/resources/programming/basic_wiki/OR.md +75 -0
  137. package/resources/programming/basic_wiki/OUT.md +35 -0
  138. package/resources/programming/basic_wiki/PAD().md +110 -0
  139. package/resources/programming/basic_wiki/PAINT.md +66 -0
  140. package/resources/programming/basic_wiki/PDL().md +53 -0
  141. package/resources/programming/basic_wiki/PEEK().md +44 -0
  142. package/resources/programming/basic_wiki/PLAY().md +58 -0
  143. package/resources/programming/basic_wiki/PLAY.md +196 -0
  144. package/resources/programming/basic_wiki/POINT.md +52 -0
  145. package/resources/programming/basic_wiki/POKE.md +51 -0
  146. package/resources/programming/basic_wiki/POS().md +36 -0
  147. package/resources/programming/basic_wiki/PRESET.md +61 -0
  148. package/resources/programming/basic_wiki/PRINT.md +179 -0
  149. package/resources/programming/basic_wiki/PSET.md +82 -0
  150. package/resources/programming/basic_wiki/PUT_KANJI.md +93 -0
  151. package/resources/programming/basic_wiki/PUT_SPRITE.md +143 -0
  152. package/resources/programming/basic_wiki/READ.md +45 -0
  153. package/resources/programming/basic_wiki/REM.md +42 -0
  154. package/resources/programming/basic_wiki/RENUM.md +78 -0
  155. package/resources/programming/basic_wiki/RESTORE.md +52 -0
  156. package/resources/programming/basic_wiki/RESUME.md +45 -0
  157. package/resources/programming/basic_wiki/RETURN.md +47 -0
  158. package/resources/programming/basic_wiki/RIGHT$().md +39 -0
  159. package/resources/programming/basic_wiki/RND().md +51 -0
  160. package/resources/programming/basic_wiki/RUN.md +56 -0
  161. package/resources/programming/basic_wiki/SAVE.md +65 -0
  162. package/resources/programming/basic_wiki/SCREEN.md +164 -0
  163. package/resources/programming/basic_wiki/SET_ADJUST.md +66 -0
  164. package/resources/programming/basic_wiki/SET_BEEP.md +76 -0
  165. package/resources/programming/basic_wiki/SET_DATE.md +103 -0
  166. package/resources/programming/basic_wiki/SET_PAGE.md +52 -0
  167. package/resources/programming/basic_wiki/SET_PASSWORD.md +75 -0
  168. package/resources/programming/basic_wiki/SET_PROMPT.md +61 -0
  169. package/resources/programming/basic_wiki/SET_SCREEN.md +100 -0
  170. package/resources/programming/basic_wiki/SET_SCROLL.md +55 -0
  171. package/resources/programming/basic_wiki/SET_TIME.md +83 -0
  172. package/resources/programming/basic_wiki/SET_TITLE.md +87 -0
  173. package/resources/programming/basic_wiki/SET_VIDEO.md +49 -0
  174. package/resources/programming/basic_wiki/SGN().md +38 -0
  175. package/resources/programming/basic_wiki/SIN().md +36 -0
  176. package/resources/programming/basic_wiki/SOUND.md +188 -0
  177. package/resources/programming/basic_wiki/SPACE$().md +38 -0
  178. package/resources/programming/basic_wiki/SPC().md +34 -0
  179. package/resources/programming/basic_wiki/SPRITE$().md +50 -0
  180. package/resources/programming/basic_wiki/SPRITE.md +31 -0
  181. package/resources/programming/basic_wiki/SQR().md +32 -0
  182. package/resources/programming/basic_wiki/STICK().md +70 -0
  183. package/resources/programming/basic_wiki/STOP.md +70 -0
  184. package/resources/programming/basic_wiki/STR$().md +37 -0
  185. package/resources/programming/basic_wiki/STRIG().md +82 -0
  186. package/resources/programming/basic_wiki/STRING$().md +42 -0
  187. package/resources/programming/basic_wiki/SWAP.md +62 -0
  188. package/resources/programming/basic_wiki/TAB().md +38 -0
  189. package/resources/programming/basic_wiki/TAN().md +36 -0
  190. package/resources/programming/basic_wiki/TIME.md +59 -0
  191. package/resources/programming/basic_wiki/TROFF.md +21 -0
  192. package/resources/programming/basic_wiki/TRON.md +39 -0
  193. package/resources/programming/basic_wiki/USR().md +66 -0
  194. package/resources/programming/basic_wiki/VAL().md +36 -0
  195. package/resources/programming/basic_wiki/VARPTR().md +50 -0
  196. package/resources/programming/basic_wiki/VDP().md +103 -0
  197. package/resources/programming/basic_wiki/VPEEK().md +46 -0
  198. package/resources/programming/basic_wiki/VPOKE.md +48 -0
  199. package/resources/programming/basic_wiki/WAIT.md +38 -0
  200. package/resources/programming/basic_wiki/WIDTH.md +76 -0
  201. package/resources/programming/basic_wiki/XOR.md +72 -0
  202. package/resources/programming/basic_wiki/_toc.json +871 -0
  203. package/resources/programming/dos_error_handling.md +85 -0
  204. package/resources/programming/toc.json +51 -36
  205. package/resources/programming/vdp_commands_speed.md +147 -0
  206. package/resources/programming/vdp_programming_faq.md +55 -0
  207. package/resources/programming/vdp_programming_tutorial.md +390 -0
  208. package/resources/programming/vdp_screensplit_programming_guide.md +166 -0
  209. package/resources/programming/vdp_scrolling_on_msx.md +124 -0
  210. package/resources/programming/vdp_the_yjk_screen_modes.md +227 -0
  211. package/resources/programming/vdp_v9938_vram_timings.md +539 -0
  212. package/resources/programming/vdp_v9938_vram_timings_part_2.md +281 -0
  213. package/resources/sdcc/toc.json +1 -1
  214. package/vector-db/index.json +1 -0
  215. /package/resources/msx-unapi/{Ethernet_UNAPI_specification_1.1.md → Ethernet_UNAPI_specification_1_1.md} +0 -0
  216. /package/resources/msx-unapi/{MSX_UNAPI_specification_1.1.md → MSX_UNAPI_specification_1_1.md} +0 -0
@@ -0,0 +1,200 @@
1
+ # Z80 programming techniques - Loops
2
+
3
+ Here I will discuss two methods to drastically increase the execution speed of different kinds of loops in assembly.
4
+
5
+ - [Fast 16-bit loops](#fast-16-bit-loops)
6
+ - [Variable length loops](#variable-length-loops)
7
+ - [Unrolling OTIRs and such](#unrolling-otirs-and-such)
8
+ - [Making LDIR 21% faster](#making-ldir-21-faster)
9
+
10
+ ## Fast 16-bit loops
11
+
12
+ Often 16-bit loops are done like this:
13
+
14
+ ```assembler
15
+ ld de,nnnn
16
+ Loop:
17
+ ; ... do something here
18
+ dec de
19
+ ld a,d
20
+ or e
21
+ jp nz,Loop
22
+ ```
23
+
24
+ Here DE counts down and D and E are OR-ed to check if the loop has completed. However there’s a much faster way to loop. In fact, looping with 16 bit counters can be as fast as with 8 bit counters. Surprised? Let me explain.
25
+
26
+ When you use a standard Z80 8-bit loop command like DJNZ, or OTIR, etc. the value of B is decreased until it reaches 0. If you want to loop 256 times, you can set the value of B to 0. So what happens if you loop 24 times, and then loop again? You’ll have looped 280 times!
27
+
28
+ This is the core of the technique. Given a 16-bit number with an MSB (the higher 8 bits) and an LSB (the lower 8 bits), you start off by looping the amount of times given by the LSB, and when B reaches 0 you do it again MSB times, each of which will loop it 256 more times. An example:
29
+
30
+ ```assembler
31
+ ld b,10 ; The LSB of the loop is 10
32
+ ld d,3 ; The MSB of the loop + the first loop is 3
33
+ Loop:
34
+ ; ... do something here
35
+ djnz Loop
36
+ dec d
37
+ jp nz,Loop
38
+ ```
39
+
40
+ This will loop 522 times (50AH).
41
+
42
+ Now the real story is a little more complex than described above. As you can see the outer loop counter is the MSB + 1. However this is not always the case. Consider this: to loop 511 times (1FFH) you have to use B = 255 and D = 2, one loop of 255 iterations and then another of 256. However to loop 512 times (200H) you have to use B = 0, but D = 2 still! In general, the rule is as follows: B is the LSB, and D is the MSB increased by 1 unless B equals zero. Without this exception for a zero LSB, you will loop 256 times too often.
43
+
44
+ Let’s compare the speed of this loop with a ‘common’ 16-bit loop. Speed on the Z80 is measured in T-states, also known as clock ticks or cycles. For an overview of T-states for each instruction, check the “Timing Z80+M1” column in the instruction set overview.
45
+
46
+ The common loop in the first example uses 4 instructions to loop, which add up to a total of 28 T-states per iteration. Looking at the fast loop however, the inner loop uses only a DJNZ instruction which takes 14 T-states, just like a normal 8-bit loop. Once every 256 iterations it also executes the outer loop which takes a little longer but still less than the common loop: 25 T-states. Because the outer loop is executed so infrequently, its cost is negligible and overall we can say the fast loop is twice as fast as the common one.
47
+
48
+ The biggest downside of the fast loop is that it is no longer easy to see at a glance how many iterations the loop performs. You should probably add a comment next to the counter values to mention the total number of loops.
49
+
50
+ ### Variable length loops
51
+
52
+ If you want to have fast loops with a variable counter value, you can calculate the correct values for B and D. This could be done using a conditional increase of the MSB, however that requires a compare and a jump which isn’t the fastest way to do go about it. In stead, you can use the following calculation which takes the counter value in DE and puts the resulting separated counters in D and B:
53
+
54
+ ```assembler
55
+ ld b,e ; Mystery fast loop calculus
56
+ dec de
57
+ inc d
58
+ ```
59
+
60
+ That’ll take just 17 T-states to precalculate the values, and compared to the slow 16-bit loop you’ll regain those after a loop or two.
61
+
62
+ So, to summarize, a full-fledged fast 16-bit loop looks like this:
63
+
64
+ ```assembler
65
+ ld b,e ; Number of loops is in DE
66
+ dec de ; Calculate DB value (destroys B, D and E)
67
+ inc d
68
+ Loop:
69
+ ; ... do something here
70
+ djnz Loop
71
+ dec d
72
+ jp nz,Loop
73
+ ```
74
+
75
+ ## Unrolling OTIRs and such
76
+
77
+ When you want to send a block from your memory to a certain port, you can use the OTIR instruction. With this instruction you can specify the number of bytes to output. This is for example used in routines which execute VDP commands, where the part which sends the command usually looks like this:
78
+
79
+ ```assembler
80
+ ld hl,command ; the address where the VDP command is stored
81
+ ld c,9BH ; the VDP port to write to
82
+ ld b,15 ; the number of loops (yes, ld bc,0F9BH is faster)
83
+ otir
84
+ ```
85
+
86
+ However, if you know in advance how many loops the otir will go through, and if you don’t care too much about wasting a little space, you can also use the OUTI instruction instead of the OTIR instruction. OUTI doesn’t loop automatically, but it does its work in 18 T-states, 5 cycles faster than OTIR which needs 23 T-states per loop. So if you just unroll the OTIR in the example above using 15 OUTIs, it saves you 5 T-states per loop (except for the last one). That adds up to a grand total of 70 T-states out of 340, about 21% faster. Here’s how that looks:
87
+
88
+ ```assembler
89
+ ld hl,command
90
+ ld c,9BH
91
+ outi ; 15x OUTI
92
+ outi
93
+ outi
94
+ outi
95
+ outi
96
+ outi
97
+ outi
98
+ outi
99
+ outi
100
+ outi
101
+ outi
102
+ outi
103
+ outi
104
+ outi
105
+ outi
106
+ ```
107
+
108
+ To make this look a little more compact, many assemblers supports a repeat directive, something like:
109
+
110
+ ```assembler
111
+ REPEAT 15
112
+ outi
113
+ ENDR
114
+ ```
115
+
116
+ Check your assembler’s manual for the precise syntax, it varies and as such this directive is not portable across assemblers.
117
+
118
+ See this article for a more complete example to execute VDP commands.
119
+
120
+ ### Making LDIR 21% faster
121
+
122
+ Now, on with the lesson. Aside from OTIR you can also unroll other things. INIR, LDIR and LDDR will also greatly benefit from this method, and sometimes it is also beneficial to unroll normal loops which use DJNZ, JR or JP.
123
+
124
+ In the case of LDIR however, the number of loops is often too large to simply use an LDI that number of times. That would take up too much space. So what we can do instead is to unroll only part of the loop. Say, we need to LDIR something 256 (100H) times. Instead of LDIR we could write:
125
+
126
+ ```assembler
127
+ ld bc,256
128
+ Loop:
129
+ ldi ; 16x LDI
130
+ ldi
131
+ ldi
132
+ ldi
133
+ ldi
134
+ ldi
135
+ ldi
136
+ ldi
137
+ ldi
138
+ ldi
139
+ ldi
140
+ ldi
141
+ ldi
142
+ ldi
143
+ ldi
144
+ ldi
145
+ jp pe,Loop ; Loop until bc = zero
146
+ ```
147
+
148
+ This method is almost 19% faster than an LDIR. Quit a significant performance gain! Note that LDI sets the parity flag to even as long as BC != 0. The jump at the end adds a little extra overhead, you can increase the number of LDIs to gain an additional few percents of speed up to almost 22%, however 16 is a nice compromise between speed and code size.
149
+
150
+ However, as you might already have noticed, this will only work if the number of loops is a multiple of 16. If it’s not, BC will never be 0 at the end of a series of 16 LDIs and we’ll end up in an endless loop followed by a reset or crash because eventually it’ll overwrite itself or system memory. That’s not what we want. If the number of loops is known in advance, it’s easiest to just put some additional LDIs before the loop.
151
+
152
+ However if the number of loops is unknown, or you simply want a faster but generic alternative for LDIR, you could try and detect when the last loop is started, and in that case let a ‘normal’ LDIR handle the last few loops. Well, that isn’t too hard, it can be handled using a few compares.
153
+
154
+ A faster and slightly trickier method is to jump inside the series of LDIs based on the count modulo the number of unrolled instructions. This is put into practice in the following example:
155
+
156
+ ```assembler
157
+ ;
158
+ ; Up to 19% faster alternative for large LDIRs (break-even at 21 loops)
159
+ ; hl = source (“home location”)
160
+ ; de = destination
161
+ ; bc = byte count
162
+ ;
163
+ FastLDIR:
164
+ xor a
165
+ sub c
166
+ and 16 - 1
167
+ add a,a
168
+ di
169
+ ld (FastLDIR_jumpOffset),a
170
+ ei
171
+ jr nz,$ ; self modifying code
172
+ FastLDIR_jumpOffset: equ $ - 1
173
+ FastLDIR_Loop:
174
+ ldi ; 16x LDI
175
+ ldi
176
+ ldi
177
+ ldi
178
+ ldi
179
+ ldi
180
+ ldi
181
+ ldi
182
+ ldi
183
+ ldi
184
+ ldi
185
+ ldi
186
+ ldi
187
+ ldi
188
+ ldi
189
+ ldi
190
+ jp pe,FastLDIR_Loop
191
+ ret
192
+ ```
193
+
194
+ It first calculates the number of loops modulo the LDI series size, then uses a self modifying relative jump to start at the appropriate position in the LDI series. The set-up does add some overhead, the break-even point compared to a normal OTIR is at 21 loops. Over a large number of iterations the performance gain is 19%, quite a lot I think so I hope it’ll be of some use.
195
+
196
+ ~Grauw
197
+
198
+ ## Source
199
+
200
+ https://map.grauw.nl/articles/fast_loops.php
@@ -0,0 +1,143 @@
1
+ # GETSLT: Get slot ID of any page
2
+
3
+ The BIOS slot routines RDSLT, ENASLT, etc. all take a slot ID. Sometimes you construct this yourself, but often you need to know the slot ID of a page that was already selected before. The initialisation code of ROM cartridges for example typically wants to enable the slot of page 1 in page 2 as well.
4
+
5
+ You can determine the slot ID of a currently paged address with a GETSLT routine. There is no BIOS routine for this, but the process is described in the MSX2 Technical Handbook and several other references, along with code examples.
6
+
7
+ This is an assembly implementation which works for all pages (followed by an explanation):
8
+
9
+ ```assembly
10
+ RSLREG: equ 138H
11
+ EXPTBL: equ 0FCC1H
12
+ SLTTBL: equ 0FCC5H
13
+
14
+ ; h = memory address high byte (bits 6-7: page)
15
+ ; a <- slot ID formatted F000SSPP
16
+ ; Modifies: f, bc, de
17
+ Memory_GetSlot:
18
+ call RSLREG
19
+ bit 7,h
20
+ jr z,PrimaryShiftContinue
21
+ rrca
22
+ rrca
23
+ rrca
24
+ rrca
25
+ PrimaryShiftContinue:
26
+ bit 6,h
27
+ jr z,PrimaryShiftDone
28
+ rrca
29
+ rrca
30
+ PrimaryShiftDone:
31
+ and 00000011B
32
+ ld c,a
33
+ ld b,0
34
+ ex de,hl
35
+ ld hl,EXPTBL
36
+ add hl,bc
37
+ ex de,hl
38
+ ld a,(de)
39
+ and 80H
40
+ or c
41
+ ret p
42
+ ld c,a
43
+ inc de ; move to SLTTBL
44
+ inc de
45
+ inc de
46
+ inc de
47
+ ld a,(de)
48
+ bit 7,h
49
+ jr z,SecondaryShiftContinue
50
+ rrca
51
+ rrca
52
+ rrca
53
+ rrca
54
+ SecondaryShiftContinue:
55
+ bit 6,h
56
+ jr nz,SecondaryShiftDone
57
+ rlca
58
+ rlca
59
+ SecondaryShiftDone:
60
+ and 00001100B
61
+ or c
62
+ ret
63
+ ```
64
+
65
+ ## Explanation
66
+
67
+ The MSX slot selection system divides the 64K CPU memory address space into 4 pages, from page 0 (0000H-3FFFH) to page 3 (C000H-FFFFH). In each of these pages you can select a different slot. Some of these slots are internal, others are for the external cartridge slots. The BIOS routines idenfity a slot with a slot ID, using the bit-format FxxxSSPP. The two P bits indicate the primary slot, the F bit indicates whether the primary slot is expanded, and if so the S bits indicate the secondary slot.
68
+
69
+ ```assembly
70
+ ; h = memory address high byte (bits 6-7: page)
71
+ ; a <- slot ID formatted F000SSPP
72
+ ; Modifies: f, bc, de
73
+ Memory_GetSlot:
74
+ call RSLREG
75
+ bit 7,h
76
+ jr z,PrimaryShiftContinue
77
+ rrca
78
+ rrca
79
+ rrca
80
+ rrca
81
+ PrimaryShiftContinue:
82
+ bit 6,h
83
+ jr z,PrimaryShiftDone
84
+ rrca
85
+ rrca
86
+ PrimaryShiftDone:
87
+ and 00000011B
88
+ ```
89
+
90
+ This first part determines the value for the primary slot number (PP bits) by reading the primary slot register through the RSLREG BIOS routine. The returned byte has two bits to indicate the primary slot for each of the pages, in the bit-format 33221100. Because the slot ID specifies the primary slot number (PP) in bits 0-1, we shift until the appropriate bits are in the correct position.
91
+
92
+ Note that if you need a shorter version hardcoded for a specific page, you can eliminate the conditional code here.
93
+
94
+ ```assembly
95
+ ld c,a
96
+ ld b,0
97
+ ex de,hl
98
+ ld hl,EXPTBL
99
+ add hl,bc
100
+ ex de,hl
101
+ ld a,(de)
102
+ and 80H
103
+ or c
104
+ ret p
105
+ ```
106
+
107
+ In this second part we determine the value for the expanded flag (F bit) by inspecting the EXPTBL table in system memory. This table consists of 4 bytes, one for each slot, where bit 7 indicates whether the slot is expanded. Because in rare cases bits 0-6 can contain a non-zero value, we and the value with 80H to mask out the unwanted bits before we insert it into the slot ID. If the slot is not expanded, we stop here.
108
+
109
+ ```assembly
110
+ ld c,a
111
+ inc de ; move to SLTTBL
112
+ inc de
113
+ inc de
114
+ inc de
115
+ ld a,(de)
116
+ bit 7,h
117
+ jr z,SecondaryShiftContinue
118
+ rrca
119
+ rrca
120
+ rrca
121
+ rrca
122
+ SecondaryShiftContinue:
123
+ bit 6,h
124
+ jr nz,SecondaryShiftDone
125
+ rlca
126
+ rlca
127
+ SecondaryShiftDone:
128
+ and 00001100B
129
+ or c
130
+ ret
131
+ ```
132
+
133
+ Lastly we determine the value for the secondary slot number (SS bits). Instead of reading from the secondary slot registers directly, which is a rather complicated affair, we advance the pointer to the SLTTBL table, which is a mirror of the four secondary slot registers. These have two bits to indicate the secondary slot for each memory page, in the same bit-format 33221100 as the primary slot register. Because the slot ID specifies the secondary slot number (SS) in bits 2-3, we shift until the appropriate bits are in the correct position.
134
+
135
+ Again note that if you need a shorter version hardcoded for a specific page, you can eliminate the conditional code here.
136
+
137
+ Now we have fully determined the slot ID for a page.
138
+
139
+ ~~Grauw
140
+
141
+ ## Source
142
+
143
+ https://map.grauw.nl/sources/getslot.php
@@ -0,0 +1,202 @@
1
+ # Interrupts
2
+
3
+ Many machine language programmers will have written a program that needed to be called from the interrupt routine. Sometimes such a program does not work well, or at all, for some vague reason. Time to shine some light on those shady interrupts.
4
+
5
+ Author: Ramon van der Winkel
6
+ Published in: MSX Computer Magazine 51 / MemMan TDK
7
+ Translated by: Laurens Holst
8
+
9
+ To extend the BIOS interrupt routine, the extension has to be attached to one of the two interrupt hooks. A choice has to be made between the hooks H.KEYI and H.TIMI. Both are invoked by the interrupt routine, however there are differences.
10
+
11
+ The H.KEYI hook is invoked every interrupt, while the H.TIMI hook is invoked at regular intervals. 50 or 60 times per second, to be precise. For example, a background song should be attached to H.TIMI, because the playback speed has to be regular.
12
+
13
+ An interrupt is — simply put — a notification from a device to the processor. With an interrupt the device tells the processor to take action. A modem can use an interrupt to notify the processor that a character has been received. Because a program that fetches the character from the modem has to be invoked every interrupt, it should be attached to H.KEYI.
14
+
15
+ ## Set-up
16
+
17
+ Interrupt routines have to be written such that they can be invoked at every possible moment. They can not change anything that would negatively impact the functioning of the main program. All register values must be preserved for example, otherwise the main program could yield some very strange results if an interrupt routine would just change the contents of a couple of registers. Also they need to account for the current state of the primary and secondary slot registers and possibly the memory mapper setting.
18
+
19
+ The preservation of the register values is done by the interrupt handler in the BIOS. A program which is attached to these hooks does not need to preserve them as well. There is one exception to this: programs who attach themselves to the H.TIMI hook must preserve register A. This register is a copy of VDP status register S#0, which is read by the BIOS routine before H.TIMI is invoked. After the hook returns the BIOS uses the contents of this register.
20
+
21
+ One of the devices that generate interrupts in the MSX is the video processor. It generates and interrupt at the moment the last line of the active area of the screen — where the text and graphics can appear — is displayed. Because this happens 50 times per second, this means that each second there are 50 interrupts coming from the VDP. If the VDP is set to 60Hz, the interrupts will also be generated 60 times per second.
22
+
23
+ ## Handling
24
+
25
+ The BIOS interrupt routine first invokes the H.KEYI hook. Next it checks whether the interrupt originates from the VDP. If not, the interrupt routine ends, otherwise the second part of the routine is executed.
26
+
27
+ This part invokes the H.TIMI hook and then performs a couple of standard activities, such as reading the keyboard, executing Basic’s ON INTERVAL GOSUB and ON STRIG GOSUB instructions, incrementing the Basic variable TIME, handling the PLAY statement, et cetera. See the overview that accompanies this article for more details about what tasks the interrupt routing performs exactly.
28
+
29
+ To determine whether an interrupt originates from the VDP, it looks at the contents of the afore mentioned VDP register S#0. Bit 7 of this register is set by the VDP the moment the last line of the screen has been displayed and the interrupt is generated. After the register is read, the bit is automatically reset to 0. While this bit is 1 the VDP will not generate new interrupts. At every VDP interrupt this bit needs to be read, otherwise the VDP will not generate any new interrupts.
30
+
31
+ When the H.TIMI hook is invoked, register A will contain the value of VDP register S#0 that was just read. After the hook returns this will be stored in the system variable STATFL. For this reason the contents of register A need to be preserved by programs that attach themselves to the H.TIMI hook.
32
+
33
+ ### Global overview of the tasks performed by the MSX BIOS interrupt routine
34
+
35
+ #### Always
36
+ - Stack all registers
37
+ - Invoke H.KEYI hook
38
+ - Read VDP status register S#0
39
+ - Turbo R: Check PAUSE key
40
+ - Check VDP interrupt
41
+
42
+ #### At VDP interrupt
43
+ - Invoke H.TIMI hook
44
+ - Enable Interrupts (EI)
45
+ - Save S#0 in STATFL
46
+ - ON INTERVAL GOSUB
47
+ - Increase TIME variable (JIFFY)
48
+ - Handle PLAY
49
+
50
+ #### Every second VDP interrupt
51
+ - Keyboard scan
52
+ - Processing keys
53
+ - ON STRIG GOSUB
54
+ - Key repetition
55
+
56
+ #### Always
57
+ - Restore all registers
58
+ - Enable Interrupts (EI)
59
+ - RETurn from Interrupt (RETI)
60
+
61
+ ## Types
62
+
63
+ The Z80 processor — and of course the compatible R800 — has three ways to handle interrupts, of which only two are really used on the MSX. These ways are called Interrupt Modes. They are numbered 0 through 2. The interrupt mode can be selected using one of the machine language instructions IM 0, IM 1 or IM 2.
64
+
65
+ IM 0 is not used on the MSX system. In this mode a device can provide the CPU with an instruction to execute. The most used interrupt mode on MSX is IM 1. In this mode the processor always calls a fixed address: 0038h. At this address every MSX BIOS has a jump to the interrupt routine.
66
+
67
+ The other interrupt mode in use is IM 2, on the MSX it is used by CP/M. In this mode the address that is called is determined by combining the I register and a byte from the device. The I register can be set by the program and contains the high part of the address. The low part is provided by the device. The combined address isn’t the address of the interrupt routine yet, but it specifies a memory location that contains the routine’s start address. Because the routine’s address always has to be in an even memory location, 128 different interrupt routines can be called. The device determines which.
68
+
69
+ In CP/M all 128 possible memory addresses are filled with the same value, so that all interrupts will end up at the same routine. Just to be sure, CP/M’s authors also had the interrupt routine start at an address where the high and low parts of the address are identical. In case an interrupt would occur where the device provides a value with bit 0 set, it will still end up at the correct address. An address with bit 0 set can occur if the device does not provide a specific value at all, because it expects interrupt mode 1 to be active.
70
+
71
+ The program can inhibit interrupts, preventing them from occurring. To do so the program needs to tell the CPU that interrupts generated by devices should not be handled. The CPU has two flags for this, named IFF1 and IFF2. IFF is an abbreviation of Interrupt Flip Flop. When the CPU detects an interrupt, it first checks IFF1. If this flag is set the interrupt is handled, otherwise it is ignored.
72
+
73
+ The machine language instructions to influence these flags are Disable Interrupts (DI) and Enable Interrupts (EI). A DI instruction will clear both flags and cause the CPU to ignore interrupts. After an EI instruction the flags will be set and interrupts will be accepted again.
74
+
75
+ Besides the above mentioned interrupts there is also a type of interrupt which can not be ignored by the CPU. This is the so-called Non Maskable Interrupt or NMI. An NMI is processed similar to an interrupt in interrupt mode 1, however in this case the CPU will call address 0066h. Even though these interrupts do not normally occur on MSX computers, the MSX BIOS does have a NMI interrupt handling routine at that address.
76
+
77
+ MSX-DOS only provides a handler for ‘normal’ interrupts. This routine enables the BIOS in page 0 and invokes its interrupt routine. Afterwards it re-enables RAM in page 0. Non Maskable Interrupts can not occur in MSX-DOS because there is no handling routine for it. Address 0066h is right in the middle of MSX-DOS’s first FCB — File Control Block — buffer, which starts at address 005Ch.
78
+
79
+ |Address|Description|
80
+ |:-:|:--|
81
+ |FD9Ah|H.KEYI hook|
82
+ |FD9Fh|H.TIMI hook|
83
+ |FDD6h|H.NMI hook|
84
+ |F3E7h|STATFL variable|
85
+ |FC9Eh|JIFFY variable|
86
+ |0038h|IM 1 interrupts entry point|
87
+ |0066h|NMI interrupts entry point|
88
+
89
+ ## The call
90
+
91
+ Before the interrupt routine is executed, the program counter is saved to the stack. After the interrupt routine ends this address is retrieved and the main program continues. In other words, the interrupt routine is executed as if the main program executes a CALL to a subroutine, in this case the interrupt routine.
92
+
93
+ To prevent nesting the CPU automatically resets the IFF1 and IFF2 flags (DI). Before calling an NMI routine IFF1 is copied into IFF2 and only IFF1 is reset, so that the state of IFF1 can be restored at the end of the NMI routine.
94
+
95
+ Just like normal routines, interrupt routines can be terminated with a RET instruction. However, the CPU has two special instructions to end interrupt routines: RETurn from Interrupt (RETI) and RETurn from Non maskable interrupt (RETN).
96
+
97
+ The RETI instruction can be recognised by a device at the moment it is executed by the CPU, so that it can know that its interrupt has been handled. For this reason it’s better to use a RETI than a RET, although they are identical in function. Note that IFF1 is not set by the RETI instruction. This must be done with an EI instruction in the interrupt routine.
98
+
99
+ The RETN instruction at the end of an NMI-routine however **is needed** to restore IFF1. RETN copies the state of IFF2 into IFF1, so that it is restored to the value it had before the NMI occurred. Apart from this a RETN instruction behaves like a normal RET instruction.
100
+
101
+ ## VDP interrupts again
102
+
103
+ Right after the VDP displayed the last line of the active display area, bit 7 of VDP register S#0 is set. At the same time the interrupt signal is activated and the CPU recognises the interrupt. At this moment the IFF1 and IFF2 flags are automatically reset, preventing the CPU from handling further interrupts.
104
+
105
+ However, the interrupt signal from the VDP stays active until the VDP status register S#0 is read. So if the interrupt routine executes an EI instruction to re-enable interrupts while S#0 has not been read yet, the CPU will immediately recognise the interrupt from the VDP again (it was still active after all) and proceed to execute the interrupt routine again, even though the previous call hadn’t ended yet.
106
+
107
+ The result is probably clear: an infinite loop where the CPU places a new return address on the stack at every new interrupt. This will in no-time cause the entire memory to fill up with return addresses, overwriting program code and most likely causing the computer to crash.
108
+
109
+ Besides the interrupt the VDP generates when the active area has been displayed, the VDP can also generate another interrupt. The moment that the VDP should do this can be set by the software in VDP register R#19. In this register a Y-coordinate can be specified. While the VDP is displaying the screen, the moment the it reaches this line an interrupt is generated.
110
+
111
+ ## Programming tips
112
+
113
+ When writing a routine that needs to be called on an interrupt, a choice has to be made between the two available hooks. A routine attached to the H.KEYI hook can never execute an EI instruction, because the BIOS interrupt routine hasn’t read the VDP status register S#0 yet. A routine on the H.TIMI hook can enable interrupts, but has to preserve the contents of register A.
114
+
115
+ The BIOS interrupt routine reads the VDP status register S#0 by directly reading the VDP’s command port, rather than first selecting the appropriate status register via register R#15 before reading the port. For this reason programs must always keep register R#15 set to 0 when the interrupts are enabled. If the program needs to read another status register, it has to disable the interrupts before doing so.
116
+
117
+ It is possible to write a custom interrupt routine which replaces the existing BIOS routine entirely. If this is done, keep in mind that all registers must be preserved and don’t forget to read the VDP status register S#0. Based on this value different subroutines can be chosen within the custom interrupt routine.
118
+
119
+ To end a ‘normal’ interrupt routine interrupts have to be re-enabled with an EI instruction. To prevent nesting the CPU will ignore interrupts for one more instruction after the EI instruction. This allows a subsequent RETI to return to the main program and prevents the stack from overflowing from a quick succession of interrupts, as these will only be recognised after the RET. So, the last two instructions in an interrupt routine are always:
120
+
121
+ ```assembly
122
+ EI
123
+ RETI
124
+ ```
125
+
126
+ An NMI-routine should not use DI and EI instructions, because these also change the IFF2 flag. This flag needs to be preserved because IFF2 contains a copy of IFF1 while the NMI routine is executing, which needs to be restored when the NMI ends.
127
+
128
+ Interrupt routines — including all routines attached to the interrupt hooks — should not take too much time. If the VDP generates a new interrupt before the routine started by the previous interrupt has ended, it will immediately be recognised when the interrupts are re-enabled. If this happens, the main program will be starved and not be able to execute another instruction.
129
+
130
+ When having a custom interrupt routine in RAM, be aware when calling BIOS routines that during their execution page 0 will no longer contain the RAM with the new routine at address 0038h. In stead when an interrupt occurs the BIOS’s interrupt routine will become active. To prevent this, take care to always keep the IFF1 flag reset with a DI instruction. Also keep in mind that some BIOS routines re-enable interrupts. This is certainly done by the SUBROM, since every entry in the jump table contains an EI instruction!
131
+
132
+ _[Translator’s note: For this reason, it may be preferable to use IM 2 for custom interrupt routines. Note; I would recommend to use the hooks and avoid creating a custom interrupt routine unless there are strong reasons to do so. Issues with compatibility and ever-spinning disk drives are lying in wait.]_
133
+
134
+ Example program application of Interrupt Mode 2
135
+
136
+ ```assembly
137
+ ;
138
+ ; im2.gen - RWi
139
+ ;
140
+ ; Example of how to use Interrupt Mode 2
141
+ ;
142
+ intVecTabAd equ 08000h ;Put the Interrupt Vector Table here
143
+ intVecHalfAd equ 080h ;High memory address pointer
144
+ intRoutStart equ 08181h ;Let interrupt routine start here
145
+ intRoutHalfAd equ 081h ;Address high and low
146
+
147
+ im2: di ;No interrupts during switch
148
+
149
+ ld hl,intVecTabAd ;Generate IVT here
150
+ ld (hl),intRoutHalfAd ;Use this as high and low address part
151
+ ld d,h ;Copy destination pointer from
152
+ ld e,l ; the source pointer
153
+ inc de ;Destination 1 byte further
154
+ ld bc,128*2 ;128 vectors, 1 byte extra for 256th
155
+ ldir ;Generate table
156
+
157
+ ld hl,intRoutHere ;Routine for IM 2
158
+ ld de,intRoutStart ;Put routine here
159
+ ld bc,intRoutLen ;Length of the routine
160
+ ldir ;Copy the routine
161
+
162
+ ld a,intVecHalfAd ;Use this as high address part
163
+ ld i,a ;Set high address part
164
+ im 2 ;Switch to IM 2
165
+
166
+ ei ;Now interrupts are permitted again
167
+
168
+ loop: jp loop ;Endless loop
169
+
170
+ intRoutHere: equ $ ;Code is now here
171
+
172
+ org intRoutStart
173
+
174
+ intRoutIM2: push hl ;Save registers that are modified
175
+ push af
176
+
177
+ ld hl,(counterIM2) ;Nr. of interrupts counter
178
+ inc hl ;Increase by one
179
+ ld (counterIM2),hl ;And save
180
+
181
+ in a,(099h) ;Read S#0
182
+ and a ;Does INT originate from VDP (b7=1 - True)
183
+ jp p,notFromVDP ;No = Return
184
+
185
+ ld a,l ;Lower counter part
186
+ out (098h),a ;Put it on the screen
187
+
188
+ notFromVDP: pop af ;Restore modified registers
189
+ pop hl
190
+ ei ;Interrupts are permitted again
191
+ reti ;Return to main program
192
+
193
+ counterIM2: defw 1 ;Nr. of interrupts counter
194
+
195
+ intRoutLen: equ $-intRoutIM2 ;Length of routine code
196
+
197
+ end ;im2.gen
198
+ ```
199
+
200
+ ## Source
201
+
202
+ https://map.grauw.nl/articles/interrupts.php