Unlocking ASIC/RMR2 access on Amstrad GX/Plus models

Hello there!

I stumbled upon Programming:Unlocking ASIC. Neat stuff there in the optimized routines! :heart_eyes:

And here are my 2 bits on the matter.

As mentioned in the linked old-forum thread, it is the caller’s responsibility
to handle the interrupts, so all DI/EI be gone! -2 bytes

As a reminder, the complete RMR2 unlock sequence to be written to I/O port #BCxx is:
NZB, #00,#FF,#77...#8A, STATE (#CD), EOK

NZB: Any value but zero.
STATE: #CD will unlock (anything else will lock).
EOK: It must be sent to snap the ASIC out of it’s unlocking sequence and get the CRTC register selection I/O back. It can be any value.

The classic optimized routine does not leverage some properties of the unlock sequence. Such as:

  • We put the sequence data right after the routine ends, with a RET, i.e. a &C9 opcode, not zero, hmm… smell like an NZB to me! : -1 byte
  • Why storing the EOK value in the sequence data? We do not care what value it is, we will send whatever we find: -1 byte

This gives a slightly tighter version:

				; Send a 17 bytes sequence to I/O port #BCxx to unlock RMR2
				; register access. KISS approach using a table of raw values.
				;
				; Size: 28 bytes
				; Time: 178 µs
				;
				; Output:
				;  BC=&BC00
				;  Trashes HL and Flags 
rmr2_unlock:
				ld bc,&BC00+17
				ld hl,_rmr2_data_key
_rmr2_send_key		inc b
					outi
					dec c
					jr nz,_rmr2_send_key
_rmr2_data_key	ret		; This opcode is also used as SYNC/NZB value (#C9)
				db 0, &FF,&77,&B3,&51,&A8,&D4,&62,&39,&9C,&46,&2B,&15,&8A
_rmr2_data_lock	db 205	; 205 to Unlock, anything else to lock

				; The routine will over-read one byte at this location
				; to retrieve an EOK value, necessary for proper unlocking.

And now at 28 bytes long, this KISS-friendly routine makes the optimized ones look like… Oh well, that’s not important. (we still love you!)

But the LFSR re-implementation routine by @Urusergi caused my nose to tingle. There are soooo many ways and bit-trickeries to implement these tiny LFSR thing, there might be some shorter variations out there…

…a few days, cries and nose bleeds later: well, not that many it seems!
Only found one:

				; Software re-implementation of the LFSR logic described in
				; the Amstrad Patent (GB2243701A). It generates a 15 bytes
				; sequence as verified by the ASIC to unlock RMR2 access.
				;
				; Designed by Urusergi
				; Modified by Grim (shaving a single byte, pfew!)
				;
				; Size: 27 bytes
				; Time: 389 µs
				;
				; Output:
				;  BC=&BCFF
				;  Trashes HL and AF
rmr2_unlock:
				ld bc,&BCFF
				out (c),c ; SYNC/NZB
				out (c),0 ; SYNC/NUL
				ld a,c
_rmr2_send_key
					out (c),a
					ld h,a
					add hl,hl
					rra
					add hl,hl
					ld l,a
					xor h
					and &F7
					xor l
					add a,h
					and &88
					xor l
					cp c
					jr nz,_rmr2_send_key
				ret

I tried to ditch the (cute) cp c exit test and use some flag instead after the last xor l, but failed so far. The cp c lives on! :slight_smile:

2 Likes

Premature optimization is the root of all evil.

You need to unlock the ASIC precisely once. There is no sane reason to ever lock it again.

Saving a handful of bytes is extremely unlikely to accomplish anything except maybe in some weird 4K demo type scenario, in which case the downsides of the Plus machines not having existing ROM routines you can call for basic functionality like this will usually wipe out any advantage over just writing standard CPC code.

I see a potential use case for locking the ASIC after resetting certain features. This scenario most likely involves CPC democode aiming to be compatible with CRTC Type 3, while intentionally abusing GateArray RMR-bit 5 for obscure optimization reasons, which could cause significant issues if the ASIC remains unlocked.

So you’re right, definitely no sane reason :slight_smile:

1 Like

If you’re code is aware of the ASIC, then I think you’d struggle to come up with a realistic case where you need to send a value that will upset RMR2. And even less likely that doing such a thing would outweigh the overhead of unlocking the ASIC again the next time you needed to change any of the Plus features.

It’s just one of those scenarios that doesn’t really make a whole lot of sense.

Democoding doesn’t have to make sense, as long as it runs fast. Especially when beam-racing, you’re usually trying to cram and bit-pack as much data as possible into Z80 registers (they are fast) and reusing and abusing them like crazy. And then your GA command is not just that, but also acts as a shadow I/O address for another device, controls bits on PPI Port C and whatnot. Every bits count.

And only your CPC init-democode needs to be ASIC-aware (to make sure the hardware is solid before you take over), but anything after that can safely consider a CPC-GateArray where its command bit-5 has no effect.

So it’s not about unlocking/locking the ASIC everytime you need to poke around in it, you do it only once. It’s just about properly setting up the hardware before your main CPC (not Plus) code runs. There is no overhead to outweigh here.

Congratulations Grimmy! :clap:

I would have bet my d*** that my routine couldn’t be improved. Thank goodness I didn’t do it :sweat_smile:

It made me laugh :joy:. It reminds me of those days :face_with_spiral_eyes:

I see that @Longshot also created his routine. Wow! I knew that using a double word would make it possible to complete the sequence Madram’s routine almost managed, but I imagined it would take up considerably more space. Bravo!

Thanks @Urusergi!

Indeed, using the double word allows keeping it down to 28 bytes while remaining fully dynamic. The main advantage is that it can also relock the ASIC on the fly via HL (with no RAM patching) and saves CPU cycles (349 µs vs 389 µs).

But that 27/28-byte LFSR approach is definitely a clever squeeze for a pure one-shot unlock!

1 Like