• Difficult questions about HP48 hardware

    From Cyan@21:1/5 to All on Tue Jan 1 20:35:25 2019
    Hello

    I'm looking for information on the way the HP48 handle its screen,
    and though I have found some by collecting documentation on the topic,
    it's still relatively fuzzy.

    Do you know if a doc could help ?

    Here is below a copy/paste from the question list (in markdown format) :
    


    Questions on HP48
    ================

    Screen
    ---------

    Questions related to grey level support

    #### What's the exact refresh rate of HP48 screen ?
    All docs seem to mention 64 Hz,
    but that sounds too "round".
    Could it be 64.1 , or 63.9 ?
    Where does the refresh rate is controlled ?
    Is refresh rate tied to cpu speed ?

    #### What is the exact refresh policy ?
    Are there 64 refreshes per second, meaning the whole screen is refreshed in a single scan ?
    Or is there one refresh per line, meaning there are actually 64x64=4096 refresh events ?

    #### _What_ is pushing pixel data onto screen ?
    Is it the cpu directly ? or another component accessing RAM ?

    If cpu : I assume there is some kind of "interrupt", and the cpu capability is "reserved" during the refresh. But in such case, it's supposed to send data to some kind of bus, with some kind of address ? What would be this address ?

    If dedicated component : I assume the RAM access is likely reserved by the component during the refresh, blocking the CPU. But could the CPU still work using its own registers during that period ?

    #### Pixel latency
    My own tests let me believe that interleaving up to 3 screens is fine.
    I've read people saying that the cycle can be more lengthy, but I wasn't convinced by the results.
    Is there any study on pixel fading latency ?

    #### Contrast control
    It's relatively straightforward to change the contrast of the screen, through a shared value in RAM.
    Is it a good idea to play with this setting to generate more nuanced grey levels ? Or is there a crippling latency involved ?


    Interrupts
    --------------
    All docs I could find provide a list of interrupts like below which does not include anything regarding screen :

    - Character received by the UART
    - Character placed in the UART transmit holding register
    - Keyboard button down/repeat
    - Timer expiry
    - Low battery condition
    - IR emission/receipt
    - ON-key press
    - VLBI (very low battery interrupt)

    Now, I understand that it's possible to know which line has been (or will be) drawn thanks to RAM value stored at `LINECOUNT` address, but checking that requires some "busy loop", which is bad for battery consumption, and does not allow doing something
    else in between.

    Is there a way to catch the `$LINECOUNT == 0` event as an interrupt ? or something equivalent ?
    I suspect this topic has been solved by arcade games using 3-grey shade graphics.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Hic quondam locutus sum@21:1/5 to Cyan on Sun Mar 31 13:49:19 2019
    On Tue, 1 Jan 2019 20:35:25 -0800 (PST), Cyan <yann.collet.73@gmail.com> wrote:

    Hello

    I'm looking for information on the way the HP48 handle its screen,
    and though I have found some by collecting documentation on the topic,
    it's still relatively fuzzy.

    Do you know if a doc could help ?

    Here is below a copy/paste from the question list (in markdown format) :
    ?


    Questions on HP48
    ================

    Screen
    ---------

    Questions related to grey level support

    #### What's the exact refresh rate of HP48 screen ?
    All docs seem to mention 64 Hz,
    but that sounds too "round".
    Could it be 64.1 , or 63.9 ?
    Where does the refresh rate is controlled ?
    Is refresh rate tied to cpu speed ?

    The timers and the screen refresh logic are all directly controlled by a crystal
    oscillator in the HP48 series AFAIK. Small variations in this oscillator's speed
    may cause the refresh rate to fluctuate. There is an internal counter, running off of the clock signal from crystal oscillator, that refreshes the screen at a constant rate after a certain amount of clock cycles have elapsed. So, assuming that the oscillator is perfectly stable, the nominal refresh rate is 64Hz. From the display controller's POV, the refresh rate is always 64Hz.


    #### What is the exact refresh policy ?
    Are there 64 refreshes per second, meaning the whole screen is refreshed in a single scan ?
    Or is there one refresh per line, meaning there are actually 64x64=4096 refresh events ?

    The display controller drives each row at a time. The CPU is halted for a certain amount of time for each row of display memory, so that the display controller can access RAM and drive a row. So, since there are 64 rows, you could, I guess, say that there are "64x64=4096" "refresh events" per second. But, these "refresh events" only start every 1/64th of a second and don't last very long*.

    *( "Long" is a relative term here as the display controller does contribute to a
    measurable slowdown on the HP48. The slowdown is around 13% or so IIRC. )


    #### _What_ is pushing pixel data onto screen ?
    Is it the cpu directly ? or another component accessing RAM ?

    It's the display controller which is external to the Saturn CPU on the Yorke/Clarke SoCs.



    If cpu : I assume there is some kind of "interrupt", and the cpu capability is "reserved" during the refresh. But in such case, it's supposed to send data to some kind of bus, with some kind of address ? What would be this address ?

    The HALT line to the CPU is driven high when the display controller needs to access memory to drive a row onto the display.


    If dedicated component : I assume the RAM access is likely reserved by the component during the refresh, blocking the CPU.

    Correct. The CPU is halted for a small amount of time when the display controller is reading data from memory.

    But could the CPU still work using its own registers during that period ?

    In-between row refreshes, you have about 220 microseconds in which the CPU can resume whatever it was doing before it was halted.


    #### Pixel latency
    My own tests let me believe that interleaving up to 3 screens is fine.
    I've read people saying that the cycle can be more lengthy, but I wasn't convinced by the results.
    Is there any study on pixel fading latency ?

    I might have to get back to you on this question. The LCD pixel response time for the original HP48 ( non-"black" screen ) G/GXs is quite high, but I don't have any specific numbers ATM.


    #### Contrast control
    It's relatively straightforward to change the contrast of the screen, through a shared value in RAM.
    Is it a good idea to play with this setting to generate more nuanced grey levels ? Or is there a crippling latency involved ?

    In my experience temporal dithering is the best method of creating greyscale images, especially when the temporal component is combined with randomness so that one doesn't experience flicker.



    Interrupts
    --------------
    All docs I could find provide a list of interrupts like below which does not include anything regarding screen :

    - Character received by the UART
    - Character placed in the UART transmit holding register
    - Keyboard button down/repeat
    - Timer expiry
    - Low battery condition
    - IR emission/receipt
    - ON-key press
    - VLBI (very low battery interrupt)

    Now, I understand that it's possible to know which line has been (or will be) drawn thanks to RAM value stored at `LINECOUNT` address, but checking that requires some "busy loop", which is bad for battery consumption, and does not allow doing something
    else in between.

    Is there a way to catch the `$LINECOUNT == 0` event as an interrupt ? or something equivalent ?
    I suspect this topic has been solved by arcade games using 3-grey shade graphics.

    Well, there is no interrupt generated by the display controller when LINECOUNT is equal to 0. One method to mostly get around this is to configure a user interrupt service routine which sets TIMER2 to generate an interrupt *just before* LINECOUNT is equal to zero. After the interrupt is generated, the CPU would have to perform some busy waiting, but it wouldn't last long.

    --------------------------------------------------------------------------------

    Send e-mails to "NOhp48 Sat Palephnaught Adot Morg"

    ( Remove the capitalized letters spelling "NOSPAM" )

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Cyan@21:1/5 to All on Tue Apr 2 14:18:16 2019
    Thanks for the detailed answer !
    It's way more accurate than anything I could find so far, and helps a lot !

    I do have a few follow-up questions :

    There is an internal counter, running
    off of the clock signal from crystal oscillator, that refreshes the screen at a
    constant rate after a certain amount of clock cycles have elapsed. So, assuming
    that the oscillator is perfectly stable, the nominal refresh rate is 64Hz. From
    the display controller's POV, the refresh rate is always 64Hz.

    I read that as : the CPU and the Display are both driven by the same crystal oscillator, and therefore, ignoring potential oscillator variations, the amount of CPU cycles between 2 refresh is simply hard-wired. It should be constant, no matter what (
    except obviously when display is turned off).
    Is this number of cycles known / documented somewhere ?


    The display controller drives each row at a time. The CPU is halted for a certain amount of time for each row of display memory, so that the display controller can access RAM and drive a row. So, since there are 64 rows, you could, I guess, say that there are "64x64=4096" "refresh events" per second. But, these "refresh events" only start every 1/64th of a second and don't last
    very long*.

    Thanks to your explanation on the display controller, I now get that the CPU gets HALTed when a line is refreshed. It lasts "a number of cycles", and I'm not sure how much. Then it can resume.

    In my initial message, I feared that the CPU was actually being interrupted, meaning that it has to stop doing its stuff, save some essential registers and context information, proceed on driving the refresh operation, then restore initial state, and
    resume. All this context switching would have costed "something", and it would have made a difference wether it was happening 64 or 4096 times per second.

    The difference between 4096 short HALT of 64 longer HALT is moot if they equal the same number of HALTed cycles. Actually, 4096 would be better, as it would spread the load more evenly.


    there is no interrupt generated by the display controller when LINECOUNT
    is equal to 0. One method to mostly get around this is to configure a user interrupt service routine which sets TIMER2 to generate an interrupt *just before* LINECOUNT is equal to zero. After the interrupt is generated, the CPU would have to perform some busy waiting, but it wouldn't last long.

    Well, thanks. I've been trying exactly that strategy.
    But the results were disappointing, to say the least.

    Even though I'm sure I selected TIMER2 value with a _very_ conservative wait amount, basically guaranteeing that the CPU would be busy-waiting, it was still resulting in large tearing.

    An important thing though : since my real HP48SX stopped working recently (I suspect a RAM corruption problem though can't be sure), I've been relying on emulator (emu48) to continue development. emu48 is said to be cycle-exact, so I had high hopes that
    it would be representative of a real hardware. But I guess that at that level of details, the emulation may reach its limits.

    It could be that the level of tearing observed was in fact emulator-related, I could not confirm it on my real calculator.

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Hic quondam locutus sum@21:1/5 to Cyan on Wed Apr 3 13:33:06 2019
    On Tue, 2 Apr 2019 14:18:16 -0700 (PDT), Cyan <yann.collet.73@gmail.com> wrote:

    Thanks for the detailed answer !
    It's way more accurate than anything I could find so far, and helps a lot !

    I do have a few follow-up questions :

    There is an internal counter, running
    off of the clock signal from crystal oscillator, that refreshes the screen at a
    constant rate after a certain amount of clock cycles have elapsed. So, assuming
    that the oscillator is perfectly stable, the nominal refresh rate is 64Hz. From
    the display controller's POV, the refresh rate is always 64Hz.

    I read that as : the CPU and the Display are both driven by the same crystal oscillator,

    Well, there is an important distinction : The display and the timers are *directly* driven by the crystal oscillator whereas the Saturn CPU's clock signal is derived from a PLL which generates a high frequency multiple of the crystal oscillator's frequency.

    and therefore, ignoring potential oscillator variations, the amount of CPU cycles between 2 refresh is simply hard-wired.

    Well, because of the Saturn CPU's PLL's small fluctuations ( there may be some "spread spectrum" functionality, but I'm not sure ) with respect to the crystal oscillator, the number of clock cycles is not going to be exactly the same for each refresh. But, you're correct in that the refresh frequency is "hard-wired".


    It should be constant, no matter what (except obviously when display is turned off).
    Is this number of cycles known / documented somewhere ?

    It's not known because it's not exactly the same in-between each display refresh
    due to the display and timer parts of the Clarke / Yorke SoC being driven directly by the crystal oscillator and the CPU clock, while being derived from the same oscillator, is generated by a PLL. So, one cannot calculate an *exact* cycle time, but only an average. Depending on one's HP48's clock frequency, the number of cycles in 1/64th of a second can vary as the CPU clock varies between as low as 3.69 MHz to 3.9 MHz on an HP48-G/GX ( Personally, I've never seen a clock speed that is 4MHz or greater ).



    The display controller drives each row at a time. The CPU is halted for a
    certain amount of time for each row of display memory, so that the display >> controller can access RAM and drive a row. So, since there are 64 rows, you >> could, I guess, say that there are "64x64=4096" "refresh events" per second. >> But, these "refresh events" only start every 1/64th of a second and don't last
    very long*.

    Thanks to your explanation on the display controller, I now get that the CPU gets HALTed when a line is refreshed. It lasts "a number of cycles", and I'm not sure how much. Then it can resume.


    The halt lasts 22 to 23 microseconds.

    In my initial message, I feared that the CPU was actually being interrupted, meaning that it has to stop doing its stuff, save some essential registers and context information, proceed on driving the refresh operation, then restore initial state, and
    resume. All this context switching would have costed "something", and it would have made a difference wether it was happening 64 or 4096 times per second.

    The difference between 4096 short HALT of 64 longer HALT is moot if they equal the same number of HALTed cycles. Actually, 4096 would be better, as it would spread the load more evenly.

    Conservatively, the number of halted cycles, for each 1/64th of a second refresh
    period, should be around 3.9e6 Hz * (22.5e-6 s * 64) = 5616 cycles, for a G/GX at least. This is about a 10% slowdown.



    there is no interrupt generated by the display controller when LINECOUNT
    is equal to 0. One method to mostly get around this is to configure a user >> interrupt service routine which sets TIMER2 to generate an interrupt *just >> before* LINECOUNT is equal to zero. After the interrupt is generated, the CPU
    would have to perform some busy waiting, but it wouldn't last long.

    Well, thanks. I've been trying exactly that strategy.
    But the results were disappointing, to say the least.

    Even though I'm sure I selected TIMER2 value with a _very_ conservative wait amount, basically guaranteeing that the CPU would be busy-waiting, it was still resulting in large tearing.


    That's interesting, as I've used the same method, but without any tearing, and so have many greyscale action games on the 48. Note that I always use double-buffering where the new display data is written to display memory after the refresh cycle ends.


    An important thing though : since my real HP48SX stopped working recently (I suspect a RAM corruption problem though can't be sure), I've been relying on emulator (emu48) to continue development. emu48 is said to be cycle-exact, so I had high hopes that
    it would be representative of a real hardware. But I guess that at that level of details, the emulation may reach its limits.

    It could be that the level of tearing observed was in fact emulator-related, I could not confirm it on my real calculator.

    My guess is that it's emulator related.

    Regards,

    Hic quondam locutus sum

    --------------------------------------------------------------------------------

    Send e-mails to "NOhp48 Sat Palephnaught Adot Morg"

    ( Remove the capitalized letters spelling "NOSPAM" )

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)
  • From Cyan@21:1/5 to All on Wed Apr 3 14:17:08 2019
    Thanks for the detailed answers.
    It's very clear.
    You seem to have an excellent grasp of HP48 internal hardware.


    It seems that my only way up, on top of finding a way to repair my trusted HP48SX,
    would be to improve an existing emulator, with proper gray level support.
    I'm sure it can be done, the main issue will be time availability,
    as usual ...


    Le mercredi 3 avril 2019 11:33:10 UTC-7, Hic quondam locutus sum a écrit :
    On Tue, 2 Apr 2019 14:18:16 -0700 (PDT), Cyan wrote:

    Thanks for the detailed answer !
    It's way more accurate than anything I could find so far, and helps a lot !

    I do have a few follow-up questions :

    There is an internal counter, running
    off of the clock signal from crystal oscillator, that refreshes the screen at a
    constant rate after a certain amount of clock cycles have elapsed. So, assuming
    that the oscillator is perfectly stable, the nominal refresh rate is 64Hz. From
    the display controller's POV, the refresh rate is always 64Hz.

    I read that as : the CPU and the Display are both driven by the same crystal oscillator,

    Well, there is an important distinction : The display and the timers are *directly* driven by the crystal oscillator whereas the Saturn CPU's clock signal is derived from a PLL which generates a high frequency multiple of the crystal oscillator's frequency.

    and therefore, ignoring potential oscillator variations, the amount of CPU cycles between 2 refresh is simply hard-wired.

    Well, because of the Saturn CPU's PLL's small fluctuations ( there may be some
    "spread spectrum" functionality, but I'm not sure ) with respect to the crystal
    oscillator, the number of clock cycles is not going to be exactly the same for
    each refresh. But, you're correct in that the refresh frequency is "hard-wired".


    It should be constant, no matter what (except obviously when display is turned off).
    Is this number of cycles known / documented somewhere ?

    It's not known because it's not exactly the same in-between each display refresh
    due to the display and timer parts of the Clarke / Yorke SoC being driven directly by the crystal oscillator and the CPU clock, while being derived from
    the same oscillator, is generated by a PLL. So, one cannot calculate an *exact*
    cycle time, but only an average. Depending on one's HP48's clock frequency, the
    number of cycles in 1/64th of a second can vary as the CPU clock varies between
    as low as 3.69 MHz to 3.9 MHz on an HP48-G/GX ( Personally, I've never seen a clock speed that is 4MHz or greater ).



    The display controller drives each row at a time. The CPU is halted for a >> certain amount of time for each row of display memory, so that the display >> controller can access RAM and drive a row. So, since there are 64 rows, you
    could, I guess, say that there are "64x64=4096" "refresh events" per second.
    But, these "refresh events" only start every 1/64th of a second and don't last
    very long*.

    Thanks to your explanation on the display controller, I now get that the CPU gets HALTed when a line is refreshed. It lasts "a number of cycles", and I'm not sure how much. Then it can resume.


    The halt lasts 22 to 23 microseconds.

    In my initial message, I feared that the CPU was actually being interrupted, meaning that it has to stop doing its stuff, save some essential registers and context information, proceed on driving the refresh operation, then restore initial state, and
    resume. All this context switching would have costed "something", and it would have made a difference wether it was happening 64 or 4096 times per second.

    The difference between 4096 short HALT of 64 longer HALT is moot if they equal the same number of HALTed cycles. Actually, 4096 would be better, as it would spread the load more evenly.

    Conservatively, the number of halted cycles, for each 1/64th of a second refresh
    period, should be around 3.9e6 Hz * (22.5e-6 s * 64) = 5616 cycles, for a G/GX
    at least. This is about a 10% slowdown.



    there is no interrupt generated by the display controller when LINECOUNT >> is equal to 0. One method to mostly get around this is to configure a user >> interrupt service routine which sets TIMER2 to generate an interrupt *just >> before* LINECOUNT is equal to zero. After the interrupt is generated, the CPU
    would have to perform some busy waiting, but it wouldn't last long.

    Well, thanks. I've been trying exactly that strategy.
    But the results were disappointing, to say the least.

    Even though I'm sure I selected TIMER2 value with a _very_ conservative wait amount, basically guaranteeing that the CPU would be busy-waiting, it was still resulting in large tearing.


    That's interesting, as I've used the same method, but without any tearing, and
    so have many greyscale action games on the 48. Note that I always use double-buffering where the new display data is written to display memory after
    the refresh cycle ends.


    An important thing though : since my real HP48SX stopped working recently (I suspect a RAM corruption problem though can't be sure), I've been relying on emulator (emu48) to continue development. emu48 is said to be cycle-exact, so I had high hopes
    that it would be representative of a real hardware. But I guess that at that level of details, the emulation may reach its limits.

    It could be that the level of tearing observed was in fact emulator-related, I could not confirm it on my real calculator.

    My guess is that it's emulator related.

    Regards,

    Hic quondam locutus sum

    --------------------------------------------------------------------------------

    Send e-mails to "NOhp48 Sat Palephnaught Adot Morg"

    ( Remove the capitalized letters spelling "NOSPAM" )

    --- SoupGate-Win32 v1.05
    * Origin: fsxNet Usenet Gateway (21:1/5)