EECTuning.org

This is where the BIN Hackers and definition junkies discuss the inner workings of the EEC code and hardware. General tuning questions do not go here. Only technical/hardware-specific/code questions and discussions belong here.

I'm about to read over it now, but if you can, make sure to put a version number in the file name which makes it far easier to discuss as the revisions go on.

Edit:
The area where you use fingers to explain binary and then jump into HEX needs a bit more work. I know these things, and I started to feel lost reading the explanation. But I think I can help clean that kind of stuff up. I don't have time tonight, but I'll see if i can get an edit of what you've got here tomorrow and get it reposted. I'll add a version number to the file IF you don't get a newer version posted before I do. Otherwise I'll start with whatever you post tomorrow and increment.

You also jumped into word, double, and long without explaining them. As a firmware developer, I know what those are. But the non-techie isn't likely to have a clue what you are talking about.

Good explanation of how the stack is used, although I just assumed you'd do what every CS-101 book does and compare the computer's stack to a stack of cards (first on the stack, last off the stack). At the very end, you refer to a register. Registers haven't been explained yet, so it's use of a term that doesn't clarify anything you've said so far.

Good explanation of interrupts. The things I'd add are that interrupts often have priorities...I assume 8061/5 do, and in special cases, interrupts can be disabled when the processor is doing something sensitive that cannot be allowed to be interrupted or would risk corruption to be interrupted. These are details I don't know well about the 8061/5 and would like to hear explained in more detail. Also what if you don't want/need all 8 or 48? Can you opt out of them completely? This may be getting more in the weeds and just be satisfying my personal curiosities and may not actually belong in an EEC primer document. But I'm curious to hear other's thoughts...

The 8065's banking mechanism was rushed. Before someone can understand that, they need to understand that the purpose for banks is to get more memory because the program gets too large to fit in 65535 address space. So since the computer can't handle another bit or two of address space, multiple "banks" of 65535 address space is created, and you switch between them....something along these lines.

Thanks for that - Yes the multibank WAS my very first attempt, so not surprised.

The order of things probably needs to be adjusted so it flows better.

this was as I thought of stuff - have to start somewhere

I won't amend anything for now and will put a version num on it for next revision - good idea.

The fingers idea - wasn't sure, but I reckon it needs a practical real life example somehow.

Yeah probably some big jumps that need an extra step....

Interesting aside - in my younger days, I worked on a 16 bit machine with address extensions, and it was called 'virtual addressing', where it had
8 extra bits, but was all done as specific index type instructions, a bit like the indexed mode in 8061, but the 'fixed' (immediate) part was multiplied by 256 and then the 'offset' added - so didn't have concept of 'banks'.

Here's some tweaks and reformatting I did. I got almost all the way through. But towards the end, I ran out of time. Anyway, here's what I did. Feel free to undo whatever I did or expound further...and more importantly CORRECT anything I might've gotten wrong!

BTW, my spell checker changed some of the US/UK variant spellings of things. Feel free to revert them back to the king's spelling or however that's said. Does New Zealand use a king? (Archer reference for those that watch that show)

Edit:
I updated to v3. Also something caught me by surprise at the very end. The doc indicates that the first few instructions of an ISR are to save off the Program Counter to the stack and some processor status. Is this true? I thought the hardware did this implicitly as part of vectoring off to the ISR. In my head, the interrupt physically came into the chip, the hardware pushed the current Program Counter (PC) to the stack, vectored the PC to the ISR being triggered, and then started executing the ISR. When the ISR completes, the RETURN would then pop off the return address off the stack that hard-coded silicon put on the stack. Is that not the case?

If that, indeed, is not how it works, then how does the code in the ISR access the previous PC position? By the time it starts executing, the PC should already be updated to point at the ISR so I wouldn't think the value currently in the PC register is going to be what's pushed.

Also most other processors, not only have a vector table, but also have an Interrupt Register where you initialize which Interrupts, if any, are active. You usually mask off the bits that represent interrupts you don't want to use OR don't want firing at a given moment. Then there's often a single non-maskable interrupt that can be defined. Do the 8061/5 have either of these concepts? If so, it might be worth at least mentioning the existence of these concepts, even if the nitty-gritty details about how to initialize them and program for them are glossed over.

I probably need to explain that better !
To my understanding of how it works (someone correct this if it's wrong)

When an interrupt happens, CPU sets flag in Int_status_reg, then current instruction is completed.
Next, CPU internally does a 'CALL [0x2010+INTNUM]' , exactly like a subroutine call.

So the return address IS automatically stored on the stack, but the Processor Status Word is NOT saved.
On the 8061 at least (must double check for 8065) interrupts are NOT auto disabled.

So therefore, the first thing an interrupt handler subroutine must do is a PUSHP and then a DI.
and the last thing the subroutine must do is a POPP, EI, and RET.

It's theoretically possible then the CPU could handle an interrupt within an interrupt by this method, as it's quite safe,
but EEC code doesn't seem to do this, probably because handlers use the CPU time for their calcs ?

Multibanks use this same trick too.....

On multibanks, you will see that subroutines do a PUSHP so that the callers bank is kept for the return call (again POPP, RET).
Which means (as I understand) when you swop banks, the PSW is NOT automatically updated with the new bank, otherwise, code would have
to do a PUSHP before the CALL. To return to callers bank, code has to do a POPP before the RET.

So interrupts and subroutine calls are handled identically in the CPU core.

To me this all logically makes sense....

(yes should add some notes about those extra registers like int_mask.....)

...So the return address IS automatically stored on the stack, but the Processor Status Word is NOT saved...

What's the significance of the Processor Status? I could take guesses, but I'd like to know for sure.

I'm also assuming the nmemonic PUSHP and POPP are specific to preserving the Processor Status registry. Correct? The EECTch98.pdf just describes these as push/pop flags. But it doesn't elaborate as to "what" flags. So I assumed Processor status flags since that's what you alluded to.

Are the Processor Flags a register that can be accessed directly? I don't see it listed as one of the standard special registers in the 0x0-F range. My guess is the flags are not mapped to a register and access to them is via opcodes only to set/clear them, jump based on them, or to preserve them on the stack.

...On the 8061 at least (must double check for 8065) interrupts are NOT auto disabled.

So therefore, the first thing an interrupt handler subroutine must do is a PUSHP and then a DI.
and the last thing the subroutine must do is a POPP, EI, and RET...

This makes sense. And I guess it's possible you could still get nested interrupts if a higher priority interrupt came in during the time between PUSHP & DI and EI & RET...although if it did, it wouldn't be doing any harm in those cases. Although their practice of this, I'm sure, makes the code more deterministic and less prone to race-condition bugs.

I did find where the interrupts do have a priority and they are defined in the EECTch98.pdf. However the doc only covers the 8 interrupts the 8061 has. It doesn't seem to have a section that elaborates on the 8065's expanded interrupt capacity. Reading in the 8096 application note, it seems it too has an expanded interrupt vector table, but it groups them so some get enabled/disabled with the same bit in the bit mask thus negating the need for a larger interrupt Pend/Mask register.

Do all the vectors get filled out? Or do some EEC-V bins disable some groups that they don't intended to use?

...On multibanks, you will see that subroutines do a PUSHP so that the callers bank is kept for the return call (again POPP, RET).
Which means (as I understand) when you swop banks, the PSW is NOT automatically updated with the new bank, otherwise, code would have
to do a PUSHP before the CALL. To return to callers bank, code has to do a POPP before the RET.

So interrupts and subroutine calls are handled identically in the CPU core.

To me this all logically makes sense....

The part I got lost in is how exactly multi-banks work. I get that when code wants to transition to another bank, it has to update a register in the processor. However this would suggest that you can only transition between banks with code that knows what bank it is going to.

But you suggested it is possible for code to CALL a subroutine that's located in a bank other than the current bank. And as you said, that would require the PUSHP happen before the CALL. Then the subroutine's calling convention would have to be that it performs a POPP without doing a PUSHP of its own in order to get the bank selection that was in place prior to the CALL. This also suggests that when you do a bank selection, the bank isn't actually referenced until a JUMP, CALL, or RET to a specific ADDRESS is made. Thus you could change the bank selection, but "strobed" instruction fetches would continue to come from the bank of the previous instruction. This would be what would allow you to do the POPP (restoring the caller's bank selection), then still get to the RET that follows in the subroutine's bank. As I'm typing this, it's occurring to me that this MUST be how it works, otherwise it would be chaos. But it'd be nice to have someone that knows say "Yes, you got it."

However when interrupts come in, are all interrupts just understood by the processor/hardware to be in a specific bank? Or does each bank need to hold interrupt code at the same vector address so it doesn't matter which bank the processor is currently executing out of, when an interrupt's CALL is made, the CALL will be to code associated with that interrupt?

I just assumed it'd be the former, where the processor just knows ISRs only exist in, say bank 8, and the hardware doesn't need to update the Program Status register to make an interrupt call. If that's the case, that would explain how the interrupt can do a PUSHP and preserve the Program Status (i.e. bank selection) that was active prior to the interrupt being triggered? If this is how it really works, then this is fundamental difference between code-generated CALLs and interrupt CALLs. Another is based on the opcode list, there seems to be a difference between a normal RET and a RETI. My guess is the RETI clears flags where a normal RET does not...another fundamental difference between the two CALLs. So subroutines and ISRs are similar, but not the same. They do have some subtle differences.

Back to the banks, something that has always confused me is there seems to be confusion in how they are numbered. I've always heard that single-bank EECs technically are using Bank 8. And that 4-bank EECs have Banks 0,1,8, and 9 I think? Trying to get clarification from the EECTch98 doc just added more confusion for me. The pinout for the J3 bus (pg19) shows a BS0 and BS3 which I'm assuming are bank-selects. And based on their numbering, I could assume there are BS1 and BS2 logically in the part, but not routed to the J3 port which would explain the Bank numbering of 0,1,8, and 9...
0000b = Bank 0
0001b = Bank 1
1000b = Bank 8
1001b = Bank 9

There's also mention on pg 18 that on Reset, the processor always starts executing at 0x2000, Bank 8.

However on pg 14, the doc refers to 8065-only opcodes in Table 9 where there's an opcode for ROMBANK and BANK0-3. Also I couldn't find any details as to how those opcodes get used. Are some writable and others status values? What's the story with these banks and the opcodes that deal with them?

Have you had a chance to read over the v3 document and make any comments, additions, corrections, removals?

What's the significance of the Processor Status? I could take guesses, but I'd like to know for sure.

I'm also assuming the mnemonic PUSHP and POPP are specific to preserving the Processor Status registry. Correct? The EECTch98.pdf just describes these as push/pop flags. But it doesn't elaborate as to "what" flags. So I assumed Processor status flags since that's what you alluded to.

Are the Processor Flags a register that can be accessed directly? I don't see it listed as one of the standard special registers in the 0x0-F range. My guess is the flags are not mapped to a register and access to them is via opcodes only to set/clear them, jump based on them, or to preserve them on the stack.

This is Processor Status Word (PSW) which contains all the flags for conditonal jumps (Carry, Negative, Zero, Sticky, Overflow, Trap) and Interrupts Enabled/Disabled status.
In 8065 it also contains "active code bank" (4 bits) and 'active RAM bank" (2 bits). PUSHP and POPP do push and pop the PSW.

You cannot address the PSW directly, but you can PUSHP and POP, Rxx to get it into a register of your choice.
I assume you could also do the reverse to write to it, but I would guess it's officially <undefined> behaviour, as you wouldn't normally do this....

Also, although I don't think it's specifically stated anywhere, at seems that the PSW is the LAST thing to be updated for each opcode, and some opcodes don't update it at all.

(priorities and) Do all the vectors get filled out? Or do some EEC-V bins disable some groups that they don't intended to use?

In all the binaries I've seen so far (both single and multibank), the vectors are ALWAYS filled out as a full list, even when interrupt mask (R8) should prevent them happening. Typically these ignored/inhibited interrupts have an entry in the vector list which goes straight to a RET (or RETEI), so it just goes straight back. Probably an extra safety thing. Yes they have priorities, but I don't see anything in the EEC code, I guess it's just the order they are triggered in.

8065 had a much bigger list, (OOPS !! that should have been 40 interrupts, and NOT 48 !!) and it has the original byte registers and 2 extra word registers (i.e. 40 bit total) for both Int_pending and Int_mask functions, so each interrupt can still be enabled or disabled individually without grouping.

YES, from your later question, each bank HAS to have a complete interrupt vector table from 0xK2010 to 0xK205f, where 'K' is the bank number.

Yes, I see the multibank extension as very much as what us Brits would call 'a bodge job'. (not sure what the equivalent US slang would be, but anyway, a bad fixup)

The extra 4 bits have been grafted on to a 16 bit core, and done in a way that the ordinary 16 bit opcodes don't see any kind of 'bank' at all, as the addresses are adjusted in an invisible manner for each opcode instruction, and then a few extra instructions/prefix opcodes are added to allow manipulation of this 'hidden bank'. This to maintain a [mostly] backwards compatible setup, I guess.

The Ford Handbook shows a picture which shows there are several places where the 'bank' is held, inside the CPU. Without getting into the messy complicated bits -

Think of the 8065 as keeping 3 things - a 'data' bank, a 'code' bank, and a 'temp' bank.

So when you execute an ordinary instruction like ADDW R32, R34 (R32 += R34) no bank is necessary.
When you do an indirect or indexed however, ADDW R32, [R34] then R34 is read and used as an address, and the data bank is added at the top to make 20 bits.

When each instruction is read in the first place, the code bank is added to all instruction fetches.

the temp bank is used when you do a BNK instruction, which the Ford handbook says is actually a prefix (like SIGN), and it overrides BOTH data and code banks (I think) for one opcode. This is how you can jump from one bank to another....then the PSW will get updated after the jump (? I assume so...)

This "single bank is always an 8" is only a convention really, THERE IS NO BANK for 8061. But if we treat it as if it was 8 (SAD does this) then the same analysis code can deal with single and multibanks without any extra logic....

0,1, 8,9 Yeah confusing, until you realise that it's only 2 binary bits 8 + 1, and the CPU always starts in bank 8 (bit 3 set), so it could just as well be called 0-3...

From what I read, looks like the 8065 can truly handle 16 banks, but maybe not the other chips ? (and so Ford saved 2 pins on each chip ? not sure after going via MBUS ). Not sure how MBUS is modded (if at all) to handle the extra 4 bits....

Perfect. I think this clarifies a LOT of my gaps in knowledge about the EEC.

One of the things I just realized is sharing versioned files is an old-school way of dealing with this. The more modern way of handling this is a wiki where multiple contributors can edit the file and each edit is tracked. No merging. No conflicts...etc. If that appeals to you, then I can create a page on wikipedia, or whatever other wiki host you'd like.

Don't have any problem with a wiki - it's as good a way as any to compile lists and data, and answer questions a single spot.
Lets try that !!

Hope this all helps so far, and this is what I've worked out (especially for multibanks) from my previous low level experience, so it may still be wrong....
The Ford handbook is of course for coders who already know what they are doing, so useful for me, but not so much for a non-techie. But if I/we can bridge that gap, happy to help.

I haven't entirely worked out what some of the extra chips do either, for example what DUCE stands for ("DUuty Cycle Exec" ?? might make sense for the idle speed control valve).

a). After the hardware decides to process an interrupt
it generates and executes a special interrupt-call instruction
which pushes the current program counter
onto the stack and then loads the program counter with
the contents of the vector table entry corresponding
to the interrupt. The hardware will not allow another
interrupt to be serviced immediately following the
interrupt-call. This guarantees that once the interruptcall
starts the first instruction of the interrupt service
routine will execute.

Being this is in my wikipedia user sandbox area, I don't know if permissions or access restrictions will crop up. From everything I can tell by google-searching, it appears others should be able to edit/access sandboxed material AND edit it.

One thing I will say is getting it formatted from DOC to WIKI was not an easy task. And getting used to the 'mediawiki' markup format takes a little getting used to as well. Most anything you want to do is google-searchable. But it can be frustrating when you just want to do something simple, and the online editor for the page just doesn't have what you are wanting to do as an easy clickable option.

There's still a lot of formatting that needs to be done to get it consistent. And there's a formal Wiki format that a published page must adhere to before it can be published on wikipedia. I'm pretty confident this doesn't meet those standards as I don't know what those standards are. But if this is something that is to be published at some point, it'll need to conform...

Being this is in my wikipedia user sandbox area, I don't know if permissions or access restrictions will crop up. From everything I can tell by google-searching, it appears others should be able to edit/access sandboxed material AND edit it.

One thing I will say is getting it formatted from DOC to WIKI was not an easy task. And getting used to the 'mediawiki' markup format takes a little getting used to as well. Most anything you want to do is google-searchable. But it can be frustrating when you just want to do something simple, and the online editor for the page just doesn't have what you are wanting to do as an easy clickable option.

There's still a lot of formatting that needs to be done to get it consistent. And there's a formal Wiki format that a published page must adhere to before it can be published on wikipedia. I'm pretty confident this doesn't meet those standards as I don't know what those standards are. But if this is something that is to be published at some point, it'll need to conform...

Oh - I was thinking of a wiki entry in this forum, which quite a few other forums have - sorry I misunderstood there.
I think that would be easier ?

Being this is in my wikipedia user sandbox area, I don't know if permissions or access restrictions will crop up. From everything I can tell by google-searching, it appears others should be able to edit/access sandboxed material AND edit it.

One thing I will say is getting it formatted from DOC to WIKI was not an easy task. And getting used to the 'mediawiki' markup format takes a little getting used to as well. Most anything you want to do is google-searchable. But it can be frustrating when you just want to do something simple, and the online editor for the page just doesn't have what you are wanting to do as an easy clickable option.

There's still a lot of formatting that needs to be done to get it consistent. And there's a formal Wiki format that a published page must adhere to before it can be published on wikipedia. I'm pretty confident this doesn't meet those standards as I don't know what those standards are. But if this is something that is to be published at some point, it'll need to conform...

Oh - I was thinking of a wiki entry in this forum, which quite a few other forums have - sorry I misunderstood there.
I think that would be easier ?

Have you had a chance to read it over? I've been busy so I haven't been able to do any more mods to it, but that's probably a good thing particularly if there's things that need correcting or re-organizing.

had a weird problem with layout in that Word doc, so put it back to text for now....

here is v4 - didn't get to end, but reordered and adjusted to try to improve flow. Tried to remove tech terms where possible, and redundant bits.
Incorporated updates from cgrey, and removed and reworked the binary counting explanation - I hope it's better than before...

The Primer is a nice start. You have explained how the processor works, what sub routines and interrupts are and so forth.

The question I am left with is what does the main program do? I assume there is some sort of main loop that reads what the RPM is, what the MAF, VAF or MAP is inputting, what the throttle position is and then it goes and looks up how much fuel to use and how much spark advance and then loops around and does it again. What do the subroutines do? I assume there's something that detects movement of the TPS and sends a shot of fuel like and accelerator pump on a carburetor. Maybe there's one that looks up the EGO and makes an adjustment. Do you have plans to go into the code? Maybe show some examples of what that looks like?

Thanks for your response - Yes that doc is just a draft at the moment, and we were seeking comments on the those basics, and intended to add more.
I'm happy it all makes sense so far.

I can tell you something about the main program now, and yes, it needs to go in the Primer Doc.

Basically there is an intialisation routine, and then the code has what's called a "background task list" this consists of a list of subroutine pointers which are called in sequence, around and around in a big loop. This list of subroutines does all of the calculations, timer stuff, inputs(sensors), output calcs (fuel, idle valve, ignition advance, etc), and similar jobs. Most of the data structures (2 Dimension tables, 1D functions etc) are used in here for calibration of sensors, timing advance curves and that sort of stuff.

Then there are a set of Interrupt handlers, which handle those events which have to be dealt with *NOW* (e.g. ignition spark and injection events)
and keeping track of which cylinder engine is at. Note that stuff like rpm calculation, ignition timing advance etc are actually done in the background task list and smoothed/averaged to stop 'jitter'. These interrupts stop the background processing until they are done.

Then there's the self test, which is also typically done as as lists of subroutine pointers, more or less one subroutine per small test (like one for each sensor) and the outputs to give the results.

And that's pretty much it. Sounds simpler than it really is of course as there's several ways to do the jobs, and the boxes got more complex as emission regulations got tightened and better self tests and error reporting came in.