6502.org Forum  Projects  Code  Documents  Tools  Forum
It is currently Sun Nov 10, 2024 7:37 pm

All times are UTC




Post new topic Reply to topic  [ 30 posts ]  Go to page Previous  1, 2
Author Message
PostPosted: Mon Jan 07, 2019 5:52 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10976
Location: England
I think the trick in the land of 6502 might be this: that the action of increment or decrement sets the flags, all in one go. In a high level language, you can't do that, and so you have to work harder to be safe.

(Edit: by way of illustration, if you had a larger buffer and needed to adjust a two-byte count, you could easily get in trouble between the main line code and the interrupt service routine.)


Top
 Profile  
Reply with quote  
PostPosted: Mon Jan 07, 2019 8:19 pm 
Offline

Joined: Wed Jul 18, 2018 12:12 pm
Posts: 96
Here is what I just whipped up, not tested yet :) I have a separate project set up for testing my buffer code so I'll copy this over there and run some tests.

Code:
;*******************************************************************************
; *OutBufRead, read next byte from outBuf, value returned in A,
; X=0 returned buffer empty (fail), X=1 success
;
;*******************************************************************************
OutBufRead2                     ; 33 cycles
        ldx #$00                ; (2) preload fail flag
        ldy outBufTail          ; (3) is there data? tail != head
        cpy outBufHead          ; (3) if not @done
        beq @done               ; (3) if equal then buffer empty
        ldx outBufTail          ; (3) get out tail pointer again
        lda outBuf, x           ; (6) read byte at tail pointer
        iny                     ; (2) inc the tail pointer
        sty outBufTail          ; (3) save back inc'd tail pointer
        ldx #$01                ; (2) set success flag
@done   rts                     ; (6)


Code:
;*******************************************************************************
; *OutBufSave read next byte from outBuf, value to save passed in A,
; X=0 returned if buffer full (fail), caller can branch on state of X
;
;*******************************************************************************
OutBufSave3                     ; 33 cycles
        ldx #$00                ; (2) preload fail return code
        ldy outBufHead          ; (3) get current value of head pointer
        iny                     ; (2) increment it one
        cpy outBufTail          ; (3) compare to tail pointer
        beq @done               ; (3) if new value same as tail then already full
        ldx outBufHead          ; (3) get current pointer again
        sta outBuf,x            ; (6) store value       
        sty outBufHead          ; (3) save incremented head pointer
        ldx #$01                ; (2) X=1 means success
@done   rts                     ; (6)


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 08, 2019 3:52 am 
Offline
User avatar

Joined: Tue Mar 05, 2013 4:31 am
Posts: 1385
BigEd wrote:
I think the trick in the land of 6502 might be this: that the action of increment or decrement sets the flags, all in one go. In a high level language, you can't do that, and so you have to work harder to be safe.

(Edit: by way of illustration, if you had a larger buffer and needed to adjust a two-byte count, you could easily get in trouble between the main line code and the interrupt service routine.)


A good point... using basic 8-bit registers and pointers with the 6502 is quite simple (noting the single threaded nature). Updating buffer pointers which are 16-bit could become a problem as the low order byte might get zeroed and then the ISR kicks in before the high order byte gets incremented. In such cases, it would make sense to mask interrupts during these type of pointer or counter updates. Granted, that doesn't work with a NMI.

However, I think the NMI shouldn't be used for general operation for interrupt-driven events. Then again, Atari's Tempest game used a 6502 running at 1.8MHz where the NMI was driven from a timer and clocked the entire game play. This particular application made some sense as the graphics were vector and required a consistent and reliable redraw of the screen (from the vector display list) to make it work.

_________________
Regards, KM
https://github.com/floobydust


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 08, 2019 6:32 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8480
Location: Midwestern USA
BigEd wrote:
(Edit: by way of illustration, if you had a larger buffer and needed to adjust a two-byte count, you could easily get in trouble between the main line code and the interrupt service routine.)

65C816 to the rescue! :D

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 08, 2019 6:50 am 
Offline
User avatar

Joined: Fri Aug 30, 2002 1:09 am
Posts: 8539
Location: Southern California
I use a timed interrupt, even NMI, to increment a pair, or a set, of timer bytes in RAM. To stay out of trouble, copy them into another multi-byte variable, rather than using them in place. Copy the high byte first, then the low byte, then compare the high bytes. If they don't match, loop back to repeat the process. So you have something like:

Code:
    BEGIN
        COPY  TIMER+1, TO, FOOBAR+1
        COPY  TIMER,   TO, FOOBAR
        LDA   TIMER+1
        CMP   FOOBAR+1
    UNTIL_EQ


Quote:
65C816 to the rescue! :D

It definitely makes a lot of things easier.

Quote:
If 'tail==head' the buffer is empty, if tail+1=head the buffer is full. I'm always checking before writing to the buffer so I should not be able to overrun the buffer. If you are only using 255 bytes then you would not have a circular buffer, you need to be able to wrap all the way around (I think).

As mentioned in my earlier link, I do use the entire 256 bytes, but it's never quite full, because I tell the sending end to hit the brakes when there are still several bytes left to go, so that varying numbers of bytes wanting to "have the last word" don't overrun the buffer.

_________________
http://WilsonMinesCo.com/ lots of 6502 resources
The "second front page" is http://wilsonminesco.com/links.html .
What's an additional VIA among friends, anyhow?


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 08, 2019 9:43 am 
Offline
User avatar

Joined: Tue Mar 02, 2004 8:55 am
Posts: 996
Location: Berkshire, UK
Jeff_Birt wrote:
Jeff_Birt wrote:
BigEd wrote:
Having got rid of any shared variable, you're on firmer ground. But, with 8 bit pointers and a 256 byte buffer, you have a potential problem of not knowing whether the buffer is full or empty. As noted upthread, one approach is to use a 256 byte buffer but with a capacity of 255 - if the head and tail would become equal when writing, that's a fail. This way, when head and tail are equal, the buffer is unambiguously empty.


If 'tail==head' the buffer is empty, if tail+1=head the buffer is full. I'm always checking before writing to the buffer so I should not be able to overrun the buffer. If you are only using 255 bytes then you would not have a circular buffer, you need to be able to wrap all the way around (I think).


I think I misunderstood about using only 255 bytes, you were meaning that you could leave a one byte gap between the head and tail? So, you are still wrapping all the way around but just not saving anything into the gap byte?

You can use all the bytes in the buffer area but you can't update the insertion point index (tail) if doing so would make head == tail when adding data.

As data for transmission is normally added to the FIFO in user (e.g. non-interrupt) code you can wait in a loop if saving the updated tail would make it the equal head.
Code:
ldx tail
sta buffer,x
inx
cpx BUFSIZE
if eq
 ldx #0
endif
repeat
 cpx head
until ne
stx tail

When receiving data you are normally adding it to the queue in an interrupt routine so if the buffer is full you will have to discard it.
Code:
lda ACIABUF
ldx tail
sta buffer,x
inx
cpx BUFSIZE
if eq
 ldx #0
endif
cpx head
if ne
 stx tail
endif

Personally I don't keep counts. You can work it out from the head and tail indexes if you need it. (utterly untested code follows)
Code:
sec
lda tail
sbc head
if cc
 adc #BUFSIZE
endif

_________________
Andrew Jacobs
6502 & PIC Stuff - http://www.obelisk.me.uk/
Cross-Platform 6502/65C02/65816 Macro Assembler - http://www.obelisk.me.uk/dev65/
Open Source Projects - https://github.com/andrew-jacobs


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 08, 2019 7:38 pm 
Offline

Joined: Wed Jul 18, 2018 12:12 pm
Posts: 96
BigEd wrote:
Yes, that last idea, I think - you need to allocate 256 bytes, to make life easy, but you can only store 255 useful bytes in there.

As you say, it's not too hard to get the full/empty thing right, but it's a common trap to fall into so I thought it worth mentioning. It's also the sort of thing which might not be found in testing.


As it turns out using up only 255 bytes, i.e. having the padding byte makes the code cleaner. Getting rid of the flag byte and your suggestion of using only 255 bytes has drastically reduced the size of my code (and made it faster).

Jeff_Birt


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 08, 2019 7:57 pm 
Offline
User avatar

Joined: Thu Dec 11, 2008 1:28 pm
Posts: 10976
Location: England
Oh, that's good!


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 08, 2019 9:14 pm 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
Quote:
…so if the buffer is full you will have to discard it.

Preferably, before you get to that point you should send a flow-control signal back to the sender so that it doesn't cause overflow.


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 08, 2019 11:12 pm 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8480
Location: Midwestern USA
Chromatix wrote:
Quote:
…so if the buffer is full you will have to discard it.

Preferably, before you get to that point you should send a flow-control signal back to the sender so that it doesn't cause overflow.

That's not the purpose of flow-control. Flow-control is used to pace the data link, in which the goal is to prevent an overrun of the receiver. Flow-control doesn't decide what to do with a datum once it has been received.

Once a datum has been received it is up to the local operating system to determine that datum's fate. In a typical TIA-232 implementation, for example, the local OS will read the datum from the UART and then attempt to store it into a receiver circular queue (RxQ) pending subsequent processing. If RxQ has room the newly-arrived datum is stored and the queue pointer is updated. If RxQ is full the datum is discarded and the queue pointer is not disturbed. Either way, the datum is read before its disposition is determined, a sequence that is especially necessary when working with a UART that lacks a receiver FIFO.

Below is a flow chart that illustrates how the interrupt service routine (ISR) in my POC series' firmware handles incoming data flow. In the chart, RHR means "receiver holding register," which is another name for the 8- or 16-deep FIFO that is part of the UART's hardware. RxRDY is a flag bit in the UART's status register that when set, indicates at least one datum is present in the RHR.

Attachment:
File comment: UART Receiver IRQ Processing
rxd_isr_flow_small.gif
rxd_isr_flow_small.gif [ 81.63 KiB | Viewed 1058 times ]

In the NXP 26** and 28** UART series, the hardware can be configured so when the RHR is full the UART will automatically deassert the request-to-send (RTS) signal. When RTS is deasserted, CTS at the remote transmitter is likewise deasserted, and the transmitter is supposed to immediately stop sending. Most TIA-232 hardware will immediately stop, but there's always the odd exception.

An alternative to automatic flow-control is to "manually" control RTS and deassert it when RxQ's fill level exceeds a certain threshold, and assert RTS once RxQ's fill level has dropped below another threshold—these thresholds are referred to as "high water" and "low water" marks, respectively. A possible high water mark would be queue_size-16 and a corresponding low water mark would be queue_size-64. Basically, the procedure is to store the most recent datum, update the RxQ "put" index and then subtract the RxQ "get" index from the "put" index, discarding the carry. The result is then compared to the high water mark and if the same or higher RTS is deasserted, (hopefully) stopping the remote transmitter. As the foreground process gets datums from the queue it would also calculate the fill level and once it drops to the low water mark, assert RTS so the remote transmitter can resume sending. This procedure would obviously demand more processing time, but would also protect from a receiver overrun due to the transmitter being slow to respond to its CTS input.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 09, 2019 12:40 am 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
Quote:
That's not the purpose of flow-control. Flow-control is used to pace the data link, in which the goal is to prevent an overrun of the receiver.

…but that's precisely what I said. If the queue is getting full, then the receiver is being overrun by the sender.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 09, 2019 3:38 am 
Offline

Joined: Sat Jun 04, 2016 10:22 pm
Posts: 483
Location: Australia
I think he means an overrun of the receiver hardware, rather than the receiving system as a whole.


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 09, 2019 5:32 am 
Offline
User avatar

Joined: Thu May 28, 2009 9:46 pm
Posts: 8480
Location: Midwestern USA
Chromatix wrote:
Quote:
That's not the purpose of flow-control. Flow-control is used to pace the data link, in which the goal is to prevent an overrun of the receiver.
…but that's precisely what I said. If the queue is getting full, then the receiver is being overrun by the sender.
DerTrueForce wrote:
I think he means an overrun of the receiver hardware, rather than the receiving system as a whole.

DerTrueForce is correct—I'm referring to a hardware overrun.

Queue control is a software function that is the responsibility of the operating system (OS). Flow control is a hardware function unrelated to the disposition of data once read from the hardware. A full RxQ (receiver circular queue) is the result of failing to read from the queue in a timely fashion. Getting data from RxQ is almost always a foreground process, which implies that the "blame" for a full RxQ lies with the software running the machine.

A receiver overrun occurs when the hardware at the receiving end of the data link doesn't have room for an incoming datum. The usual result is the newly-arrived datum "steps" on the datum that is currently buffered in the receiver, resulting in corruption. In a receiver that has a FIFO, an overrun will not happen until the FIFO is full, a feature that makes the system less sensitive to how quickly receiver interrupts can be processed.

In my previous post, I mention that flow control with an NXP UART (and other similar UARTs) can be made fully automatic, which is how I operate my POC units. In such a case, the OS has no knowledge of what flow control is doing, and in fact doesn't need to know anything about it.

_________________
x86?  We ain't got no x86.  We don't NEED no stinking x86!


Top
 Profile  
Reply with quote  
PostPosted: Wed Jan 09, 2019 7:08 am 
Offline

Joined: Mon May 21, 2018 8:09 pm
Posts: 1462
In the case where you have reliable hardware flow control, software flow control for the purpose of avoiding queue overflow is easy: just don't read from the receiver FIFO if there's no space to put the datum. The hardware will then settle with a full FIFO and a negated flow-control signal, with no data lost. The ISR should disable the relevant interrupt source when that occurs, and the queue consumer should re-enable the interrupt source when there is space - to avoid overhead in the fast path, perhaps when the queue turns up empty.


Top
 Profile  
Reply with quote  
PostPosted: Tue Jan 15, 2019 9:17 pm 
Offline

Joined: Wed Jul 18, 2018 12:12 pm
Posts: 96
Just to complete this thread. My circular buffer works! The final code is below. After a bit of head scratching I was able to save a few more cycles from what I posted previously. If it were not for an odd bug with VICE I could have gotten rid of the last LDX #$01 and just branched on the state of the carry flag. It works fine when running the code but not with VICE in debug mode.

At this time I can calculate 9-10 bytes of output (for 2 axis only) for every 1 byte output at a 1kHz rate. Adding in a Z axis will require slowing down the output rate somewhat. Still pretty good for a 1mHz 6502 though :)

Ah yes, I implemented them as macros since they are only used at once place each in the code this saves about 6 cycles for the jsr/ret.

Code:
;*******************************************************************************
; *OutBufWrite read next byte from outBuf, value to save passed in A,
; X=0, Z=1 returned if buffer full (fail), caller can branch on state of X/Z
; could have used C flag but VICE seems to have a bug in debug mode
; Only uses 255 out of 256 bytes, 1 byte padding between head and tail
;*******************************************************************************
defm    OutBufWrite
        ldx outBufHead          ; (3) get current value of head pointer
        inx                     ; (2) increment it one, does not affect C
        cpx outBufTail          ; (3) compare to tail pointer
        beq wdone               ; (3) tail=head is full, if Z=0 then C=1
        dex                     ; (2) need X back to where it was
        sta outBuf,x            ; (6) store value, index to X
        inx                     ; (2) increment it one, again!
        stx outBufHead          ; (3) save incremented head pointer
        ldx #$01                ; (2) so BNE/BEQ can be used
wdone                           ; (26 if a macro)
        endm


Code:
;*******************************************************************************
; *OutBufRead, read next byte from outBuf, value returned in A,
; X=0, Z=1 returned buffer empty (fail), X=1 success
; could have used C flag but VICE seems to have a bug in debug mode
;*******************************************************************************
defm    OutBufRead
        ldx outBufTail          ; (3) grap the tail pointer
        cpx outBufHead          ; (3) comapre to head pointer
        beq rdone               ; (3) Z=0 empty, if Z=0 then C=1
        lda outBuf,x            ; (6) laod value, X indexed
        inx                     ; (2) inc tail pointer, does not affect C
        stx outBufTail          ; (3) save inremented tail pointer
        ldx #$01                ; (2) so BNE/BEQ can be used
rdone                           ; (22 if a macro)
        endm


Top
 Profile  
Reply with quote  
Display posts from previous:  Sort by  
Post new topic Reply to topic  [ 30 posts ]  Go to page Previous  1, 2

All times are UTC


Who is online

Users browsing this forum: No registered users and 13 guests


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 post attachments in this forum

Search for:
Jump to: