forked from MEGA65/mega65-user-guide
-
Notifications
You must be signed in to change notification settings - Fork 0
/
memory.tex
2012 lines (1647 loc) · 82.4 KB
/
memory.tex
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
% TODO: index terms
\chapter{Programming with Memory}
\label{cha:programming-with-memory}
When you write programs in assembly language, understanding how memory works is
crucial. Nearly every operation interacts with memory in some form. Data
processing happens in memory, and programs are stored in and executed from memory.
With a Commodore-style hardware register architecture, every aspect of the
computer's input and output involves manipulating values through the memory system.
You can write a substantial program in MEGA65 BASIC without having to
think about the computer's memory. The BASIC system and most of its commands
represent data in terms of numbers, arrays, and strings stored in named variables.
You never have to know how that data is represented as 1's and 0's, or which addresses
refer to which data. That said, understanding how memory works can take your
BASIC programs to the next level. You can invent new ways to manage your
program's data, and interface directly with MEGA65 hardware. Some of the
MEGA65's advanced capabilities are only accessible from memory registers.
Like several systems in the MEGA65, the memory system is more sophisticated than you
might expect, with multiple modes of operation. When you first turn on your
MEGA65, it starts with a memory configuration intended to support the
BASIC 65 environment. A program can reconfigure the memory to take full
advantage of the MEGA65's capabilities, or to make the memory environment
compatible with a Commodore 64 (as \stw{GO64} does) or a Commodore 65.
In this chapter, you will learn all about the MEGA65 memory system, with a
focus on writing programs for the MEGA65 in BASIC or machine language. Complete
descriptions of the memory features, including backwards compatibility features
for the C64 and C65, are available in the appendicies.
\newpage
\section{Memory Concepts}
\label{sec:programming-with-memory-concepts}
A computer performs tasks by manipulating data stored in its {\em memory}.
Computer memory consists of many thousands of cells, each containing a single
{\em byte} of data capable of representing 256 possible values. Each cell has a numeric
{\em address}, and the program using the memory decides what kind of data is stored
at each address. The program itself is stored as bytes in memory, as
machine code instructions to be executed by the CPU.
\subsection{Bits, Bytes, and Nibbles}
A {\em bit} is a single digital signal with two possible values: off or on. A
bit is often combined with other bits to represent more possible values. A byte
is 8 bits, with 256 possible values.
Bits and bytes are often used to represent numbers, and often have their values
described as positive integers even when they represent other kinds of data.
When discussing the bits of a byte, the bits are arranged in increasing place
value from right to left. Bit 0 is the rightmost bit, also known as the
``lowest'' bit. Bit 7 is the leftmost bit, also known as the ``highest'' bit.\footnote{See
\bookvref{cha:decimal-binary-and-hexadecimal} for a complete explanation of
binary and hexadecimal notation.}
\begin{center}
\begin{tabular}{ccccccccl}
{\bf b\textsubscript{7}} &
{\bf b\textsubscript{6}} &
{\bf b\textsubscript{5}} &
{\bf b\textsubscript{4}} &
{\bf b\textsubscript{3}} &
{\bf b\textsubscript{2}} &
{\bf b\textsubscript{1}} &
{\bf b\textsubscript{0}} & \\
\huge\texttt{0} &
\huge\texttt{1} &
\huge\texttt{1} &
\huge\texttt{0} &
\huge\texttt{1} &
\huge\texttt{0} &
\huge\texttt{1} &
\huge\texttt{1} & = 107 \\
\small{}128 &
\small{}64 &
\small{}32 &
\small{}16 &
\small{}8 &
\small{}4 &
\small{}2 &
\small{}1 & \\
\end{tabular}
\end{center}
A {\em nibble} is 4 bits. In computer architecture, the term ``nibble'' refers
exclusively to one of the two halves of a byte. The {\em high nibble} is bits 7 --
4, and the {\em low nibble} is bits 3 -- 0.
\subsection{Hexadecimal Notation}
Hexadecimal (``hex'') is a numbering system using sixteen possible values per digit,
written as numerals 0 -- 9 followed by letters A -- F. It is especially
convenient when describing values in computer memory because each hex digit
represents a nibble (4 bits) of data. The 8-bit binary value \texttt{\%01101011} can be
read as the two nibbles \texttt{0110 1011}, or \$6B in hexadecimal.
Memory addresses are often presented in hexadecimal notation. Computer memory
is typically organized in amounts that can be represented as round hexadecimal
numbers, like \$C000.
\begin{center}
\begin{tabular}{|c|c|c|c|c|c|c|c|c}
\cline{1-3}\cline{5-8}
\$0000 & \$0001 & \$0002 & $\cdots$ & \$0FFE & \$0FFF & \$1000 & \$1001 &
$\cdots$ \\
\cline{1-3}\cline{5-8}
\end{tabular}
\end{center}
Round hexadecimal numbers represent common memory sizes, like so:
\begin{center}
\begin{tabular}{rl}
\$0001 & 1 byte \\
\$0010 & 16 bytes \\
\$0100 & 256 bytes \\
\$0400 & 1 kilobyte (1KB = 1024 bytes) \\
\$1000 & 4 kilobytes (4KB = 4096 bytes) \\
\$4000 & 16KB \\
\$10000 & 64KB \\
\$100000 & 1 megabyte (1MB = 1024KB) \\
\end{tabular}
\end{center}
Address numbering starts at 0, so regions of these common sizes tend to start
with ``0'' digits on the right, and stop just before the start of the next region.
For example, \$2000 -- \$2FFF represents a 4KB region.
You can enter numbers as hexadecimal in BASIC using the dollar sign prefix
(\$). To display the decimal equivalent of a hexadecimal number in BASIC:
\begin{screenoutput}
PRINT $C000
\end{screenoutput}
To display a number in hexadecimal notation in BASIC, use the
{\bf HEX\$()}\index{BASIC 65 Functions!HEX\$} function:
\begin{screenoutput}
PRINT HEX$(127)
\end{screenoutput}
When programming in assembly language or using a machine language
monitor, addresses and values are presented in hexadecimal by default.
Much like how commas are used to separate decimal digits in long numbers, this
book sometimes uses a dot (.) to separate groups of four hex digits to make
them easier to read: FFF.FFFF. You do not type the dot when entering a long
address into a program or command. To enter FFF.FFFF in BASIC, type
\stw{\$FFFFFFF}.
Each hex digit represents a nibble of a value. The value FFF.FFFF can be
represented in seven nibbles (28 bits).
\subsection{Random Access Memory}
{\em Random Access Memory}, or RAM, is memory that contains data temporarily. A
program can read from and write to this data. When the computer is turned off,
this data is deleted. A program that needs to
preserve this data must save it to a long-term storage device like a floppy
disk (or the MEGA65 SD card) before the computer is powered down.
Try this. Enter the following command at the \screentext{READY.} prompt:
\begin{screenoutput}
POKE $1800,127
\end{screenoutput}
This {\bf POKE}\index{BASIC 65 Commands!POKE} command stores the byte value
127 at memory address \$1800. Now enter this command:
\begin{screenoutput}
PRINT PEEK($1800)
\end{screenoutput}
The {\bf PEEK()}\index{BASIC 65 Commands!PEEK} function evaluates to the byte
value stored at the memory address \$1800, in this case 127. The {\bf
PRINT}\index{BASIC 65 Commands!PRINT} command outputs this value.
Try turning off your MEGA65, turning it back on, and executing
\screentext{PRINT PEEK(\$1800)} again. The RAM value is reset.
\subsection{Read-Only Memory}
{\em Read-Only Memory}, or ROM, contains data that is written permanently at
the factory. Like other vintage microcomputers, the Commodore 65 has a ROM chip
that contains the program code for the operating system: BASIC, the kernel,
routines for accessing peripherals and performing common calculations. ROM
data cannot be overwritten directly by a program, and it remains in the
computer when the computer is turned off.
The MEGA65 does not contain a traditional ROM chip. Instead, the MEGA65
Hypervisor (the operating system that manages the features of the MEGA65)
loads the ``ROM'' program data from a file on the SD card into a region of RAM.
The Hypervisor protects that RAM as ``read only'' by default, simulating the ROM
chip of a Commodore 65. You can replace the ROM file on the SD card to upgrade
to newer versions of the MEGA65 ROM.
Try this. Enter these commands, and try to predict what the second command will
display. Change 63 to another number then try again.
\begin{screenoutput}
POKE $2100,63
PRINT PEEK($2100)
\end{screenoutput}
The address \$2100 refuses to accept a new value. A complete explanation
of why this is requires several concepts described in this chapter. For now, it
is sufficient to say that the memory at address \$2100 is configured to behave
as ROM when accessed in this way.
\subsection{I/O Registers}
Some addresses refer to special memory cells used by the computer's hardware.
These are known as {\em I/O registers}. You can interact with the hardware by writing
data to and reading data from these addresses. This is how BASIC commands
display graphics, play sounds, and read joysticks: they read and set values in
the I/O registers.
Try this command. Try replacing 7 with another number between 0 and 31.
\begin{screenoutput}
POKE $D020,7
\end{screenoutput}
The VIC video chip uses address \$D020 to store the colour of the screen border.
The {\bf BORDER} command sets this register to achieve the same effect.
\begin{screenoutput}
BORDER 7
\end{screenoutput}
\subsection{Addresses}
The examples so far have used addresses consisting of four hexadecimal digits,
such as \$1800. Each hexadecimal digit represents four bits (one nibble), so a four-digit
hexadecimal number represents a 16-bit address. Such an address is in the range
\$0000 -- \$FFFF, with 65,536 possible addresses. The Commodore 64 and its MOS
6502 CPU use 16-bit addresses to access up to 64 kilobytes (64KB) of memory.
The MEGA65 has an address space of 28 bits, in the
range 000.0000 -- FFF.FFFF. Such addresses can be stored as a 32-bit value with
the highest nibble set to 0. In hexadecimal, this would be an
eight-digit number with the leftmost digit of 0. (This digit is typically
omitted from notation.)
The BASIC {\bf POKE} command and {\bf PEEK()} function can use 28-bit
addresses. For example, to store then retrieve a value from RAM at address \$8000000:
\begin{screenoutput}
POKE $8000000,63
PRINT PEEK($8000000)
\end{screenoutput}
This example works on the MEGA65 and the DevKit. The Nexys board lacks RAM
at that address, and will ignore attempts to write it.
\subsection{16-bit Address Translation}
The MEGA65's memory system allows programs to refer to memory using 16-bit
addresses. To determine the full 28-bit address, the memory system translates
the address according to rules that can be set by the program. This translation
is also known as {\em remapping}, or, in some contexts, {\em
banking}.
16-bit address translation provides many advantages. The 45GS02 CPU processes
16-bit addresses faster than 32-bit addresses. 16-bit addresses take up less
space in memory when stored. Most program tasks operate in a single 64KB region
of memory, so it is often convenient for a program to set the memory
configuration at the beginning of a task and use 16-bit addresses.
Address translation is a common feature of most microcomputers. The Commodore
64 uses banking to allow programs to access 64KB of RAM {\em and} additional
ROM and I/O registers using only 16-bit addresses. The Commmodore 65 was
designed such that the memory system could be configured to resemble a
Commodore 64 memory map through 16-bit address translation, and also support
C64-style banking of ROM and I/O registers.
Overall, the MEGA65 provides multiple mechanisms of address translation. Memory
system configuration is one of the more complex topics of MEGA65 programming,
and this chapter covers it in detail.
\subsection{Banks and Pages}
MEGA65 addresses can be divided into regions of 64KB known as {\em banks}. It's
easy to identify the bank given an address specified in hexadecimal: the bank
number is the leftmost digits of the address, and the rightmost four digits are
a location within the bank.
\medskip
\begin{center}
\begin{tabular}{m{0.4cm}m{0.4cm}m{0.05cm}m{0.4cm}m{0.4cm}m{0.4cm}m{0.4cm}}
\multicolumn{1}{c}{\huge\texttt{\$}} &
\multicolumn{1}{c}{\huge\texttt{3}} &
\multicolumn{1}{c}{ } &
\multicolumn{1}{c}{\huge\texttt{F}} &
\multicolumn{1}{c}{\huge\texttt{D}} &
\multicolumn{1}{c}{\huge\texttt{A}} &
\multicolumn{1}{c}{\huge\texttt{8}} \\
\cline{2-2}\cline{4-5}
& \multicolumn{1}{c}{\small Bank} & & \multicolumn{2}{c}{\small Page} &
\multicolumn{2}{c}{ } \\
\end{tabular}
\end{center}
\medskip
For example, the address 3.FDA8 (or \$003FDA8) is an address in bank \$3. The
address 0.1800 (\$0001800) is an address in bank \$0. The address 880.12AB
(\$88012AB) is in bank \$880.\footnote{In a few cases, this is better understood as
``megabyte \$88, bank \$0, offset \$12AB.''}
It is important to remember that if you see a 16-bit
address such as \$2100, the actual bank that it refers to may not be bank 0.
The full address depends on how the memory system is configured, and how the
memory is accessed.
A bank can be further divided into regions of 256 bytes known as {\em pages}.
As with banks, the page is easy to identify from a hexadecimal address: it is
the fourth and third rightmost digits. For example, page \$1C consists of
\$1C00 -- \$1CFF. The address \$1CB2 is in page \$1C.
\subsection{How Addresses are Stored in Memory}
Computers often manipulate memory addresses as data, including storing
addresses in RAM or in registers. A 16-bit address is stored as two bytes,
and a 28-bit address is stored as four bytes (with the leftmost nibble set
to zero).
To determine the bytes for an address, take the hexadecimal digits and
group them in pairs, starting from the {\em rightmost} pair. Each pair of
digits is a byte value. For example, the address \$853FDA8 can be represented
by the bytes \$A8, \$FD, \$53, and \$08.
This ordering of the bytes from right to left is known as {\em Little Endian
byte order}. The MEGA65 stores multi-byte values using Little Endian byte
order by convention. The 45GS02 CPU, I/O registers, and other routines expect
addresses to be stored in this way.\footnote{There exist computers that use the
opposite ordering convention, known as {\em Big Endian} byte order. Most
microcomputers use the Little Endian convention.}
For example, to store the 28-bit address 853.FDA8, you need four bytes of
memory. To store this at memory locations 0.1800 -- 0.1803, you would
start by storing the rightmost byte (\$A8) in location 0.1800. The rest is
as follows:
\begin{center}
\begin{tabular}{cccc}
\hline
\multicolumn{1}{|c}{\huge\texttt{A8}} &
\multicolumn{1}{|c}{\huge\texttt{FD}} &
\multicolumn{1}{|c}{\huge\texttt{53}} &
\multicolumn{1}{|c|}{\huge\texttt{08}} \\
\hline
\rotatebox{90}{0.1800 } &
\rotatebox{90}{0.1801 } &
\rotatebox{90}{0.1802 } &
\rotatebox{90}{0.1803 } \\
\end{tabular}
\end{center}
When a 28-bit address is stored as four bytes, the upper four bits are unused.
There are a few cases where a 28-bit address is stored in a way that uses those
bits for something else. You must take care when writing such addresses that
the upper bits retain appropriate values. This is typically done with Boolean
logic to ``mask'' the upper bits.
The following BASIC example sets the variable \stw{B} to \$A9, then updates it
such that the upper nibble is preserved and the lower nibble is set to \$3.
Try to guess what this will print before running it.
\begin{screenoutput}
10 B = $A9
20 B = B AND $F0 OR $03
30 PRINT HEX$(B)
\end{screenoutput}
The Commodore 65 uses a 20-bit address space, so it sometimes only uses five
nibbles (five hexadecimal digits) to encode an address. In a few cases, the
MEGA65 extends this to 28 bits by storing a separate ``megabyte byte'' in
addition to a ``bank nibble'' and two bytes for the lower part of the address.
An example of this will be explained later in this chapter.
BASIC 65 includes commands for reading and writing 16-bit values as bytes
without having to convert to Little Endian explicitly. {\bf WPOKE addr,val}
sets {\bf addr} to the low byte of {\bf val} and {\bf addr+1} to the high byte.
Similarly, {\bf WPEEK(addr)} reads the bytes at {\bf addr} and {\bf addr+1} and
evaluates to the 16-bit number.
\begin{screenoutput}
WPOKE $1900,$FFD2
\end{screenoutput}
BASIC does not have dedicated commands for reading or writing 32-bit values,
but you can use the 16-bit commands to handle two bytes at a time. Take care to
use Little Endian order for each half:
\begin{screenoutput}
WPOKE $1802, $0853
WPOKE $1800, $FDA8
\end{screenoutput}
\newpage
\section{The 28-bit Address Space}
\label{sec:programming-with-memory-address-space}
The 28-bit addresses form an {\em address space} in the range 000.0000
-- FFF.FFFF. Only some addresses are assigned to memory cells. Others are assigned to I/O
registers and other specialty devices. The remaining addresses are unassigned, reserved
for future use by expansion hardware and future versions of the computer.
A MEGA65 (board revision R3A) and a DevKit (R3) have three major regions of
assigned address space:
\begin{itemize}
\item {\bf Chip RAM}, 384KB, in range 000.0000 -- 005.FFFF
\item {\bf Attic RAM}, 8MB, in range 800.0000 -- 87F.FFFF
\item {\bf I/O registers and specialty RAM}, in range F00.0000 -- FFF.FFFF
\end{itemize}
The remaining regions of the address space are reserved for future expansion
and future models. Nexys boards do not have Attic RAM. See
\bookvref{cha:memory-map} for details on the reserved regions.
Chip RAM is the primary working space for the MEGA65. It contains program code,
the MEGA65 ROM code, and space for variables and other data. It is called ``Chip
RAM'' because it can be accessed by the CPU running at full speed. BASIC and
kernel functions use portions of Chip RAM, and your BASIC and machine code
programs will make extensive use of it as well.
Attic RAM provides expanded memory for general use, with a few limitations.
Typically, a program uses Attic RAM via the MEGA65's Direct Memory Access (DMA) capability to
copy data between Attic RAM and Chip RAM (described later). Attic RAM cannot be used directly by the VIC
chip for graphics data, nor can it be used directly for audio sample playback
(``audio DMA''). The CPU can run code stored in Attic RAM, but it runs more
slowly than code stored in Chip RAM. In general, accessing Attic RAM is about
ten times slower than accessing Chip RAM.
The upper I/O register space is the permanent home for device registers, and
memory for the Hypervisor functions. Most programs access these features
indirectly using the memory configuration features described later in this
chapter, especially the VIC colour RAM, character ROM, and VIC and SID I/O
registers. See \bookvref{cha:memory-map} for a detailed list of upper I/O regions.
\newpage
\section{The Chip RAM Memory Map}
The following table summarizes how the MEGA65 kernel and BASIC use Chip RAM.
\setlength{\tabcolsep}{3pt}
\begin{longtable}{|L{1.5cm}|L{1.5cm}|p{6cm}|}
\hline
{\bf{Start}} & {\bf{End}} & {\bf{Description}} \\
\hline
\endfirsthead
\multicolumn{3}{l@{}}{\ldots continued}\\
\hline
{\bf{Start}} & {\bf{End}} & {\bf{Description}} \\
\endhead
\multicolumn{3}{l@{}}{continued \ldots}\\
\endfoot
\hline
\endlastfoot
\hline
\small 0.0000 & \small 0.0000 & \multicolumn{1}{p{6cm}|}{CPU I/O Port Data Direction}\\
\hline
\small 0.0001 & \small 0.0001 & \multicolumn{1}{p{6cm}|}{CPU I/O Port Data}\\
\hline
\small 0.0002 & \small 0.15FF & \multicolumn{1}{p{6cm}|}{Kernel variables and data}\\
\hline
\small 0.1600 & \small 0.1EFF & \multicolumn{1}{p{6cm}|}{Free for program use}\\
\hline
\small 0.1F00 & \small 0.1FFF & \multicolumn{1}{p{6cm}|}{BASIC bitmap graphics
base page}\\
\hline
\small 0.2000 & \small 0.F6FF & \multicolumn{1}{p{6cm}|}{BASIC: program text}\\
\hline
\small 0.F700 & \small 0.FEFF & \multicolumn{1}{p{6cm}|}{BASIC: scalar variables}\\
\hline
\small 0.FF00 & \small 0.FFFF & \multicolumn{1}{p{6cm}|}{Reserved}\\
\hline
\hline
\small 1.0000 & \small 1.1FFF & \multicolumn{1}{p{6cm}|}{DOS buffers and variables}\\
\hline
\small 1.2000 & \small 1.F6FF & \multicolumn{1}{p{6cm}|}{BASIC: arrays and strings}\\
\hline
\small 1.F700 & \small 1.F7FF & \multicolumn{1}{p{6cm}|}{Reserved}\\
\hline
\small 1.F800 & \small 1.FFFF & \multicolumn{1}{p{6cm}|}{Colour memory window}\\
\hline
\hline
\small 2.0000 & \small 2.FFFF & \multicolumn{1}{p{6cm}|}{ROM}\\
\hline
\small 3.0000 & \small 3.FFFF & \multicolumn{1}{p{6cm}|}{ROM}\\
\hline
\hline
\small 4.0000 & \small 4.FFFF & \multicolumn{1}{p{6cm}|}{BASIC bitmap graphics, utilities}\\
\hline
\small 5.0000 & \small 5.FFFF & \multicolumn{1}{p{6cm}|}{BASIC bitmap graphics, utilities}\\
\hline
\end{longtable}
The ROM banks are arranged in regions. Most programs don't need to access these
addresses directly.
\setlength{\tabcolsep}{3pt}
\begin{longtable}{|L{1.5cm}|L{1.5cm}|p{6cm}|}
\hline
{\bf{Start}} & {\bf{End}} & {\bf{Description}} \\
\hline
\endfirsthead
\multicolumn{3}{l@{}}{\ldots continued}\\
\hline
{\bf{Start}} & {\bf{End}} & {\bf{Description}} \\
\endhead
\multicolumn{3}{l@{}}{continued \ldots}\\
\endfoot
\hline
\endlastfoot
\hline
\small 2.0000 & \small 2.3FFF & \multicolumn{1}{p{6cm}|}{DOS}\\
\hline
\small 2.4000 & \small 2.8FFF & \multicolumn{1}{p{6cm}|}{Reserved}\\
\hline
\small 2.9000 & \small 2.9FFF & \multicolumn{1}{p{6cm}|}{Character set A}\\
\hline
\small 2.A000 & \small 2.BFFF & \multicolumn{1}{p{6cm}|}{C64 BASIC}\\
\hline
\small 2.C000 & \small 2.CFFF & \multicolumn{1}{p{6cm}|}{Interface}\\
\hline
\small 2.D000 & \small 2.DFFF & \multicolumn{1}{p{6cm}|}{C64 character set
(C)}\\
\hline
\small 2.E000 & \small 2.FFFF & \multicolumn{1}{p{6cm}|}{C64 BASIC and kernel}\\
\hline
\hline
\small 3.0000 & \small 3.1FFF & \multicolumn{1}{p{6cm}|}{Monitor}\\
\hline
\small 3.2000 & \small 3.7FFF & \multicolumn{1}{p{6cm}|}{C65 BASIC}\\
\hline
\small 3.8000 & \small 3.BFFF & \multicolumn{1}{p{6cm}|}{C65 BASIC graphics}\\
\hline
\small 3.C000 & \small 3.CFFF & \multicolumn{1}{p{6cm}|}{Reserved}\\
\hline
\small 3.D000 & \small 3.DFFF & \multicolumn{1}{p{6cm}|}{Character set B}\\
\hline
\small 3.E000 & \small 3.FFFF & \multicolumn{1}{p{6cm}|}{C65 kernel}\\
\hline
\end{longtable}
\subsection{How Programs Use Chip RAM}
There are two common kinds of MEGA65 programs: programs written entirely in BASIC,
and programs written entirely in machine code (via assembly language, or a
compiled language such as C or Rust).
A typical BASIC 65 program never accesses memory directly.\footnote{Commodore
64 BASIC programmers are accustomed to using {\bf POKE} and {\bf PEEK} to
access memory and I/O registers for most functions like graphics, sound, and
input devices. BASIC 65 provides dedicated commands for these functions.} It
uses BASIC commands and language features to manipulate variables, arrays,
strings, sprites, and bitmap graphics. The BASIC system uses all of Chip RAM to
support these features, and manages memory configuration to access registers on
behalf of the program. This requires that most of the ROM loaded and protected
by the Hypervisor remain in place.
A BASIC program that does not use the bitmap graphics system can repurpose the
related memory regions for other purposes. This includes the region 0.1F00 --
0.1FFF, and all of banks 4 and 5. If you intend to use bitmap graphics but
need some spare Chip RAM for another purpose, you can use the {\bf
MEM}\index{BASIC Commands!MEM} command to reserve blocks in banks 4 and 5. The
graphics system knows to avoid regions reserved by {\bf MEM}, at the expense
of needing to use fewer screens, lower resolution, or lower colour bit depth.
For a description of the {\bf MEM} command, see \pageref{memcommand}.
Some immediate mode BASIC tools, such as {\bf RENUMBER}, make temporary use of
memory in banks 4 and 5. This does not affect a program that initializes its
memory when it starts, but it may interfere with interactive tasks
performed at the \screentext{READY.} prompt that need that memory preserved.
A common technique for using memory from a BASIC program is to take advantage
of the unused ``program text'' memory after the end of the BASIC program data
in 0.2000 -- 0.F6FF. If your program needs to do this, note that some BASIC 65
graphics commands use some of the memory immediately following the end of the
program as temporary storage. Depending on the length of your program, it is
usually safe to choose a high address range, such as 0.C000-0.F6FF, for
program use.
\begin{center}
\begin{tabular}{m{0.14cm}m{0.06cm}m{1.45cm}m{0.21cm}m{1.4cm}m{0.1cm}m{0.1cm}m{3.3cm}m{3.3cm}l}
\cline{1-1}\cline{3-9}
\multicolumn{1}{|l|}{\rotatebox{90}{Kernel}} & \multicolumn{1}{l}{\ldots} &
\multicolumn{1}{|l}{\rotatebox{90}{BASIC}} & \multicolumn{1}{|l}{\rotatebox{90}{DOS}} &
\multicolumn{1}{|l}{\rotatebox{90}{BASIC}} & \multicolumn{1}{|l}{\rotatebox{90}{Res.}} &
\multicolumn{1}{|l}{\rotatebox{90}{Colour}} & \multicolumn{1}{|l}{\rotatebox{90}{ROM}} &
\multicolumn{1}{|l|}{\rotatebox{90}{BASIC Gfx }} & \\
\cline{1-1}\cline{3-9}
\rotatebox{90}{\small 0.0000} & \rotatebox{90}{\small 0.1600} &
\rotatebox{90}{\small 0.2000} & \rotatebox{90}{\small 1.0000} &
\rotatebox{90}{\small 1.2000} & \rotatebox{90}{\small 1.F700} &
\rotatebox{90}{\small 1.F800} & \rotatebox{90}{\small 2.0000} &
\rotatebox{90}{\small 4.0000} & \rotatebox{90}{\small 5.FFFF} \\
\end{tabular}
\end{center}
A typical program written in machine code never uses the BASIC system at all,
and instead manages its own memory. It might use a short BASIC program to
start the program, but never returns control to the system after the program
is invoked. Such a program is free to use any region of Chip RAM that would
otherwise be used by BASIC.
If the program calls the kernel, such as for disk functions, the program must
avoid the areas of Chip RAM reserved for kernel use. It can do whatever it likes
with the rest.
\begin{center}
\begin{tabular}{m{0.14cm}m{0.06cm}m{1.45cm}m{0.21cm}m{1.4cm}m{0.1cm}m{0.1cm}m{3.3cm}m{3.3cm}l}
\cline{1-1}\cline{4-4}\cline{6-8}
\multicolumn{1}{|l|}{\rotatebox{90}{Kernel}} & \multicolumn{1}{l}{\ldots} &
\multicolumn{1}{l}{\ldots} & \multicolumn{1}{|l|}{\rotatebox{90}{DOS}} &
\multicolumn{1}{l}{\ldots} & \multicolumn{1}{|l}{\rotatebox{90}{Res.}} &
\multicolumn{1}{|l}{\rotatebox{90}{Colour }} & \multicolumn{1}{|l|}{\rotatebox{90}{ROM}} &
\multicolumn{1}{l}{\ldots} & \\
\cline{1-1}\cline{4-4}\cline{6-8}
\rotatebox{90}{\small 0.0000} & \rotatebox{90}{\small 0.1600} &
\rotatebox{90}{\small 0.2000} & \rotatebox{90}{\small 1.0000} &
\rotatebox{90}{\small 1.2000} & \rotatebox{90}{\small 1.F700} &
\rotatebox{90}{\small 1.F800} & \rotatebox{90}{\small 2.0000} &
\rotatebox{90}{\small 4.0000} & \rotatebox{90}{\small 5.FFFF} \\
\end{tabular}
\end{center}
If the program never needs to call the kernel, it can unlock and remove the
ROM entirely, and claim every byte of the 384KB Chip RAM for its own purposes.
Using a technique discussed later, if you don't need all 32KB of the MEGA65's
colour RAM, you can repurpose the ``colour RAM window'' at 1.F800 as regular
chip RAM.
\subsection{The Memory Map Contract}
The MEGA65 project adopted the ROM code from Commodore's unfinished draft for
the C65, and invested time and effort into finishing incomplete features and
fixing bugs. This project is on-going, and the MEGA65 team expects to release
new versions of the ROM in the future.
This makes writing programs with the MEGA65 ROM characteristically different
from writing programs with the Commodore 64 ROM, which is etched in silicon
and is no longer under development. It is important to write code that
conforms to documented behaviors described in these manuals---and not rely on
undocumented behaviors that might change in a future version.
It is a goal of the project to keep the Chip RAM memory map consistent with
the table above across future versions of the MEGA65 ROM. To maintain future
compatibility, programs should avoid relying on ``reserved'' regions as if
they were free space. If a new mode or feature is introduced that changes the
memory map, it will be something a program must request, such that older
programs can expect the original memory map.
Within each block of the memory map, only some addresses are guaranteed to
remain constant between ROM versions. For example, a machine code program can
invoke kernel routines by calling addresses in a ``jump table,'' a set of
fixed addresses in ROM guaranteed to remain the same in future
versions.\footnote{The jump table is not yet described in this book. The C65
used the same jump table as the C128, and so far the MEGA65 ROM has not
extended it. If a ROM revision expands this table, existing entries will
remain constant. See the MEGA65 Wiki for a list:
\url{https://mega65.atlassian.net/}}
Programs are not expected to read or modify kernel data directly, so these
locations are not documented. Some memory locations, such as the location of
screen memory, can be determined from (and adjusted by) registers. (See
\bookvref{cha:viciv}.)
The region of Chip RAM in 0.1600 -- 0.1EFF is guaranteed to be unused by the
MEGA65 kernel and BASIC. 0.1F00 -- 0.1FFF is also available if you do not use the
BASIC bitmap graphics system. This region is exposed as RAM in the default memory
configuration, so it is useful for storing machine code and data that can be
accessed from a BASIC program.
\newpage
\section{Using Memory from BASIC}
BASIC 65 includes several commands for accessing and manipulating memory, and
for calling machine code subroutines:
\begin{itemize}
\item {\bf POKE address,value} : stores a value at an address
\item {\bf WPOKE address,value} : stores a 16-bit value starting at an address
\item {\bf PEEK(address)} : reads a value at an address
\item {\bf WPEEK(address)} : reads a 16-bit value starting at an address
\item {\bf BLOAD filename [,args...]} : loads data into memory from a file
\item {\bf BSAVE filename, P start TO P end [,args...]} : saves a region of memory to a file
\item {\bf SYS address [,registers...]} : calls a machine code subroutine
stored at an address
\item {\bf EDMA ...} : transforms or copies large regions of memory quickly
using hardware accelleration
\end{itemize}
The {\bf BOOT} and {\bf WAIT} commands also operate on memory in some fashion.
See the BASIC 65 Command Reference for more information about these commands.
\subsection{BASIC Address Remapping}
You may have noticed that the addresses from the BASIC {\bf POKE} commands
described at the beginning of this chapter all refer to areas of Chip
RAM described in the memory map as either ``Free for program use,'' or ``BASIC:
program text.'' However, they seem to behave differently:
\begin{itemize}
\item \stw{POKE \$1800,127} behaves like RAM: it sets the memory at the address,
and the memory can be read with \stw{PRINT PEEK(\$1800)}.
\item \stw{POKE \$2010,63} behaves like ROM: attempting to set the memory does nothing,
and reading the memory with \stw{PRINT PEEK(\$2010)} returns the original ROM value.
\item \stw{POKE \$D020,7} behaves like an I/O register: it changes the colour of
the border immediately. While this value can be read with \stw{PRINT
PEEK(\$D020)}, it is not useful as memory because the border colour changes with the value.
\end{itemize}
\newpage
BASIC 65 commands and functions treat addresses in the range \$0000 -- \$FFFF
according to special remapping rules, also known as {\em banking}. In the
default mode, BASIC 65 remaps these addresses as follows:
\begin{center}
\begin{tabular}{|c|c|c|}
\hline
{\bf 16-bit address block} & {\bf Mapped address block} & {\bf Type} \\
\hline
\$0000 -- \$1FFF & 0.0000 -- 0.1FFF & Chip RAM \\
\hline
\$2000 -- \$3FFF & 3.2000 -- 3.3FFF & ROM \\
\hline
\$4000 -- \$5FFF & 3.4000 -- 3.5FFF & ROM \\
\hline
\$6000 -- \$7FFF & 3.6000 -- 3.7FFF & ROM \\
\hline
\$8000 -- \$9FFF & 3.8000 -- 3.9FFF & ROM \\
\hline
\$A000 -- \$BFFF & 3.A000 -- 3.BFFF & ROM \\
\hline
\$C000 -- \$CFFF & 2.C000 -- 2.CFFF & ROM \\
\hline
\$D000 -- \$DFFF & FFD.2000 -- FFD.2FFF & MEGA65 I/O\\
\hline
\$E000 -- \$FFFF & 3.E000 -- 3.FFFF & ROM \\
\hline
\end{tabular}
\end{center}
For example, accessing I/O registers via \$D000 -- \$DFFF actually accesses
the I/O registers in the upper region of the address space. These two BASIC
commands are equivalent:
\begin{screenoutput}
POKE $D020,7
POKE $FFD2020,7
\end{screenoutput}
The BASIC system itself uses Chip RAM directly in accordance with the Chip RAM
memory map. For example, BASIC stores program text starting at 0.2000 in Chip
RAM, even though the BASIC function \stw{PEEK(\$2000)} reads from 3.2000 using
the default mapping mode.
BASIC 65's remapping of addresses only applies to addresses in the range \$0000
-- \$FFFF. When you use an address \$10000 or higher with {\bf POKE} or
{\bf PEEK()}, it accesses that Chip RAM address directly. {\bf SYS} also
supports long addresses, with limitations (discussed later).
You can control BASIC address remapping using the {\bf BANK}\index{BASIC 65
Commands!BANK} command. This command accepts a bank number as an argument, in the range 0
-- 5. Subsequent uses of addresses in the range \$0000 -- \$FFFF are
interpreted as being offsets in that bank. For example, to write the value 63
to address 0.2010 in Chip RAM:
\begin{screenoutput}
BANK 0
POKE $2010,63
\end{screenoutput}
The {\bf BANK} mechanism can only be used with banks 0 -- 5. It cannot be used
to reach Attic RAM or upper I/O registers.
To re-enable BASIC address remapping, execute {\bf BANK} with the argument 128.
This does not refer to a bank in memory. Instead, it tells the {\bf BANK}
command to re-enable mapped addresses.
\begin{screenoutput}
BANK 128
POKE $D020,9
\end{screenoutput}
{\bf BANK} affects every BASIC command and function that accepts an address as
a parameter. This includes {\bf PEEK}\index{BASIC 65 Functions!PEEK},
{\bf POKE}\index{BASIC 65 Commands!POKE},
{\bf WAIT}\index{BASIC 65 Commands!WAIT},
{\bf BOOT}\index{BASIC 65 Commands!BOOT},
{\bf BSAVE}\index{BASIC 65 Commands!BSAVE}, and {\bf BLOAD}\index{BASIC 65
Commands!BLOAD}. {\bf SYS} also has special behavior with
regards to {\bf BANK}.
\subsection{BANK and SYS}
The {\bf SYS}\index{BASIC 65 Commands!SYS} command executes a machine code
subroutine at a given address. It handles the address differently from other
BASIC commands, and comes with some limitations. Because {\bf SYS} transfers
control to a custom routine, it needs to take extra precautions to preserve
the system's access to memory used to support BASIC.
With \stw{BANK 128} enabled (the default), {\bf SYS} uses the BASIC memory
mapping for addresses in \$0000 -- \$FFFF. As usual, the first 8KB block of
\$0000 -- \$1FFF maps to bank 0 RAM, and the rest map to ROM and I/O registers.
\begin{screenoutput}
BANK 128
SYS $1800 : rem Calls subroutine at 0.1800
SYS $FFD2,65 : rem Calls kernel ROM routine (with an argument)
\end{screenoutput}
With \stw{BANK 0} enabled, {\bf SYS} uses bank 0 for addresses in \$0000 --
\$BFFF only. Unlike other BASIC commands that use memory addresses, {\bf SYS}
continues to interpret \$C000 -- \$FFFF as mapped to ROM and I/O registers with
\stw{BANK 0} selected.
\begin{screenoutput}
BANK 0
SYS $1800 : rem Calls subroutine at 0.1800
SYS $7000 : rem Calls subroutine at 0.7000
SYS $FFD2,65 : rem Calls kernel ROM routine (with an argument)
\end{screenoutput}
{\bf SYS} has limited access to banks 1 -- 5. When {\bf BANK} is set to any of
those banks, {\bf SYS} uses that bank for addresses \$2000 -- \$7FFF only.
Every other address behaves like \stw{BANK 0}, including the ROM and I/O
registers at \$C000 -- \$FFFF.
\begin{screenoutput}
BANK 1
SYS $1800 : rem Calls subroutine in bank 0 at 0.1800
SYS $7000 : rem Calls subroutine in bank 1 at 1.7000
SYS $FFD2,65 : rem Calls kernel ROM routine at 3.FFD2 (with an argument)
\end{screenoutput}
{\bf SYS} can accept an address larger than \$FFFF that refers to a location in
banks 1 -- 5. However, these addresses have the same limitations as when using
{\bf BANK}. Only offsets \$2000 -- \$7FFF within the bank actually refer to the
memory of that bank, such as address \$47000 in bank 4. For other offsets, the
{\bf SYS} command behaves as if it was called using a {\bf BANK} setting.
This restriction produces the counterintuitive behavior that, for {\bf SYS}, a long address
doesn't necessarily refer to the memory at that address, and multiple addresses
may refer to the same location in memory. \stw{SYS \$11800} behaves like
\stw{BANK 1:SYS \$1800}, which calls a subroutine at address 0.1800 in bank 0.
\stw{SYS \$41800} has the same effect.
\begin{screenoutput}
SYS $11800 : rem Calls subroutine in bank 0 at 0.1800
SYS $41800 : rem Calls subroutine in bank 0 at 0.1800
SYS $51800 : rem Calls subroutine in bank 0 at 0.1800
SYS $17000 : rem Calls subroutine in bank 1 at 1.7000
SYS $47000 : rem Calls subroutine in bank 4 at 4.7000
SYS $57000 : rem Calls subroutine in bank 5 at 5.7000
SYS $1FFD2,65 : rem Calls kernel ROM routine at 3.FFD2 (with an argument)
\end{screenoutput}
The {\bf SYS} command cannot access every address. Address offsets \$C000 --
\$FFFF are hidden by ROM and I/O registers in every bank, and address offsets
\$0000 -- \$1FFF and \$8000 -- \$BFFF can only access bank 0 and are hidden in
banks 1 -- 5.
{\bf SYS} is limited to banks 0 -- 5. It cannot access Attic RAM or upper
memory addresses, even when using long addresses.
Despite these limitations, {\bf SYS} is quite powerful. It is common for a
machine code program to start with a short BASIC program, so the user can type
{\bf RUN} after loading it. This program selects \stw{BANK 0}, then calls {\bf SYS}
to start the machine code stored in memory after the end of the BASIC text.
\begin{screenoutput}
10 BANK 0:SYS $2014
\end{screenoutput}
Notice that this BASIC launcher must use \stw{BANK 0} so that BASIC interprets
\stw{SYS \$2014} to refer to the machine code that starts at address 0.2014 in
Chip RAM. In mapped memory mode (\stw{BANK 128}), \stw{SYS \$2014} would refer to the ROM
address 3.2014, which is not what is intended.
You can also use {\bf SYS} to augment a BASIC program with machine code
subroutines. A convenient location for these subroutines is in the \$1600 --
\$1FFF range of bank 0. {\bf SYS} can access this location regardless of the
current {\bf BANK} setting.
\newpage
\section{Using Memory from Machine Code}
Every 45GS02 machine code instruction that operates on memory has one or more
variants that can locate memory via {\em addressing modes}. One of these
addressing modes operates on full 28-bit addresses, reading the full address
from four consecutive bytes of memory in Little Endian order.
For speed, space efficiency, and backwards compatibility with processors
earlier in the MOS 6502 lineage, most 45GS02 addressing modes operate on 16-bit
addresses. The 45GS02 calculates the complete 28-bit address from a special
purpose register known as {\em the MAP register}. Setting this register
involves executing a sequence of special-purpose CPU instructions.
With the MEGA65, three other mechanisms affect 16-bit memory address
translation. The Commodore 64 ROM and I/O banking mechanism controlled by the
CPU registers at addresses 0.0000 and 0.0001 is supported for backwards
compatibility and access to I/O registers. The Commodore 65 has another ROM
banking mechanism using a register at address \$D030. Finally, if a cartridge
is inserted in the expansion port, additional rules expose cartridge data
lines at specific 16-bit address banks.
Programs written for the MEGA65 can rely entirely on the MAP register for
accessing memory. The C64, C65, and cartridge memory mapping mechanisms are
included in the MEGA65 primarily for compatibility purposes.
This section describes accessing memory with 45GS02 addressing modes and the
MAP register. The other mapping mechanisms are discussed briefly later in the
chapter. For complete documentation on addressing modes, see
\bookvref{sec:addressing-modes}. For a detailed explantion of the banking
mechanisms, see \bookvref{cha:45gs02}.
\subsection{The MAP Register}
The MAP register remaps 8KB (\$2000) blocks of the 16-bit address space using an {\em
offset}, an amount that's added to the 16-bit address to determine the final
address. The offset must be a multiple of 256 bytes (\$100).
For example, if the block \$2000 -- \$3FFF is offset by \$45200, then accessing
the 16-bit address \$335F will access physical address \$4855F (in bank 4).
\begin{center}
\begin{tabular}{ccccc}
\$335F & + & \$45200 & = & \$4855F \\
\vtop{\hbox{16-bit}\hbox{address}} & & offset & &
\vtop{\hbox{actual}\hbox{address}} \\
\end{tabular}
\end{center}
The 4510 CPU from the Commodore 65, on which the MEGA65's 45GS02 is based,
supports offsets up to \$FFF00. This allows any 8KB block to be remapped anywhere in
Chip RAM on a 256-byte boundary, including possible future RAM expansions up to
1MB. (The 45GS02 also has a way to set larger offsets, discussed later.)
The MAP register can store two offsets at a time, one for blocks in the range
\$0000 -- \$7FFF (``MAPLO''), and one for blocks in the range \$8000 -- \$FFFF
(``MAPHI''). It also stores a selection flag for each 8KB block that says
whether the offset should be applied to that block.
Each 8KB address block is either ``unmapped'' or uses the MAP offset for its
half (MAPLO or MAPHI). All mapped address blocks in the same half must share
the same offset.
\begin{center}
\begin{tabular}{c|c|c|c|c|l}
MAPLO: & \$6000 -- \$7FFF & \$4000 -- \$5FFF & \$2000 -- \$3FFF & \$0000 --
\$1FFF & \\
& 0 & 0 & {\bf 1} & 0 & = \$2 \\
\hline
MAPHI: & \$E000 -- \$FFFF & \$C000 -- \$DFFF & \$A000 -- \$BFFF & \$8000 --
\$9FFF & \\
& {\bf 1} & 0 & {\bf 1} & {\bf 1} & = \$B \\
\end{tabular}
\end{center}
The selection flags and the offset for MAPLO and MAPHI are encoded altogether
as four bytes, two for MAPLO and two for MAPHI. The first nibble contains the
selection flags for the four blocks of the region. Notice the order of the
flag bits: the higher bits correspond to the higher address blocks.
For example, to select just the \$2000 -- \$3FFF block, set the MAPLO selection
flag to binary ``0 0 1 0,'' which is the hexadecimal digit \$2.
The remaining three nibbles are the upper three hex digits of the
offset. To use the offset \$45200 for the selected MAPLO blocks, set the
remaining bits of MAPLO to \$452. For this example, the complete MAPLO value is
\$2452.
\begin{center}
\begin{tabular}{cc|cc}
\multicolumn{2}{c}{MAPLO} & \multicolumn{2}{c}{MAPHI} \\
X & A & Z & Y \\
\hline
\$s\textsubscript{lo} o\textsubscript{1} &
\$o\textsubscript{2} o\textsubscript{3} &
\$s\textsubscript{hi} o\textsubscript{1} &
\$o\textsubscript{2} o\textsubscript{3} \\
\hline
\$24 & \$52 & \$B3 & \$00 \\
\end{tabular}