User Tools

Site Tools


ugbasic:user:expansion
Translations of this page:


ugBASIC User Manual

EXPANSION MEMORY

In this section we will introduce the concept of “expansion memory”, as well as all the features that ugBASIC makes available to increase the memory actually available on 8-bit computers. Particular attention should be paid to the fact that ugBASIC is not intended to provide a way to overcome the physical limitation of CPUs: for most of them, the maximum addressable memory is 64KB, and often even less. However, some of them provide hardware solutions, such as “paged memory” or DMA access, which can help mitigate the limitations.

Type of memories

In ugBASIC there are two types of memory:

  • resident memory, which is the one that can be addressed directly by the CPU through its instructions;
  • expanded memory, which is not directly addressable by the CPU.

When you program using the available instructions, define a variable, load an image, resize an array, and so on, you are using resident memory. The advantage of resident memory is that it is always available. So you don't have to worry about setting it up to use it. Conversely, it is usually in very limited quantities (and, depending on the target, decreases as the program grows).

Conversely, when you tell ugBASIC that a resource is meant to be loaded into expanded memory, the matter changes. It is still possible to use that resource but, obviously, ugBASIC will perform a series of “maneuvers” to make the data available when you use it.

How it works

Let's take the simplest case, even if perhaps a little useless. We have an image and we want to store it in expanded memory. And when we need it, we want to be able to draw it on the screen.

So, what happens? That ugBASIC will store the image on expanded memory (with the LOAD IMAGE command). When it's time to draw it on the screen, just use the PUT IMAGE command. Behind the scenes, ugBASIC will take the image from the expanded memory and copy it, reading it from the bank, into the resident memory. At this point it will use the data copied in the resident memory to draw on the screen.

What happens if you ask to draw the same image again? Clearly, since the graph data has already been copied from extended memory, it will not be copied again. The previous one will be used. And again, and again, whenever you draw that image.

Shared resident memory

Clearly, you may have more than one image to draw. For example, suppose you have two and want to draw them. The mechanism will be similar to the previous one.

The key difference in this case is that you are drawing two different images. In this case, ugBASIC will not be able to refrain from copying the graphic image from the expanded memory again, even during the second call.

Note that a certain amount of resident memory is reserved to implement this mechanism. How much memory? The one needed to host the largest image.

Multiple shared resident memory

Can you avoid copying the graphic resource every time? Can you have multiple resident memory areas? The answer is yes! In that case, the behavior changes again because ugBASIC will be able to avoid copying a given resource that has already been copied previously.

However, attention must be paid to the fact that, for each area dedicated to copying, a certain amount of resident memory will be lost. Also, as we explained in the previous section, the required resident memory area will be sized with the largest of the blocks that can be copied from the expanded memory. It goes without saying, therefore, that it is advisable to combine blocks of memory of similar length in a single sharing space.

Clearly, what is the best strategy between having memory resident zones and shared zones is the full responsibility of the developer.

Loading resource on expansion

To load a resource into an expanded memory bank, and then to use a single shared space, the BANKED keyword must be used. This command tells ugBASIC that that resource should be stored in expanded memory, and retrieved from there if needed.

Example:

    player := LOAD IMAGE("player.png") BANKED

This command will load the player image into expanded memory and associate it with the player variable. Notice how we are using the direct assignment syntax (:=). This is because, otherwise, the resource will be placed in expanded memory but what will be assigned to the player variable will be a copy. So we will take up both expanded and extended memory, to no avail.

Of course, we can use the variable as we did before, without any particular restrictions:

    PUT IMAGE player AT 40, 40

Loading resource on separate shared memory

To load multiple resource into an expanded memory bank, and then to use different shared space for each, the BANKED keyword must be followed by the number of the shared memory. You can have up to 256 different resident memories.

Example:

    CONST playerShared = 1
    CONST alienShared = 2
    
    playerIdle := LOAD IMAGE("player_idle.png") BANKED ( playerShared )
    playerRun := LOAD IMAGE("player_run.png") BANKED ( playerShared )
    alienIdle := LOAD IMAGE("alien_idle.png") BANKED ( alienShared )
    alienRun := LOAD IMAGE("player_run.png") BANKED ( alienShared )

Of course, we can use the various variables as we did before, without any particular restrictions:

    PUT IMAGE playerIdle AT 40, 40
    PUT IMAGE alienIdle AT 0, 0

Other available primitives

In addition to managing resources, you can directly access and manage the expanded memory. In this case, however, you will be fully responsible for its use and therefore you will have to be careful to avoid creating problems in the automatic management. Also, not all primitives are available or work on all targets, unlike resource management.

To know which resources are available as expanded memory, it is necessary to use the constant BANK COUNT. This contains the number of banks (ie the number of expanded memory areas) that can be used. Each of these areas usually starts from a certain address (BANK ADDRESS (…)) and it extends for a certain number of bytes (BANK SIZE (…)).

Normally there is always only one bank selected, and its number is returned by the BANK() function.

The following example returns the map of all the available memory banks, and indicates the selected one with an asterisk.

  FOR i=0 TO BANK COUNT
      IF i = BANK() THEN
          PRINT "*";
      ELSE
          PRINT " ";
      ENDIF
      PRINT BANK ADDRESS(i);" size = "; BANK SIZE(i)
  NEXT
  
  PRINT "* = actual bank selected"

To copy from a memory bank to the resident memory you can use the BANK READ command, with the following syntax:

  BANK READ [number] FROM [exp address] TO [address] SIZE [size]

Where:

  • [number] is the number of the bank (from 0 to BANK ACCOUNT-1);
  • [exp address] is the source address (zero means start of expansion memory);
  • [address] is the target address;
  • [size] is the size of the memory to copy.

On the other and, to copy from resident memory to a memory bank, you can use the BANK WRITE command, with the following syntax:

  BANK WRITE [number] FROM [address] TO [exp address] SIZE [size]

Where:

  • [number] is the number of the bank (from 0 to BANK ACCOUNT-1);
  • [address] is the source address;
  • [exp address] is the target address (zero means start of expansion memory);
  • [size] is the size of the memory to copy.

If desired, it is possible to copy variables in the resident memory. To this end, you can use the VARPTR() function, which allows you to obtain the address (in the resident memory) of the variable.