Close

Tilp2, and a rambling on the benefits(?!) of DOWNscaling

A project log for Vintage Z80 palmtop compy hackery (TI-86)

It even has a keyboard!

eric-hertzEric Hertz 05/27/2021 at 07:3116 Comments

yes, I can say that I managed to connect the TI-86 via "BlackLink" aka "TI-Graph Link for Windows" cable (so maybe the "$4" homebrew cables as well?) via pl-2302 usb-serial converter in linux. Yes, I had to edit the sourcecode of the tilp connectivity library because for some reason you can't enter the /dev/ path directly... also, i didn't fight with permissions, just ran tilp2 under sudo.

I just hardcoded "serial port 4" to /dev/ttyUSB0, leaving the rest the same... it seems the I/O address is completely unused, maybe a remnant from its originally being written for DOS or something? 

Other than that, it seems all I really had to do was change the delay in ~/.tilp to 3000us from the default 10, and wait patiently for several minutes to transfer 3KB. Though, to find out such a huge delay was necessary (why is it there at all?!) involved a lot of other code changes (mostly just printing variables) which may have some effect, as well, by adding extra delays elsewhere. I dunno, and aside from the hardcoded /dev/ entries' being a huge hindrance, and delays that seemingly should be unnecessary due to the inherent syncing/handshaking in the graph-link protocol (which is actually quite clever, BTW), I dunno what's responsible for making it eventually function besides just finger-crossing (or, rather, what made it NOT function, in the first place). Oh, and some of the stuff still flakes-out... "backup" isn't functioning, says the calc responded with an invalid command... yet it clearly /did/ respond... (0xAA, 0xAA).

Regardless, the goal was to get the assembler ONTO the calc so I no longer need to run my engine just to experiment with z80 assembly, and that's on there, now. Wee!

@ziggurat29   might like to look at some of the files that came with ZAC, including names/addresses for OS functions .. I see a bunch there... _putc, _fdiv, _clrscreen, etc. Some really intriguing, and also address stuff for e.g. the framebuffer. I think those files may be in TI's format, which may be unreadable on a computer without some conversion. 

Compy-wise, the laptop I bought for $10 (last day, half-off!) at an estate sale worked way better than I remembered. I get about an hour or more off the battery, but can't run off my 120W inverter unless the engine is running. Too much voltage sag in the cigarette lighter wiring, I think. So, run for an hour, charge for an hour, repeat. Linux Mint actually works with Suspend, which was a bit of a surprise in this era.... This is a core2 duo, so 64bit dual-core... uses a TON of power compared to the atom machine my buddy rigged up for me, and probably not as computationally powerful (being older and half the cores) but I accidentally fried that atom machine and haven't had the means to try to fix it. In the intermediate, I swapped the atom's guts for a PiZero, and it works quite well considering its low specs, but it is not at all usable for web browsing, it's kinda hokey in its assembly making it delicate and unwieldy, and I managed to fry its HDMI port a while back, requiring a parallel-interfaced display replacement, which I scavenged from an old DVD player...  800x480 isn't much to work with... though the scaler built into the Pi runs 1200x720 on there surprisingly well, making two terminal windows legible. Though, I don't really consider it powerful enough to even try devel on. So... core2 is a pretty decent machine. $10! Now, its GIGANTIC screen is surprisingly low-res, too... so I might look into downscaling with xrandr (is that possible?). 

I'da NEVER considered that a good idea back in the day... use the native pixel resolution! But, since the Pi/DVD-display combo kinda forced me to try it, I was very pleasantly surprised. And, why not? 1200 shrunk to 800 is one lost pixel for every three... that's hardly noticeable with today's huge fonts and icons and such. It's really only a problem for me in Gimp, and even then, individual pixel editting is usually done zoomed way in. And the extra desktop space is HUGE in comparison to the minor glitches. The only other annoyance I can think of right now is trying to manipulate window-sizes, grabbing that edge if it's a missing pixel, but even that isn't tough since the mouse pointer changes when in position. I mean, seriously, it works surprisingly well compared to what I'd thought for many years prior. Don't forget, a 1/3rd increase in both directions is a MUCH larger increase in AREA. I wouldn't recommend it for a product design, but as a workaround it's pretty awesome. Though, on the Pi, I believe the scaler actually does some filtering where two pixels are shoved into one, so I imagine/it seems it makes edges stand-out, rather than just choosing one pixel over another which could otherwise make a single-pixel-wide edge disappear (e.g. the upright on an 'I'). That sophisticated downscaling probably makes a huge difference... I bet I wouldn't've been nearly so impressed if not for that. Thus, I assume (though don't know for sure) the Pi's GPU is doing that. We'll see if the lappy's does... 'cause as it stands two terminal windows can't be displayed side-by-side on a friggin' 15inch widescreen?! Seems ridiculous. Shrinking the font helps a little, but still the menus and window borders... and the font sizes are pretty set in stone... what if I want 11.8 pitch? Or 9.6?

Oh, and another benefit of downscaling rather than having a higher resolution... my eyes aren't what they used to be, so that single larger pixel dot over an 'i' is a lot easier to see with a lower /native/ resolution and downscaling. Heh! I'DA NEVER THOUGHT I'd vouch for downscaling.

(Some math... 800x480 native is 384000 pixels; downscaled from 1200x720, which only drops one pixel from every four (whoops, not three)... no wait, I'mm all off, here... 1 pixel for every three (I'm getting confused between native pixels and framebuffer pixels... really? I was pleasantly surprised by its shoving three pixels in 2's space?! And four into one at every corner?!) Well, I was pleasantly surprised at the visibility of shoving 1200x720=864000 x-windows pixels into 384000 actual pixels... more than DOUBLING the workspace area on a tiny screen)

...

But, there's a lot I'd been planning that involves overnight uninterrupted processes... so obviously alternating between an hour on followed by an hour charging isn't feasible for those. Though, I've found an outlet I might be able to use for a night or two here and there.

Thus, all this, why I think the TI-86 being (now) a standalone Z80 assembler development system akin to a vintage like a Kaypro, could be handy in my situation... even runs on AAAs! We'll see where it goes.

Discussions

ziggurat29 wrote 05/28/2021 at 15:10 point

minor point regarding permissions:  if you add your user to the 'tty' and 'dialup' groups, you won't have to sudo to open the serial port.  (well, at least this is what I did a couple weeks ago on RPi -- maybe your distro is different).  E.g. from my notes:

#lets user use serial port without sudo (need logout login to take effect)
sudo adduser ziggurat29 tty
sudo adduser ziggurat29 dialout

  Are you sure? yes | no

Eric Hertz wrote 05/28/2021 at 21:50 point

LOL, I was wondering what happened to your earlier comment... yeah, the threading here is not at all ideal for our sorts of discussions... which I think a bit odd, really, since it seems collaboration is encouraged here... I guess every platform has its quirks.

Though, I admit to being a bit lost as to where we are in this thread...

Adduser: see, now, I'd been thrown down so many gnarly paths trying to look up things like that that I'd long-since given up... adduser, to a group, seems the most logical... but I've even been sent down paths requiring looking into systemd and USB PIDs for something so basic?! Just run 'em as root. Heh. No, adduser makes MUCH more sense.

I think I may try to tackle my first z80 assembly program in a bit. 

BTW, it seems this thing uses a few weirdities like bank switching and 24-bit addressing that aren't defacto z80 stuff... does that mean it has non z80 standard instructions or registers, or does all that stuff fit withing 'pure' z80 assembly? (Also, just read about shadow registers, is that normal for z80?)

  Are you sure? yes | no

ziggurat29 wrote 05/29/2021 at 20:38 point

I apologize for breaking threading -- I should have looked more closely, but there is no 'undo' here.  To wit, this platform is based on WordPress, and the oddities are due to that underlying platform.  I don't know why you can only 'reply' so deep, but we both know that we can work around it by replying to the last message, and then time will take its toll and order things for us.  Now as for reliable notifications... well we're on our own there.

The bank switching is purely a platform invention -- the Z80 knows nothing about it, and any discussion about 'direct 24-bit addressing' is a myth.  Such 'direct addressing' can only be done through software, and as such is not 'direct' at all.

I haven't detected any deviations from the standard Z80, but that doesn't mean there aren't any.  Toshiba was free to do whatever they wanted with their silicon.  E.g., to wit, the GameBoy's ostensible z80 core had a couple deviations.

Do have fun writing some assembly programs.  When you tire of shuttling registers about, possibly check out SDCC:
https://en.wikipedia.org/wiki/Small_Device_C_Compiler
and free yourself from the bondage of assembly.  But that will be a significant project in itself!  Also, you only have 9000 bytes for your assembly program if you want to play in TI's scheme, so maybe not really doable?  Fate favors the bold; I say go for it!

As for 'shadow registers' -- this is a standard Z80 concept.  There is a 'main' bank of registers that normal programs use, and a second bank that looks exactly the same that can be switched in with a couple instructions.  They were intended to be used for ISR's, so you could get in, do your business with your own set of registers, and get out.  Their purpose is to avoid the cost of otherwise having to do a lot of push'es and pop's on entry and exit from the ISR.  The TI-86 does use this scheme.

You cannot transfer between the 'main' and 'alternate' register set.  I don't know for certain, but perhaps this limitation is why they made two instructions for switching:  "ex af, af'", which just swaps out A and the Flags, and "exx" which does bc, de, hl, ix, iy.

The z80 design predates the concept of 'VLSI' and certainly 'RISC'.  It is very much microcoded, and I think (though I could be wrong) that Faggin did hand tape rubylith in the original design.  As such, luxuries such as large register sets, and long instruction words were just not in the cards.  "CAD" -- lol.  You first have to have the "C" before you can have the "AD".

The z80 is a cherished memory to me because it is what on which I as a late 1970's nerd-child cut my programming teeth.  It's a klunky processor.  The then contemporary 6502 was the shape of what was ultimately to come.  But I love my z80 as one loves their mother:  she might not be the greatest looker to everyone, but she was sexy enough to at least someone that she made you.  And I will always love her for that.  The z80 birthed my entire career.  Yes I haven't talked to her in aeons, and I sometimes do get emails 'how come you don't call?', but we're all on to ARM and RISC-V now, mom.  But I'll always still show up for thanksgiving, christmas, and mothers' day.

If you're into silicon porn, then I cannot point to anything better than Ken Shirriff's magnificent work; e.g.:
http://www.righto.com/2013/09/understanding-z-80-processor-one-gate.htm
He has several other articles as well about the silicon underlying the z80.

  Are you sure? yes | no

Eric Hertz wrote 05/29/2021 at 21:49 point

very interesting and helpful insight, I'm sure I'll be referring back to soon. Glad to know the thing is standard z80!

As far as "we're all onto [64-bit RISC]", well, I suppose, but x86 is still a thing, and the 8086 is an extension of 8080 to 16-bit, and 8080 is, well, z80's older brother... 

Anyhow, for me it's a mix of vintage nostalgia AND trying to understand new-to-me/present-day concepts... Basically everything low-level I've done is on an AVR (Harvard Architecture)... which is GREAT for the sorts of things I usually do, but the next step up has been very elusive to me for a very long time... In fact, way back in college my biggest-ever design was around an ARM7... I thought it'd be a simple step up from the AVR, but in fact I never figured out how to run the code from RAM, and as-such it was actually slower than my AVR designs. (Really quite a shame, since I think the I/O/RAM and related software design was quite clever, allowing DMA between multiple devices and multiple memory banks simultaneously, which was in fact the whole point of the design, simultaneous/synchronous high-speed DtoA fed through a circuit/device-under-test and back through AtoD). So, I think you can see, starting "smaller" and "working my way up" is apparently necessary for my learning process... CACHE, MMUs, and so-forth are still a ways away!

  Are you sure? yes | no

ziggurat29 wrote 05/29/2021 at 22:44 point

is x86 still a thing? lol.  the 386 was a magnificent achievement -- a mainframe on a chip.  Intel didn't intend that line to go beyond that point.  They were driving towards the RISC future with the '860, but that processor failed in the marketplace, so they trudged on with what was selling, and here we are today.  They tried a couple times again, like with the Pentium and Xeon, but still couldn't pull it off.

Zilog on the other hand failed early with the Z8000 16-bit line due to bugs, sub optimum performance, and a strange choice of having two versions: segmented vs non-segmented.  We collectively hadn't decided yet if we liked segmented memory vs virtual memory.  Time has since answered that question.  Zilog lived on in the peripherals market, and also embedded with the Z180.

The Z80 is a good choice if you want to get your hands dirty.  It's a very simple architecture, albeit a bit klunky.  I can always hear pistons chuffing when I look at z80 code -- so many clock cycles; average of 4 per instruction and some over 20!  /NOT/ risc; lol

I find it informative to realize that there are human beings behind these designs and companies, and that reality is not as sterile as datasheets and programming manuals might lead one to think.  Regarding the z80, here is an interview with the founders of Zilog:
http://archive.computerhistory.org/resources/text/Oral_History/Zilog_Z80/102658073.05.01.pdf
In it you'll read about the pragmatics of starting a silicon technology company in the 1970's, and also some of the design choices that were made that comprise the architecture of the z80.

  Are you sure? yes | no

[deleted]

[this comment has been deleted]

Eric Hertz wrote 05/28/2021 at 04:19 point

not sure I totally get it... but as I recall reading, there are two available memory locations to switch in banks... usually one is used for rom and one for ram, but if i understand correctly they can be assigned any way, so it's possible to have three banks of ROM accessible simultaneously. Is it ever used that way, i dunno...

I see now... _PIOVER2 is NOT in ROM page zero, yet the actual constant is... weird. Why bother having the former? I wonder if there are two layers, here... like a core kernel and a separate shell? Maybe a bit like the BIOS vs. DOS? Oh, you did say something about the jump/call stuff being in a separate page, where they could all remain at the same addresses regardless of e.g. firmware updates that might relocate things. Sorta forward-thinking that only barely makes sense since these have ROMs rather'n EEPROMs/Flash... EXCEPTing to make users' assembly programs (binaries) compatible across firmware revisions... hmmm. Seems they really made a point of user binaries with this thing!

Calls vs jumps... i wonder if they programmed much of it in (or for?) C. C style was very firm about not using gotos, and I think it's relatively recent (C99?) that e.g. inline functions and such can actually be explicitly told to truly only ever be inlined... or error if not, rather than not inlining without notice.

Jump table indexing... well... i mean... pi/2 returns a float, but cursorX is an integer, and i imagine some other routines require arguments and such, so such a table might be difficult if things aren't aligned the same?

(Assembly is *barely* within my realm, I'm learning from you!)

  Are you sure? yes | no

ziggurat29 wrote 05/28/2021 at 14:33 point

you are correct; from a hardware capability standpoint, you can page ram or rom in either of the two pageable regions, so yes you can have page0rom simultaneously at 0000, and 4000, and 8000.  They don't intend for you to do that, but the hardware doesn't prevent it.  The rom code is mostly non-relocatable, so usually it just won't work at all if not mapped into 4000.

What blows my mind about the constant is that the code is non-callable.  That means that the programmer needs to know that _PIOVER2 is non-callable, and instead pluck the embedded address out of it at (_PIOVER2+1), and then load data off that.

Regarding page13 API -- yes, I'm pretty sure that is the point:  to make user binaries compatible across firmware versions.  Internal code cannot make calls off the page13 API since something else would be paged in, and anyway the rom code already knows the page0 addresses.  It's kind of impressive that TI made a calculator that was intended to be hacked at such a low level as Z80 asm code.

As for 'is it C', I can't really tell yet.  So far there does seem to be regularity in register usage for function calls, but just now I'm still propagating symbols across pages, so it will be a while before I can analyze further.

  Are you sure? yes | no

ziggurat29 wrote 05/28/2021 at 21:12 point

also, sorry about deleting the comment to which you replied.  you know how threading works (er, doesn't work) on this site, and I moved it below before I noticed you had responded to the original.  so now it all looks weird.  but I guess we're used to that by now! lol

  Are you sure? yes | no

Eric Hertz wrote 05/28/2021 at 06:10 point

FYI, this seems unique to the 86... i wonder if many such things were meant to be used with rst, but they used calls instead? The 11byte copy sticks out...

http://jgmalcolm.com/z80/intermediate/rstc

  Are you sure? yes | no

Eric Hertz wrote 05/28/2021 at 06:21 point

and some stuff about 24bit absolute addressing

http://jgmalcolm.com/z80/variables/abso

  Are you sure? yes | no

ziggurat29 wrote 05/28/2021 at 14:45 point

Oh! That's handy.  I had deciphered some of the RST's on my own, but that saves time on the others I hadn't figured out yet.
As for the 11 byte copy:  the calculator 'virtual machine' has 7 (so far that I have found) 'registers' which are 11 bytes long.  Not all those bytes are in use at any given time, and there's a raft of internal 'copy' routines that transfer varying amounts of data between them.  These 'registers' can hold many types -- obviously the BCD floating point, but also strings and integers and I'm sure others I haven't seen yet.  Maybe I should take a peek at the manual!  Matrices?  Complex numbers seem to be handled by implicitly joining two registers.
As for the 24-bit addressing, I had found the AHL and ADE scheme throughout, and I'm sure that your link will provide a leg up with more insight.  I had not found ABC and AIX.  Maybe I'll stumble across it later...

  Are you sure? yes | no

ziggurat29 wrote 05/28/2021 at 01:22 point

lol; I did look at the .inc files, and spent several days furiously cut-and-paste'ing labels into the disassembly.  it is a grueling and mind-knumbing task and I'm probably less than a quarter of the way done yet.

I'm also not convinced that everything is as it seems.  There are some routines like _PIOVER2 that are not executable code at all, though written that way.  /Looks/ executable:

ROMpage:44EB             _PIOVER2:
ROMpage:44EB CD 0E 15      call    _constPIover2
ROMpage:44EE C9            ret

But /NOT/ executable:

ROM:150E 00  _constPIover2:  db 0
ROM:150F FC                  db 0FCh
ROM:1510 15                  db 15h   ; mantissa 1570796326794897
ROM:1511 70                  db 70h
ROM:1512 79                  db 79h
ROM:1513 63                  db 63h
ROM:1514 26                  db 26h
ROM:1515 79                  db 79h
ROM:1516 48                  db 48h
ROM:1517 97                  db 97h

So, in this case of a constant, the call target points to a constant value, but you're not meant to execute it because that would be garbage; e.g.

ROM:150E           _constPIover2:
ROM:150E 00            nop
ROM:150F FC 15 70      call    m, 7015h
ROM:1512 79            ld      a, c
ROM:1513 63            ld      h, e
ROM:1514 26 79         ld      h, 79h
ROM:1516 48            ld      c, b
ROM:1517 97            sub     a

Weird scheme, totally non-executable.  Why?

a challenge I have in this disassembly is that because of the banking, it's a little difficult for me to propagate symbols across the banks.  I've tried a couple tricks, but am not happy with them, so I'm presently focusing on disassembling Page 0 as much as possible in the context of having Page 13 mapped in.  (Page 13 is always what is mapped when running user ASM -- it provides the public API to the internal implementations, and is pretty much the entry points that the existing .inc files document.)  Then I guess I'll wind up with 13 separate disassemblies.  Hopefully RAM will not complicate this further.

I have noticed out of the corner of my eye some structure in the paging.  It seems like the error stuff is on a page, and the editing stuff is on a page, and trig functions are on a page, and....

Maddening fun!

  Are you sure? yes | no

Eric Hertz wrote 05/28/2021 at 01:49 point

ohhhh.... "Pi over 2" i had to reread that many times, thinking PIO was I/O related!

I wonder about this callability, maybe _PIOVER2 is in fact supposed to be and contain a function-call, but if a different page is located in the constant's space it might be a function for loading the constant from the other page...? E.g. one page might be filled with functions like "float piOver2(void)" which then call "loadConstant()" within that same page, which then switches pages to load a constant stored at the same address as the original function-call...? Seems iffy, since it would have to have a return back into the function-page... i dunno, totally hypothesizing...

Nice work, BTW.

  Are you sure? yes | no

ziggurat29 wrote 05/28/2021 at 14:03 point

can't.  the implementation is in page 0, and is always mapped.  The usual way of doing thunks like this is a jump table, in which case all this stuff would be jumps into page 0.  But they're not -- they're calls.  Mostly!  There are indeed several jumps.  But why?  If TI had stuck to a jump table, then things would be much simpler, because you could directly index into it.  In fact, it would also be slightly faster because you're not incurring stack ops for the call.  But they didn't, and they didn't do it consistently.

So the fact that they mixed calls with jumps, and then this weirdest of things with the constants makes me think that things were perhaps a bit wack in TI's software division (if it was not outsourced) in the '90s.  Frankly, the charm of disassembling these old ROMs is that I get a feel for the mind of their authors, and also the tools they used to build them.
I did find the cross-page call mechanism -- stook out like a sore thumb.  A loooooottttt of instructions.  You complained that this calculator was slow -- maybe this is why.  What's notable is that it is one level deep.  I found the processor state block, and there's only one of those.  So you can't generally call across pages ad infinitum.  You can call a routine in one page that has to be happy doing its work completely while mapped in and not defer to a routine in another page (except maybe for a fault), and then return.

  Are you sure? yes | no

Eric Hertz wrote 07/16/2021 at 09:35 point

I remembered your plight with Piover2 a bit differently while trying my hand at some disassembly of my own... trying to figure out if this 24bit addressing scheme is purely software or maybe hardware... looking like the former, switching a bank in, doing its thing, then restoring the previous bank before returning... and all that, say, just to grab one byte from a user variable... now to copy it to another!

So, I've only been able to parse __load_ram_ahl, which is supposed to swap pages, anyhow. The sceme, for 24bit ram addressing is pretty straightforward, but looks a mess of if-thens. Looks very much like it's all just calculations in software. But, still, something hardware has to handle those higher address bits which are shared between the ram and rom chips, so, it's still plausible there may be a "quick-mapping" sort of scheme using ports we don't get to access... because it would seem absurd to me to e.g. copy from one bank to another, or just grab a single byte, without it! 

So, i kept looking for other functions using 24bit stuff.... and the remainder seem... absurd.

I dare you to look at write-byte-ahl+inc, at 0x5567!

I'll give you a hint, 0x28cb is "___bank_call" yet nowhere in it do I see any calls. And nowhere do i see any memory accesses. From the looks of things, it grabs data from the stack, but nothing I've seen /loads/ the stack prior. So, then, start looking at the code surrounding the calls.... and... i think it's not executed code at all, but a 2byte address and a 1byte page. Which would *almost* make sense, but the addresses are *way* over 16KB. This call/access-raw-data-that-looks-like-code scheme is what i thought I recalled your discussing regarding Piover2...

I found *one* page on google (isn't there an award for that?) Regarding bank-call... but it's for a TI-82, and I don't really get it any more than the disassembly...

https://codewalr.us/index.php?topic=929.0

....

WAIT! when you make a 'call' it puts the PC on the stack!!!(?) So, then, call a call, the second has data before it which the function can find by reading the old PC value from the stack, and reading the bytes prior in the program memory. WEIRD!

OK... Still gotta figure out this weird addressing that's not within the ROM mapped-page. A. And B, figure out how /that/ function actually calls the one addressed in that data. Still don't see that. No jump, either. Oh, nice, and C) no ret from the second call-table... so then that data would get executed.

WTH?

  Are you sure? yes | no

Eric Hertz wrote 07/17/2021 at 21:09 point

i figured it out and wrote a log about it... pretty clever. The addresses are so large (A) because they're loaded from the second (mapped-in) page. Thus, of course, the addresses are relative to the processor's address-zero, so they're all at 16kb-32kb after the mapping. 0x4000 higher than the address /within/ the page.

The interesting bit is B) it doesn't /call/ the function, it overwrites the calling-function's return-address in the stack, then when it returns, it returns to the requested function! Clever. And, thus, no need for a ret in the calling function, after the call (C).

And, no... it looks like there is no special hardware for 24bit addressing; it's all just math and mapping...

  Are you sure? yes | no