Forums

Sega Master System / Mark III / Game Gear
SG-1000 / SC-3000 / SF-7000 / OMV
Home - Forums - Games - Scans - Maps - Cheats - Credits
Music - Videos - Development - Hacks - Translations - Homebrew

View topic - Accessing Z80 CPU registers using C

Reply to topic
Author Message
  • Joined: 12 Aug 2021
  • Posts: 73
Reply with quote
Accessing Z80 CPU registers using C
Post Posted: Sat Apr 09, 2022 8:11 am
Last edited by armixer24 on Sat Apr 09, 2022 10:46 am; edited 1 time in total
Hi,

I am currently trying to "fix" the RNG of my homebrew, and I need to add more randomness to it.

It seems that an easy way to add randomness is to read the R register of the Z80 CPU, like suggested here: https://www.smspower.org/Development/RandomNumberGenerator

However, I am having a hard time finding how to do that using C. I use DevKitSMS (and by extension, SDCC) for my homebrew, and I can't seem to find a method to access the Z80 CPU registers in the documentation.

Has anyone done this before ?


Note: I am aware of the other methods (like using user inputs to shuffle the RNG), and I am already using a frame-based incrementer, but I would at least like the solution of using the R register to be implemented.
  View user's profile Send private message
  • Joined: 23 Jan 2010
  • Posts: 417
Reply with quote
Post Posted: Sat Apr 09, 2022 9:59 am
@sverx will help you. Keep calm.
  View user's profile Send private message
  • Joined: 04 Jul 2010
  • Posts: 539
  • Location: Angers, France
Reply with quote
Post Posted: Sat Apr 09, 2022 12:32 pm
you must write it in ASM, like this :


void my_function(){
__asm
   ld a, r
   ld (#_my_var), a
__endasm;
}

"my_var" is declared as a global variable in ram
(unsigned char my_var // outside any function)

you can also read it 2 times, with some cycles between each read.

void my_function(){
__asm
   push bc    
   ld a, r
   push ix
   ld b, a
   pop ix
   ld a, r
   add a, b
   ld (#_my_var), a
   pop bc
__endasm;
}
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Sat Apr 09, 2022 1:01 pm
sure you need asm for that - but you can use a function like this one (untested but should work [with SDCC 4.1.0]):

unsigned char read_R(void) __naked {
  __asm
  ld a,r        ; read R into A
  ld l,a        ; put value into L to return it to caller as unsigned char
  ret
  __endasm;
}


which will return the value of R register to your caller.
(note that you should get only values 0 to 127 IIRC)

edit: with SDCC 4.2.0 this should instead be:

unsigned char read_R(void) __naked {
  __asm
  ld a,r        ; read R into A to return it to caller as unsigned char
  ret
  __endasm;
}

as the new calling convention requires.
  View user's profile Send private message Visit poster's website
  • Joined: 12 Aug 2021
  • Posts: 73
Reply with quote
Post Posted: Sat Apr 09, 2022 3:04 pm
Thank you both for your answers!

I should probably learn how to read basic ASM instructions...
  View user's profile Send private message
  • Joined: 16 May 2002
  • Posts: 1355
  • Location: italy
Reply with quote
Post Posted: Sat Apr 09, 2022 3:49 pm
sverx wrote
note that you should get only values 0 to 127 IIRC
True, but since there are only 7 possible tetrominos (numbered from 0 to 6, I guess), you only need three bits, and if you get a 7, you can simply roll the RNG again.
  View user's profile Send private message Visit poster's website
  • Joined: 05 Dec 2019
  • Posts: 56
  • Location: USA
Reply with quote
Post Posted: Sat Apr 09, 2022 6:05 pm
I'm biased by my experience on platforms without a counterpart to register r, but if I were making a block game, I'd do these:

  1. Use a linear congruential generator with the factor 0x01010101. I've seen "x = x * 0x01010101 + 0x31415927;" and "x = x * 0x01010101 + 0xB3B3B3B3;" in the standard library of various versions of cc65. This RNG can be implemented very efficiently on both 6502 and Z80, though Compiler Explorer shows that SDCC doesn't emit the most efficient code for it.
  2. Use the 2 most significant bits of the result as an index into the first 4 of the 7 least recently dealt pieces. This also builds in robust repeat avoidance.
  View user's profile Send private message Visit poster's website
  • Joined: 04 Jul 2010
  • Posts: 539
  • Location: Angers, France
Reply with quote
Post Posted: Mon Apr 11, 2022 7:11 am
@sverx,
what's the real benefit of using __naked ?
AFAIK its only removing the RET statement of the function, no ?
For special cases like a jump elsewhere or a reti/retn, I understand, but for the vast majority of functions, it seems useless.
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Mon Apr 11, 2022 7:35 am
ichigobankai wrote
what's the real benefit of using __naked ?


it's mostly just to make sure that whatever you write in the function body, SDCC never adds any preamble or trailing code. I like all my pure asm functions __naked, even if it means I have to write 'ret' myself :)
  View user's profile Send private message Visit poster's website
  • Joined: 04 Jul 2010
  • Posts: 539
  • Location: Angers, France
Reply with quote
Post Posted: Mon Apr 11, 2022 8:15 am
Ok, so its really useless, unless specific cases.
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Mon Apr 11, 2022 8:35 am
ichigobankai wrote
Ok, so its really useless, unless specific cases.


I prefer the better safe than sorry approach, I don't want to check the generated code each time I compile - but yeah usually it's not a big problem to leave that out
  View user's profile Send private message Visit poster's website
  • Joined: 25 Jul 2007
  • Posts: 716
  • Location: Melbourne, Australia
Reply with quote
Post Posted: Mon Apr 11, 2022 10:48 am
more accurately, it stops it from saving the registers before entering the function so without it you get code like below:

_My_Function::
   push   af
   push   bc
   push   de
   push   hl
   push   it
...

   pop   iy
   pop   hl
   pop   de
   pop   bc
   pop   af
   retn
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Mon Apr 11, 2022 10:58 am
this happens only when a function is declared as __interrupt, no function explicitly preserves register contents otherwise
  View user's profile Send private message Visit poster's website
  • Joined: 09 Aug 2021
  • Posts: 106
Reply with quote
Post Posted: Mon Apr 11, 2022 2:48 pm
only NMI ISR more or less look like that. INT handlers have different entry and exit code (depending on the function attributes). normal functions do not care about saving of caller registers.
  View user's profile Send private message
  • Joined: 12 Dec 2021
  • Posts: 43
  • Location: Melbourne, Australia
Reply with quote
Post Posted: Fri Apr 15, 2022 2:16 am
If it helps, I have two random examples that I've been playing with in https://github.com/mikehdt/sms-demo/tree/main/src/helpers

The first, fast_rand.c is quick if fewer cycles is important, but the randomness is not great - you'll spot a lot of repeating-ish patterns if you use it in quick succession. When I was testing it with the pixel fire effect (elsewhere in that project, I'm using tiles as "pixels"), it became a little predictable and periodic, so I didn't end up using it there.

The second, ps_rand.c is just a slightly wrapped version of the Phantasy Star routine as found here on SMSpower (basically I just added a bit that returns the result back from the assembly to the function in C, so it can be used). As you can see just from the code, it's got a lot more going on, but the result is also a lot better with more entropy.

Note that this only works with SDCC 4.1.0, for the time being. Although I'm nowhere near as knowledgeable as others here on the particulars of this stuff, happy to help if I'm able.
  View user's profile Send private message
  • Joined: 05 Sep 2013
  • Posts: 3763
  • Location: Stockholm, Sweden
Reply with quote
Post Posted: Sat Apr 16, 2022 7:54 pm
darkowl wrote
Note that this only works with SDCC 4.1.0, for the time being. Although I'm nowhere near as knowledgeable as others here on the particulars of this stuff, happy to help if I'm able.


they should both works with SDCC 4.1.0 and SDCC 4.2.0 as in the previous calling convention the char return value has to be stored in the L register prior to return and in the current calling convention it has to be stored in A register - and both your functions do copy A into L (so A=L) just before returning so they're fine.
  View user's profile Send private message Visit poster's website
Reply to topic



Back to the top of this page

Back to SMS Power!