Hmm, okay then thanks!
Though if you use macros, be careful to note which registers they modify since you won't see the "ld hl,buffer" anymore.

As for passing arguments on the stack, it's not that common but i've done it before for a text routine, whenever i came across a command to display something special (another text, the player's name, a number, etc.) i just pushed that value onto the stack. So the routine would read through the stack until it read a $0000 value Smile
Thanks for the help chickendude.

New Question: Is there a faster way to fill in empty bytes into a range of address such as $8000 to $8000F without having to do it manually?
Well are you trying to fill the bytes with all the same number? i.e. 0. If you are make a loop that loops through $8000 to $8000F with that number. Or if it is increasing numbers, do the same but have a counter and increase each time you store it.
If you want to fill it with the same byte, you can do:

Code:

;A is the byte to fill with
;B is the number of bytes to fill (if it is less than 256)
;HL is where to fill
FillLoop:
     ld (hl),a
     inc hl
     djnz FillLoop

If you want to copy something like a string of 15 bytes from one place to another, you can do something like:

Code:

     ld hl,Address
     ld de,New_Address
     ld bc,15
     ldir

If you want to fill those bytes with zeros, I like to do:

Code:

     xor a
     ld b,15
     ld hl,8000h
Loop:
     ld (hl),a
     inc hl
     djnz Loop
     ret
Hmm, wow, thanks Xeda! Actually what do the ldir and djnz commands do?
LDIR stands for something like 'LoaD Increment Repeat'. It loads the byte at (hl) into the place pointed to by (de). Then it increments HL and DE, decrements BC and repeats this process until BC = 0

DJNZ will decrement B and jump to the label, as long as B is not 0. It is like doing this (but faster and smaller):

Code:

     dec b
     jr nz,Loop
Can someone please explain this code?


Code:

  LD     HL, (stack_ptr)    ; Load stack pointer
    LD     (HL), E            ; Push the low-order byte
    INC    HL                ; Move stack pointer to next byte of available space
    LD     (HL), D            ; Push the high-order byte
    INC    HL
    LD     (stack_ptr), HL    ; Save new stack pointer


I mean i get what it does, in terms of stack, I just need an explanation of what the commands are doing, especially with how the ()s are being interacted with.

Also thanks Xeda.
As a full example:

Code:

STACKSIZE .equ 100 ; (A) bytes

; ...

    LD     HL, (stack_ptr)    ; (B) Load stack pointer
    LD     (HL), E            ; (C) Push the low-order byte
    INC    HL                 ; (D) Move stack pointer to next byte of available space
    LD     (HL), D            ; (E) Push the high-order byte
    INC    HL                 ; (F)
    LD     (stack_ptr), HL    ; (G) Save new stack pointer

; ...
stack_ptr:                    ; (H)
    .dw start_of_stack        ; (I)

start_of_stack:               ; (J)
    .fill STACKSIZE, 0x00     ; .fill size, value
                              ; same as having "size" number of "value" written in a .db statement


So, on line (A), it just defines a variable, STACKSIZE to be 100. This is only visible during assembly, and isn't actually stored on the calculator. Line (B) references another assembler-only variable "stack_ptr" which you can think of as simply being an address. The address, if you looked at that location under a memory debugger, would be a single word (because of the ".dw", "data word"), which in the case contains the address of "start_of_stack." Using the parentheses with an address between them (which "stack_ptr" is), causes the calculator to load a byte or word of memory into another register, in this case HL. After that command is executed, HL contains the value/address of "start_of_stack."

Line (C) uses indirection again, but slightly different. The parentheses load from a memory location if that's what's between them, or if it's a 2 byte register (Such as HL, DE, and a few others) it would load the memory that is contained at the address the register points to. In this case, HL points to "start_of_stack", so line (C) stores the value of register E to "start_of_stack." Don't forget that, when using "LD", the first argument is where to store, and the second is what to store. Line (D) just increments HL by one, causing it to point to "start_of_stack + 1." Lines (E) and (F) should be pretty self-explanatory.

Then on line (G) it uses indirection with an address to save the new value of HL (which points to "start_of_stack + 2" to "stack_ptr." This way, next time you run lines (B) through (G) again, it will be pointing to the next part of the stack and work as expected, so that you don't overwrite old parts of the stack.

Finally, line (H) creates a assembler variable "stack_ptr" which points to a word (I) that initially points to "start_of_stack." And line (J) creates another one and uses a Brass feature to create a large block (size STACKSIZE, or 100, bytes) of memory, all set to an initial value (0x00).

(Also, since I'm pretty sure someone will call me out on it: when using ASM, you don't necessarily "compile" code, you just "assemble" it. Assembling just changes the forms (one using mnemonics, the other using machine language) whereas compiling actually creates new assembly code from your program and doesn't give you full control over what the processor sees.)
Quote:
[22:17:19] < Link_> so, wait, if memory adddress say $FEDC = stackpointer, has the value of $FE,
[22:17:34] < Link_> after that command, HL = $00FE right?
[22:17:47] < Tari> no
[22:18:21] < Link_> then what happens when I do LD HL, (stack_pointer)?
[22:18:27] < Tari> if fedc is fe and fedd is 00, hl becomes 00fe
[22:20:43] < Link_> isn't that what I did?
[22:21:04] < Tari> you didn't say anything about fedd
[22:21:15] < Link_> oh, wait, the H regitar's value is affected by the one before it?
[22:21:28] < Tari> it's a 16-bit load
That is, it takes the two-byte value at the specified location and loads that into HL.

For example, we'll put 0x1234 in memory at 0x8000:

Code:
    ld hl,0x1234
    ld (0x8000), hl
We end up with 0x34 at 8000, and 0x12 at 8001 (seemingly reversed with the high-order byte lower in memory because the Z80 is little-endian).

With that, your ld hl,(xxxx) is equivalent to this:

Code:
    ld hl,0x8000
    ld a,(hl)
    inc hl
    ld h, (hl)
    ld l,a
Well, this is slower and clobbers A. But you get the point (I hope).

See also: 83pa28d (as pointed out by Runer in IRC as I was composing this).
In z80 assembly, anything with parentheses (except for 1 instruction, I believe) is indirection. What that means, is that the stuff in the parentheses points to a byte in RAM. For example, (hl) means "the byte that HL points to" and ($8000) means "the byte at $8000." something like add a,(hl) will take the byte pointed to by hl and add it to register a. Something like ld a,(OP1) will take the byte at OP1 and load it into a, ld (OP1),a will load the value of a to the byte at OP1.

Depending on the context, you might actually load 2 bytes. For example, you can do ld (OP1),hl and that will store it little endian (you might think of this as 'backwards'). So L→(OP1), H→(OP1+1). ld (**),hl is 3 bytes, ld (**),de, ld (**),bc, ld (**),sp are all 4 bytes.

Z80 does not have a lot of indirection support. You cannot do add a,(OP1) and you cannot use something like ld bc,(hl) You can use (hl) almost anywhere you can use a register, though.

Later, you will probably encounter index registers (IX and IY) and this stuff will be important Smile
There are also the in/out instructions that use the parentheses to communicate with the ports. in a,(1) reads a byte from port 1 (the keyboard). But i think the instruction Xeda was talking about is "jp (hl)", which is really equivalent "jp hl". Basically, if HL = $9D95, "jp (hl)" will jump to the address $9D95 instead of reading the two bytes stored at $9D95 and jumping to that address (what you'd expect it to do since it looks like it should be using indirection, but it doesn't).

Also, the stack generally works downwards, meaning it starts at $FFFF and will work it's way down towards $0000. So when you push a value onto the stack, the stack's address actually gets lowere. Popping a value off the stack will increase the address.

EDIT:
Xeda112358 wrote:
If you want to fill those bytes with zeros, I like to do:

Code:

     xor a
     ld b,15
     ld hl,8000h
Loop:
     ld (hl),a
     inc hl
     djnz Loop
     ret
There's also the age-old:

Code:
     ld hl,8000h
     ld (hl),0                ;here l = 0 so we could also use "ld (hl),l" or move the "ld bc,14" up front and then use be to load 0 into (hl). that saves a byte and some speed.
     ld de,8001h
     ld bc,14
     ldir
Many thanks to all the people who just helped with that.

Next:
In this instruction:

Code:

    JR C, label
label:
     ; ... other stuff


What is the point of the C, and possibly NC. I get that they are carry flags and stuff, and i know flags are used to indicate stuff. But i don't get why it's needed here.
With no code between the jump and the destination like that, it's useless. But when actually making decisions about whether or not to execute certain code, conditional jumps are essential.



A simple example might be to take the absolute value of a number. If it's positive, you don't want to do anything to the value. But if it's negative, you want to negate it to make it positive. In C, coding such logic might look like this:

Code:
   if (var < 0)
      var = -var;


In z80 assembly, coding the absolute value logic might look like this:

Code:
   or   a      ; Updates sign, zero, and parity flags according to the value in A
   jp   p,skip ; Skips negating A if it is positive (sign flag: p=positive, m=negative)
               ; Note that this has to be an absolute jump (jp) and not a smaller
               ;  relative jump (jr) because jr only supports the carry and zero flags
   neg         ; Negates A, as A cannot be positive here so must be negative
skip:




For more advanced decision making, simply use multiple jumps! Let's say you wanted a piece of code that brings a signed integer closer to zero by one (or do nothing if it's already zero). In C, that might look like this:

Code:
   if (var < 0)
      var++;
   else if (var > 0)
      var--;


In z80 assembly, it might look like this:

Code:
   or   a      ; Updates sign, zero, and parity flags according to the value in A
   jr   z,skip ; Skips doing anything to A if it is zero
   jp   p,pos  ; Skips the negative logic and goes to the positive logic if A is positive
   inc  a      ; Increments A, as A cannot be positive or zero here so must be negative
   jr   skip   ; Negative logic is done; skips the positive logic
pos:
   dec  a      ; Decrements A, as A must be positive here
skip:
Thanks Runer, I understand that now.

Next thing, can someone give an example of the ret and call commands, and how they're used?
For the sake of simplicity, we will look at a subroutine for multiplying HL by 12. To do it inline, we would do:

Code:

     ld b,h
     ld c,l
     add hl,hl
     add hl,bc
     add hl,hl
     add hl,hl

However, if you want to use this as a subroutine, you would put an RET at the end (for RETurn) and use the CALL instruction to call the subroutine, like this:

Code:

     bcall(_RclAns)
     bcall(_ConvOP1)
     ex de,hl
     call HL_Times_12
     push hl
     bcall(_DispHL)
     bcall(_NewLine)
     pop hl
     call HL_Times_12
     bcall(_DispHL)
     bcall(_NewLine)
     ret
HL_Times_12:
     ld b,h
     ld c,l
     add hl,hl
     add hl,bc
     add hl,hl
     add hl,hl
     ret

In practice, I never use HL_Times_12 as a subroutine. 'CALL' uses 3 bytes and an additional 17 cycles, RET uses a byte and 10 cycles. In-line, it is 6 bytes and 52 cycles, so you would need to use the call at least 3 times to get any size savings. However, calls are very useful if you need to use more complicated codes in several locations, like sprite drawing routines. Then using calls could save hundreds or even thousands of bytes (depending on the size of the code and how often it is used).

bcalls are a special type of call as well. When you use bcall(_whatever), you are actually using a special type of call instruction, rst 28h, which directly calls the code at address 0028h and has the code for the bcall subroutine. The OS then use the two bytes following it to redirect the call to another location (it is actually a pretty complicated changeover).
Thanks Xeda112358! My next two questions are how do I compare greater or lesser than in between two registers (8 bit registers) and how do i generate a random number between range of a and b?
For comparing two values, there is the CP instruction which compares the register A to another register or a constant. The valid instructions are:
CP B
CP C
CP D
CP E
CP H
CP L
CP (HL)
CP A ;redundant, but this resets the c flag, sets the z flag
cp * ;* is an 8-bit constant

If A and the given register are equal, the z flag is set, else it is reset. If the register is greater than A, then the c flag is set, else it is reset. For example:

The way I remember the c and z flag is by pretending I am using sub instead. For example, sub 10 returns the c flag set if a<10 and cp 10 returns the c flag set if a<10. Often I use sub instead of cp if I do not need to preserve a since I find it easier to remember how it works and it often leads to optimisations later.

You can actually "create" a 16-bit CP instruction with HL and BC or HL and DE (or HL and SP, but Hl is redundant and SP is rarely used this way):

Code:

     or a     ;only to reset the c flag. If the c flag is already set, you do not need this
     sbc hl,de
     add hl,de


For a random number on a range from a to b, there are a bunch of options. As well, depending on the situations, there can be tons of optimisations. If you want more methods, feel free to ask, but here are a few simple methods:

Code:

     ld e,a
     sub b
     ld d,a
     ld a,r         ;the r register is loaded with a random-ish value
     ld h,0
     ld l,a         ;now HL points somwhere in the first 256 bytes of addressed memory
     ld a,r         ;to make it possibly a little more randomish
     add a,(hl)
     ld c,a
     call C_Div_D
     add a,e
     ret
C_Div_D:
;Inputs:
;     C is the numerator
;     D is the denominator
;Outputs:
;     A is the remainder
;     B is 0
;     C is the result of C/D
;     D,E,H,L are not changed
;
     ld b,8
     xor a
       sla c
       rla
       cp d
       jr c,$+4
         inc c
         sub d
       djnz $-8
     ret

If you know for sure that you are going to be having an interval of at least 11, you can actually save on time and size by using the naive method of division:

Code:

     ld c,a
     sub b
     ld b,a
     ld a,r         ;the r register is loaded with a random-ish value
     ld h,0
     ld l,a         ;now HL points somwhere in the first 256 bytes of addressed memory
     ld a,r         ;to make it possibly a little more randomish
     add a,(hl)

     sub b
     jr nc,$-1
     add a,b

     add a,c
     ret
Runer112 wrote:
In z80 assembly, it might look like this:

Code:
   or   a      ; Updates sign, zero, and parity flags according to the value in A
   jr   z,skip ; Skips doing anything to A if it is zero
   jp   p,pos  ; Skips the negative logic and goes to the positive logic if A is positive
   inc  a      ; Increments A, as A cannot be positive or zero here so must be negative
   jr   skip   ; Negative logic is done; skips the positive logic
pos:
   dec  a      ; Decrements A, as A must be positive here
skip:
I'm pretty sure 0 is also considered positive, so you don't need to handle the z flag here Wink

Quote:
For comparing two values, there is the CP instruction which compares the register A to another register or a constant. The valid instructions are:
CP B
CP C
CP D
CP E
CP H
CP L
CP (HL)
CP A ;redundant, but this resets the c flag, sets the z flag
cp * ;* is an 8-bit constant
There's also (ix+nn) and (iy+nn) Smile

Random number routines generally involve use of the r register, as its value is constantly updated as your program runs. Really, it gets incremented every M1 cycle (where the processor fetches the opcode), you can sorta think of this as incrementing once every instruction, but some instructions have more than one M1 cycle (possibly up to 3?). This is why r is often used in random number generators, since its value is constantly changing as your program runs. However, the last (highest) bit never changes, only the lower seven bits.
Thanks for all the help guys :'D really appreciate it.

So I wrote my first full program/routine: (a numerical input routine) and was wondering how to optimize it in any possible way.


Code:
.nolist
#include "ti83plus.inc"
.list
.org $9D93
.db t2ByteTok, tAsmCmp
   b_call(_ClrLCDFull) ; Clear Screen
   ld hl, $0000 ; Random coordinates to test printing location
   call numInput ; My Input Routine
   ret
numInput:
   ld de, $0000
   push hl ; Save hl (where to put)
top:
   b_call(_GetKey); Get first key
   ld b, a ; // ------ Check within numeric range ------
   ld a, 152
   cp b
   jr c, top
   ld a, b
   cp 5
   jr z, finish ; // End input if enter pressed
   ld a, 141
   cp b
   jr nc, top ; // ------ End numeric check ------
   ld a, b
   pop hl ; // ------ Show the next number ------
   sub 94
   ld (CurRow), hl
   b_call(_PutC)
   inc h
   push hl ; // ------ End display routine ------
   sub 48
   ex de, hl
   ld d, h
   ld e, l
   add hl, hl
   add hl, hl
   add hl, de
   add hl, hl
   ld b, 0
   ld c, a
   add hl, bc
   ex de, hl
   jr top ; // Goto top
finish:
   pop hl
   ld hl, $0504
   ld (CurRow), hl
   ex de, hl
   b_call(_DispHL)
   ret
.end
.end
  
Register to Join the Conversation
Have your own thoughts to add to this or any other topic? Want to ask a question, offer a suggestion, share your own programs and projects, upload a file to the file archives, get help with calculator and computer programming, or simply chat with like-minded coders and tech and calculator enthusiasts via the site-wide AJAX SAX widget? Registration for a free Cemetech account only takes a minute.

» Go to Registration page
Page 2 of 3
» All times are UTC - 5 Hours
 
You cannot post new topics in this forum
You cannot reply to topics in this forum
You cannot edit your posts in this forum
You cannot delete your posts in this forum
You cannot vote in polls in this forum

 

Advertisement