|
ForumsSega Master System / Mark III / Game GearSG-1000 / SC-3000 / SF-7000 / OMV |
Home - Forums - Games - Scans - Maps - Cheats - Credits Music - Videos - Development - Hacks - Translations - Homebrew |
Author | Message |
---|---|
|
Accessing Z80 CPU registers using C
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. |
|
|
Posted: Sat Apr 09, 2022 9:59 am |
@sverx will help you. Keep calm. | |
|
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; } |
|
|
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. |
|
|
Posted: Sat Apr 09, 2022 3:04 pm |
Thank you both for your answers!
I should probably learn how to read basic ASM instructions... |
|
|
Posted: Sat Apr 09, 2022 3:49 pm |
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. |
|
|
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:
|
|
|
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. |
|
|
Posted: Mon Apr 11, 2022 7:35 am |
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 :) |
|
|
Posted: Mon Apr 11, 2022 8:15 am |
Ok, so its really useless, unless specific cases. | |
|
Posted: Mon Apr 11, 2022 8:35 am |
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 |
|
|
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 |
|
|
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 | |
|
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. | |
|
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. |
|
|
Posted: Sat Apr 16, 2022 7:54 pm |
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. |
|