varios question about z80 code generation

  • Robert Ramey

    Robert Ramey - 2006-10-26

    a) For a given function x, the generator emits 3 symbols x_start, x_end and x .

    What is the purpose of x_start and x_end.  To me they just clutter up the *.asm file.

    b) The "banked" keyword in the z80 code generation "almost works".  I'm would like to fix it.  Is the "banked" keyword used in other processors or is it broken everywhere.

    c) the current #pragma bank bank# sets the bank for all the code and const data in the module. I can live with this.  But it is implemented such that it puts ALL code in the banked memory - not just that which is "banked".  This would permit standard calls to be generated in other modules which use the banked address but don't do the bank switching.  I would think that code would be placed in banked or unbanked memory in accordance with the banked keyword.  Unfortunately, this is a little harder to implment.  It also leaves an a confusion regarding const data memory allocations in the banked segment.

    Any other information on this subject would be welcomed !!!

    Robert Ramey

    • Maarten Brock

      Maarten Brock - 2006-10-26


      a) I think x_start and x_end can be used for inspecting where you're code ended up.

      b) At least mcs51 can use the keyword banked, I'm not sure about the others. What does 'almost works' mean and what is 'broken'?

      c) I think all functions in a module with pragma bank must be banked unless you do not intend to call them from another bank (e.g. static functions).

      For the mcs51 I implemented this differently: pragma banked is not supported, the keyword banked is. And the bank number cannot be supplied. Instead you can use pragma codeseg or command line option --codeseg to set the name of the code segment. To set the name of the const data segment use pragma constseg or option --constseg. When linking you can use -Wl,bMYSEG=0x0BAAAA to put segment MYSEG in bank 0x0B starting at address 0xAAAA.

    • Robert Ramey

      Robert Ramey - 2006-10-26

      a) I think x_start and x_end can be used for inspecting where you're code ended up.

      Since _x is in the map and is always equal so x_start it seems redundant.  It seems its not used for anything else.

      b) At least mcs51 can use the keyword banked, I'm not sure about the others.

      OK - so it seems that there isn't a common requirement as to the "banked" functionality.  I take that to mean that if I were to make it work in a reasonable way, no one would object.

      b) What does 'almost works' mean and what is 'broken'?

      i) The current setup emits the following code for a banked call

      call banked_call
      .dw  x ; x is function address
      .dw  0 ; PENDING insert number of memory bank here

      clearly, someone started this and didn't finish it.

      ii) The code for banked_call in included in crt0.s for the gameboy, it stores a return address on the stack (address of banked_call_exit) changes the bank and calls the target.  Unfortunately, the displacement of the arguments on the stack is  now different for a banked call than for an unbanked call so any arguments passed won't be correct.  That is, the current implementation will work only for functions with no arguments.

      iii) The current code changes the code segment globally for all functions anc const data in the module.  This might be OK. But if there is a function that is not marked banked, this function cannot be called from outside the module - that is it would have to be static by necessity.

      So what I would like to do is:

      i) fix code generation to include the bank - easy
      ii) take the code for banked_call out of crt0.s and make it an optional module.
      iii) fix the code for banked_call so that it maintains the integretity of the arguments on the call stack.
      iv) alter code generation so that only functions marked "banked" are relocated to the banked segment.  Other functions would be located to the normal code segment.
      2) probably changing #praga bank # so that its not global to the module but can be reset farther down in the source.

      This is what I think is necessary to make banking useful for the gameboy color.

      I have also made some other small changes in the gbdk - which is where banked_call implementation now resides.  Assuming I can get all this to work according to taste, I would like to upload this to the site as well along with some notes using SDCC for programming gameboy cartridges.

      I'm curious if this has any application to the Z80 generally.  The method of bank switching is particular to the gameboy and probably not generally applicable.  So I would think the best is to leave in the banking stuff for the Z80.  If someone tries to use it without linking in the gbdk - he'll get an undefined global for banked_call - which will be his queue to re-implement for his particular environment.

      I'm also curious if I'm the last person on earth that has an interest in this.  I'm still using this because its an old project being made new. 

      Robert Ramey

    • Maarten Brock

      Maarten Brock - 2006-10-26

      A reasonable way is OK, a compatible way is better.

      i) My recommendation would be to let the linker fill in the bank.
      call banked_call
      .dw x
      .db x>>16

      ii) Moving banked_call outside crt0.s is fine. I'd put it in the library. But I don't see it in crt0.s that comes with SDCC. I'd recommend to save the bank on stack, change the bank, call the function, and restore the bank on return. The displacement of arguments on stack is compensated for by using 'banked_overhead' in gbz80_port (src/z80/main.c). Change it if the value is incorrect. See also src/SDCCmem.c allocParms().

      iii) For mcs51 all constant data is placed in a separate segment CONST (or name provided by user). It's safer to put constant data in the common bank because you can have pointers to it.

      iv) There is value in nonbanked nonstatic functions in a bank, because you can call them from other functions in other modules in the same bank without bankswitching overhead.

      Please try to find a solution that can work for z80 too with a proper banked_call implemented by the user.

      If you have it working you can upload a patch in the tracker system.

      Happy coding,

    • Robert Ramey

      Robert Ramey - 2006-10-26


      i) Note that this won't work because all banked code starts at x04000 regardless of the bank its in.  So I've addressed this by including making:

      call bank_call
      .dw  _x
      .dw  b_x; fixed up by the linker?

      working with the idea that that banked functions will emit:

         .global b_x
         .area _CODE_2; bank #2
          b_x = 2 captured from #pragma bank 2

      My understanding of the linker suggests that this will fixup
      the word in the banked_call

      ii)"displacement of arguments on stack is compensated for by using 'banked_overhead'" - Ahhh very good - that will make things much simpler.  I'm presuming that other versions use that to good effect - that is that's been tested and known to work.  I ask this be I don't see which processor currently supports "banked"

      "Please try to find a solution that can work for z80 too with a proper banked_call implemented by the user."

      OK - I notice that there is ISGB sprinkled around the code in the z80 directory.  I can look at this.

      Robert Ramey

    • Maarten Brock

      Maarten Brock - 2006-10-27

      This makes me wonder how you tell the linker where to put the banked segments. It's not in the linker script SDCC generates (.lnk). For mcs51 I would use the option -Wl,bBANK1=0x018000 where the 1 indicates the bank and 0x8000 is the start of the banked memory. The linker will just see this as a 32 bit address and thus it can fixup (x>>16). The global b_x is not needed.

      Yes, I've tested the banked_overhead displacement but I doubt it has seen very much usage by others. As you have noticed this is not documented yet properly. So there could still be bugs.

    • Robert Ramey

      Robert Ramey - 2006-10-27

      "This makes me wonder how you tell the linker where to put the banked segments."

      Hmmm - I haven't looked at this because the banked segments have always been placed in the correct spot.  That is bank 2 appears at address 0x8000 in the ROM image but has code that originates at 0x4000.

      Since this always worked as I expected I never looked into it.  And this without any special command line switches at link time. What didn't work was the inserted code

      call banked_call
      .dw  address
      .dw  bank_number

      bank_number was always 0 regardless of the bank in which the target was found.

      On the other hand, maybe if I included the correct command line switch at link time - the bank number would be properly generated - I'll look into it.  But I doubt it as the generated assembler code looks like

      call banked_call
      .dw  address
      .dw  0; PENDING fixup of bank number

      Of course now I am curious how the linker knows to put .area CODE_2 into the ROM image at address 0x8000.

      Actually, it looks like the changes are much smaller than I thought.  It looks like some effort was invested and was either lost or not quite finished.  I think with just a couple small tweaks - AND a clear explanation on how to use it will address the issues.

      To summarize - the main points to remember about using banking will be:

      #pragma bank <bank#>

      sets the memory bank for the whole module being compiled - exactly the same as a compile time switch.  (This switch is processed in the sdcc code - I don't know if its documented).
      Hence there should be only at most one such pragma in the program.

      Only functions marked "banked" are called with bank switching.  So, when the #pragma banked is used, all functions not marked "banked" should be declared static so that they are only called from the correct bank.

      Robert Ramey

    • Maarten Brock

      Maarten Brock - 2006-10-27

      I see, the linker output uses the address space of the eeprom, not the banked mcu address space. I'm curious how it works too.

      > Only functions marked "banked" are called with bank switching.


      > So, when the #pragma banked is used, all functions not marked "banked"
      > should be declared static so that they are only called from the correct bank.

      I disagree. When you have two or more modules that use bank 2, it is allowed to call functions from one module to the other without bankswitching. If they were static that is impossible. The reverse is true however: Every static function can only be called from within the same bank and therefor never needs bankswitching.


    • Robert Ramey

      Robert Ramey - 2006-10-28

      "Of course now I am curious how the linker knows to put .area CODE_2 into the ROM image at address 0x4000."

      It turns out that this is done by the GAMEBOY specific code in gbz80link.

      Also the number of banks in the final eprom image is specified in the gbz80link command line.

      So if one want's to enable bank switching for the Z80, there is a little bit more to specify.

      a) Bank switching function (z80 lib and/or user program)
      b) address where banked code starts (z80 link)
      c) size of code banks   (z80 link)

      and the question arises as to how this would be tested for the z80 in the absence of standardized bank switching hardware.

      I think I'm OK with my current GAMEBOY programming setup - still pending rebuilding my "real" project - but I'm hopeful this will get me past the 32K "wall".  But I'm not soooo sure that generalizing this to z80 is so easy.

      Another thing I would like to know is if it would be interesting to upload somewhere my "tweaked" gameboy development kit.  The changes are minor but it could include an example of a simple gameboy project.

      Thanks for all your help.

      Robert Ramey

    • Thomas Tensi

      Thomas Tensi - 2006-11-03

      Hello Robert and Maarten,

      I'm currently completely redesigning the SDCC linker such that it can
      handle multiple targets more easily and also supports bankswitching with
      trampoline functions as discussed in this old thread:

      As you can see it takes a long time for me to implement that, but I hope
      to spend enough time in the next months to complete this implementation.

      I am still interested in the GBZ80 platform and will try to participate
      in the discussion with my knowledge about the internals of the SDCC
      implementation for it.

           Best regards


    • Robert Ramey

      Robert Ramey - 2006-11-03

      I've been working at this and have gotten bank switching to work to my satisfaction.  Difficulties I had related to bank switching during interrupts.  My current thinking is the following:

      a) I'm using the bank switching code found in the gbdk without any changes to the code generation of the compiler.

      b) I had to augment the bank switching code on the gbdk library in order to correctly keep track of and restore the correct interrupt enable state.

      I believe the following changes are necessary and or desirable

      c) for the z80 crt0.s - implement a for each interrupt vector code to jump to a specific place e.g. jp isr_4.  The user would specify isr_4 in his code with the key word "interrupt".  Same for the non-maskable interrupt.  The library would contain default implementations of these functions which just return.

      d) the divide subroutine in the runtime library uses a static variable.  This raises havoc with interrupt driven code.

      e) The "__critical" functionality should be eliminated from the compiler for these two platforms.  Besides getting confused with the "critical" key word in the function declaration, it requires compiler support.  A much simpler method is to implement functions disable_interrupts and enable_interrupts in the runtime library - with different versions each for the gameboy and z80.

      f) More complete documenation on this subject.

      g) The only changes I need in the assembler and linker were to suppress code which hardcoded in some address.  So I don't think any work is necessary in the linker to support this.  The only think that is necessary is for each user to implement a bank_switch function in accordance with his particular hardware.

      That's really all that's necessary to make the z80/gb work very well.  This is not a big change and would help alot.

      Robert Ramey

    • Robert Ramey

      Robert Ramey - 2006-11-06

      Thinking about this a little more, Maybe its best to make the changes less and reduce them to:

      c) - interrupt table for z80
      d) - fix static variable in divide library
      e) - not mentioned before - patch to compiler to support banking - done and tested here.

      Other changes are not really required.  __critical and interrupt are OK for the z80 but won't give good results with the gbdk.  However, I'll make a note which describes what a gbdk user needs to know.  If someone had nothing else to do he could "unify" things better but I think this is good enough for now.

      I'll submit some patches in a couple of days.

      Robert Ramey

    • Thomas Tensi

      Thomas Tensi - 2006-11-07

      Hi Robert,

      first of all thanks for your commitment to the (GB)Z80
      platform and your thorough analysis of the problems with
      current code banking support!

      I am not rewriting the linker solely for banking support but
      firstly to modularize it and separate target-independent stuff
      from target-dependent.  We currently have several linker
      sources in the repository which is problematic for
      maintenance.  Any modification or improvement has to be done
      in several slightly different sources.  Also supporting new
      platforms is easier with a unified linker source.

      As I understand you, your intention is to support banking on
      the (GB)Z80 platform via the conventional compilation
      arguments ("CODE_SEG xxx").  This is great, but as discussed
      in the bankswitching thread mentioned above, this makes you
      have to recompile banked modules whenever this assignment

      This is bad for libraries which must be recompiled for each
      system built.  I would like to do the assignment at linking
      time.  That requires that the linker reads some
      configuration information and add some trampoline code, but
      this is not too complicated.

      So secondly I shall add this modifications to the unified
      linker which allows other platforms to used them if

      We'll see how I manage to get through.

            Best regards


    • Maarten Brock

      Maarten Brock - 2006-11-07


      I hope you use the linker sources from SubVersion as I've changed and moved them recently as a first step towards unification.

      The mcs51 way already lets you do the assignment at linking time through the use of named code segments. But currently all library functions are declared non-banked and reside in the common CSEG.

      And I do hope you will try to run the regression tests on the modified linker.

      I look forward to the patches of the both of you.
      Good luck.


    • Robert Ramey

      Robert Ramey - 2006-11-11

      "As I understand you, your intention is to support banking on
      the (GB)Z80 platform via the conventional compilation
      arguments ("CODE_SEG xxx"). This is great, but as discussed
      in the bankswitching thread mentioned above, this makes you
      have to recompile banked modules whenever this assignment

      My intention is to be able to run a bigger program with the minimum investmement of effort.  After some investigation, I concluded that banking support for gb/z80 was "Almost Done". The compiler generated the code to specify the bank number for banked calls and invoke "call banked" in the appropriate spot.  the #pragma was already in there to asign the current module being compiled to the desired bank.  Seeing this, and given my experience with the torturous process of debugging code for the gameboy, I did the following:

      a) tweaked the compiler code to emit a symbol b_<function name> for banked functions when called.

      b) tweaked the compiler code emit ".globl b_<functionname> <bank # from pragma.

      c) tweaked the banked_call section of the gb crt0.s so that
      i) disable_interrupts and enable_interrupts properly nest.
      ii) banked_call disables/enables interrupts so that banked functions
      can be called from interrupts.
      iii) eliminated the HOME segment.  things work with CODE=0x200 now - just like the z80 compiler.

      c) I made a change to the linker and compiler to eliminate CODE= and DATA= - but I don't think these are now necessary.  So I believe that no changes to the linker are necessary.

      I've gotten this working and applied it to my "real project" which is pretty large and complex. It seems to work - but has turned out to be excruciatingly slow.  This is due to the banking system.  I'm addressing this by making some minor adjustments to source code organization to diminish inter-bank calls.  After I check this out some more I'll upload my changes - which are not very much after all.

      So I'm sort of off the hook with my original problem.  But it sounds like proposing something more ambitious.  I'll comment on the features of the current system and how I think a better system would work.

      The current system permits modules to be compiled separately. Suppose function A calls function B.  Recompile of function A and B are necessary when B is changed from banked to nonbanked or vice-versa.  If the bank number of B is changed - only function B need be recompiled.  This would permit a lot of functions like B to be placed in a library and changes in banks of B wouldn't require recompilation of A.  This sounds to me what you want to achieve.

      However, I dislike the following about the current system:

      a) bank switching code cannot be optimized out automatically.  I would like to have a call from one module in bank 1 to another module in bank1 be optimized to a simple call automatically.

      b) currently all code in one module is assigned to the same bank - which is inconvenient to me.

      c) In order to minimize inter-bank calls I have to go through the program by hand - a very tedious process.

      If one had nothing else to do - I would have preferred that a banking system syntax which would work like this:

      void a(...) banked(1); // compile to bank 1
      const banked(1) int x; // const data in bank 1
      extern void a(...) banked(1); // switch to bank 1 if necessary when calling.
      If the bank number varies between the declaration and implemention - a compile time error would be invoked.

      This would

      a) permit the compiler to optimize out superfluous banking changes.
      b) permit code within the same module ( with access to static variables) to be assigned to different banks.
      c) by using macros for bank numbers, one could shuffle the code organization easily.
      d) require recompilation of the whole project - no big problem for me as my whole project only takes a few seconds to rebuild from scratch.
      e) require no changes to the linker or assembler

      Just one man's opinion.

      Robert Ramey


Log in to post a comment.

Get latest updates about Open Source Projects, Conferences and News.

Sign up for the SourceForge newsletter:

No, thanks