Talking to the IPC: How do you do it from low level?

Anything QL Software or Programming Related.
User avatar
Pr0f
QL Wafer Drive
Posts: 1548
Joined: Thu Oct 12, 2017 9:54 am

Re: Talking to the IPC: How do you do it from low level?

Post by Pr0f »

I think that sometimes the trick to add data register to itself to affect a shift may be done to set flag bits. Without looking it up - not sure what the rotate instruction would do regarding flags.

BaudX4 is literally 4 times the baud rate that's been set - so it's not the output of register directly - but of a selected divider chain within the ZX8302.

I think Resetout is not represented by a value in the register either. But COMDATA and COMCTL defintely are.

The protocol is half duplex - but the IPC always expect to be reading first - and the COMCTL is what's clocking that data.

COMDATA is an input/output on the P2 port - P2.7 (top bit), COMCTL on the IPC is the /WR output from the IPC - output only - this is the logic used to read 4 bit nibbles (for a byte read - it just calls this twice :-) )
;
; get 8 bits?
;
IPC_Get_8:
mov r6,#001H
jmp L0345
; get 4 bits?
;
IPC_Get_4:
mov a,#010H
L0344:
mov r6,a
L0345:
in a,p2 read the bit
jb7 L0345
movx @r0,a - this command will pulse /WR pin low, and hence COMCTL low
in a,p2 - read the bit
movx @r0,a
rlc a
mov a,r6
rlc a
jnc L0344
ret

This reading logic in the IPC exactly matches this description:

78 1. ZX start bit (COMDATA = 0)
79 2. IPC clock (COMCTL = 0, COMTL = 1)
80 3. ZX data bit (COMDATA = 0/1)
81 4. IPC clock (COMCTL = 0, COMTL = 1)
82 5. ZX stop bit (COMDATA = 1)


stephen_usher
Super Gold Card
Posts: 524
Joined: Tue Mar 11, 2014 8:00 pm
Location: Oxford, UK.
Contact:

Re: Talking to the IPC: How do you do it from low level?

Post by stephen_usher »

Which syntax is that code? It's definitely not Motorola.


User avatar
Pr0f
QL Wafer Drive
Posts: 1548
Joined: Thu Oct 12, 2017 9:54 am

Re: Talking to the IPC: How do you do it from low level?

Post by Pr0f »

That's MCS 48 code - from the 8049 IPC.

I spent a good while dissasembling and reverse engineering the Hermes code, along with the original code - although someone has provided a really good commented version of that.


stephen_usher
Super Gold Card
Posts: 524
Joined: Tue Mar 11, 2014 8:00 pm
Location: Oxford, UK.
Contact:

Re: Talking to the IPC: How do you do it from low level?

Post by stephen_usher »

Ah, I see.


User avatar
tofro
Font of All Knowledge
Posts: 3062
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: Talking to the IPC: How do you do it from low level?

Post by tofro »

stephen_usher wrote: Mon Apr 28, 2025 9:38 am Taken a look, but assembler isn't the best descriptive code, you often can't see the wood for the trees.

This assembler syntax has confused me:

Code: Select all

        lea     pc_ipcwr,a5
        lea     pc_ipcrd-pc_ipcwr(a5),a4 ; hardware addresses
Which addresses are these? Obviously two of the registers' addresses are being loaded into a4 and a5 but which ones?
This is simply to save code space (the second instruction fits into 2 words, while lea address,a4 would need 3). a5 is loaded with pc_ipcwr, a4 with pc_ipcwr+the offset between pc_ipcrd and pc_ipcwr, so basically pc_ipcwr - + 2. Sometimes, compacting assembly code also makes it hard to read...
stephen_usher wrote: Mon Apr 28, 2025 9:38 am I'm guessing that this is moving the value in bit 7 to bit 0

Code: Select all

        move.b  (a4),d0                  ; rx bit in msb
        add.b   d0,d0                    ; ... in X
        roxl.w  #1,d1                    ; ... in bit 0
Yes. This gets the uppermost bit of pc_ipcrd into d0, shoves it into the extent flag and rotates that into bit 0 of d1.

add.w dx,dx is also a cheaper way to shift dx one to the left than lsl.b #1,dx - it doesn't help readability, though (but that's not what assembler is known for anyways, isn't it?.
stephen_usher wrote: Mon Apr 28, 2025 9:38 am
And I'm not exactly sure what this code is doing...

Code: Select all

qlhc_wloop
        move.b  #pc.ipcwr>>2,d0          ; IPC write bits
        rol.w   #1,d0
        add.b   d0,d0                    ; bit to send in bit 1
        move.b  d0,(a5)
I'm guessing that "add.b d0,d0" is a quick way of doubling rather than than using a shift left? By why not just rotate 2 rather than one in the previous instruction?
rol.w rotates bit 14 of d0 into bit 0. If you simply rotate "one more", bit 13 would also end up in d0, which is not wanted. The way its done, it moves bit 14 of d0.w into bit 1 of d0.b and guarantees bit 0 of d0.b is zero.


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
User avatar
tofro
Font of All Knowledge
Posts: 3062
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: Talking to the IPC: How do you do it from low level?

Post by tofro »

Pr0f wrote: Mon Apr 28, 2025 12:38 pm I think that sometimes the trick to add data register to itself to affect a shift may be done to set flag bits. Without looking it up - not sure what the rotate instruction would do regarding flags.
Yes, that's sometimes done. Here, it's just cheaper in terms of CPU cycles to add dx,dx rather than shifting, and also avoids a memory read of an immediate (because the instruction fits into one word).

LSL.B takes 12 cycles (including the immediate ea) and 2 words, ADD Dx,Dx takes 4 cycles and one word. So, 3 times faster and half the code space, not bad. If you could do that as simple as that with all your instructions, you'd get lightning-fast compact programs ;)


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
stephen_usher
Super Gold Card
Posts: 524
Joined: Tue Mar 11, 2014 8:00 pm
Location: Oxford, UK.
Contact:

Re: Talking to the IPC: How do you do it from low level?

Post by stephen_usher »

OK, does this look sane?

Code: Select all

;***
;
; ipc_write_bit - Write a bit to the IPC with timeout.
;
; d0.b	-	Bit to be written in bit 0.
; d1.w	-	Timeout in number of retries reading the status bit.
;		(Must be given)
;
; Returned values.
;
; d1.w	-	Zero if timeout error otherwise undefined.
;
;***

ipc_write_bit:
	movem.l	d2-d7/a0-a6,-(SP)

	lea	zx83_w_ipcwreg,a0	; Load the ZX8302 registers into a0 and a1
	lea	zx83_r_cstatus,a1

	rol.b	#1,d0			; Move the data bit to be bit 1
	or.b	#%00001100,d0		; Or it with the rest of the required register content.

	move.b	d0,(a0)			; Write it to the IPC Write register.

ipc_write_bit_loop1:			; Loop around checking the status bit (6)
	move.b	(a1),d3
	btst	#6,d3
	dbeq	d1,ipc_write_bit_loop1	; If it's not set then go around d1.w + 1 times.

	addi.w	#1,d1			; Add 1 to d1. If the loop has completed then this will be -1
					; We need it to be 0 as an error condition.

	movem.l	(SP)+,d2-d7/a0-a6
	rts

;***
;
; ipc_read_bit	- Read a bit from the IPC with timeout.
;
; Parameters
;
; d1.w	-	Timeout in number of retries reading the status register.
;		(Must be given)
; Returns
;
; d0.b	-	Bit returned in bit 0 of the byte.
; d1.w	-	Timed out if 0 otherwise undefined.
;
;***

ipc_read_bit:
	movem.l	d2-d7/a0-a6,-(SP)

	lea	zx83_r_cstatus,a0	; Load the address of the communication status register into a0

ipc_read_bit_loop1:
	move.b	(a0),d2			; Get the contents of the communications status register into d2
	btst	#6,d2			; Is the IPC acknowledge bit set?
	dbeq	d1,ipc_read_bit_loop1	; No? If d1 is > -1 go around again.

	addi.w	#1,d1			; If the timeout failed then d1 will be -1, so add 1.

	andi.b	#%1000000,d2		; Mask off all the bits we're not interested in.
	ror.b	#7,d2			; Move it to bit 0
	move.b	d2,d0			; Copy it to d0

	movem.l	(SP)+,d2-d7/a0-a6
	rts

I'm not interested in speed, merely clarity. :-)


User avatar
Pr0f
QL Wafer Drive
Posts: 1548
Joined: Thu Oct 12, 2017 9:54 am

Re: Talking to the IPC: How do you do it from low level?

Post by Pr0f »

Looking at the IPC code side - the ZX8302 has to send a 0 bit first - which is effectively dropped, and then the next bit, as either 0 or 1 is stored and rolled round into the nibble that's formed - the start of the loop to read bits coming from the ZX8302 is always waiting for that start bit of 0.

What that effectively means is you would have to write a 0 bit then when bit 6 is pulsed low to indicate that had been read - write your actual bit of 0 or 1, and wait for that bit 6 again. And that's done for all the bits you are sending - either 4 or 8.

So to Send 4 bits to the IPC, COMMDATA would go through 0 b3 1 0 b2 1 0 b1 1 0 b0 1 COMCTL would be 1 (0) 1 (0) 1 (0) 1 (0) 1 (0) 1 (0) 1 (0) 1 (0) 1 where COMCTL is pulled low by the /WE pin on the IPC either side of each valid data bit received on COMDATA - you are effectively getting an acknowlegement after each start bit or each data bit.

The system was never built for speed ;-)

Commands are always 4 bits, then depending on the command and if any follow on data is expected - more nibbles or bytes may follow. In the IPC code - each of the commands has it's own routine and handles it's own parameter collection.


User avatar
tofro
Font of All Knowledge
Posts: 3062
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: Talking to the IPC: How do you do it from low level?

Post by tofro »

stephen_usher wrote: Mon Apr 28, 2025 4:10 pm OK, does this look sane?

Code: Select all

;***
;
; ipc_write_bit - Write a bit to the IPC with timeout.
;
; d0.b	-	Bit to be written in bit 0.
; d1.w	-	Timeout in number of retries reading the status bit.
;		(Must be given)
;
; Returned values.
;
; d1.w	-	Zero if timeout error otherwise undefined.
;
;***

ipc_write_bit:
	movem.l	d2-d7/a0-a6,-(SP)

	lea	zx83_w_ipcwreg,a0	; Load the ZX8302 registers into a0 and a1
	lea	zx83_r_cstatus,a1

	rol.b	#1,d0			; Move the data bit to be bit 1
	or.b	#%00001100,d0		; Or it with the rest of the required register content.

	move.b	d0,(a0)			; Write it to the IPC Write register.

ipc_write_bit_loop1:			; Loop around checking the status bit (6)
	move.b	(a1),d3
	btst	#6,d3
	dbeq	d1,ipc_write_bit_loop1	; If it's not set then go around d1.w + 1 times.

	addi.w	#1,d1			; Add 1 to d1. If the loop has completed then this will be -1
					; We need it to be 0 as an error condition.

	movem.l	(SP)+,d2-d7/a0-a6
	rts

;***
;
; ipc_read_bit	- Read a bit from the IPC with timeout.
;
; Parameters
;
; d1.w	-	Timeout in number of retries reading the status register.
;		(Must be given)
; Returns
;
; d0.b	-	Bit returned in bit 0 of the byte.
; d1.w	-	Timed out if 0 otherwise undefined.
;
;***

ipc_read_bit:
	movem.l	d2-d7/a0-a6,-(SP)

	lea	zx83_r_cstatus,a0	; Load the address of the communication status register into a0

ipc_read_bit_loop1:
	move.b	(a0),d2			; Get the contents of the communications status register into d2
	btst	#6,d2			; Is the IPC acknowledge bit set?
	dbeq	d1,ipc_read_bit_loop1	; No? If d1 is > -1 go around again.

	addi.w	#1,d1			; If the timeout failed then d1 will be -1, so add 1.

	andi.b	#%1000000,d2		; Mask off all the bits we're not interested in.
	ror.b	#7,d2			; Move it to bit 0
	move.b	d2,d0			; Copy it to d0

	movem.l	(SP)+,d2-d7/a0-a6
	rts

I'm not interested in speed, merely clarity. :-)
That looks OK to me on first glance - obviously, you will need to play with the timeout values to get your bits across properly. Whether it makes sense to shift the read bit to position 0 all the time when you'll have to shift it yet again anyways to insert it into a nibble or byte at its proper position is questionable, though.


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
stephen_usher
Super Gold Card
Posts: 524
Joined: Tue Mar 11, 2014 8:00 pm
Location: Oxford, UK.
Contact:

Re: Talking to the IPC: How do you do it from low level?

Post by stephen_usher »

OK, I misread the docs on bit 6.

So, on the write a bit I have to do it three times per bit:

0
Bit value
1

Checking for an acknowledge each time.

OK. I can do that.

As for shifting, I'm not assuming what the calling routine is going to do with the data, that's not the routine's problem.


Post Reply