Since we can control I and R register, it's possible to force some patterns, that are unlike to occur when running an application.
For example, we can FORCE the I and R register to become ZERO.
The R register is incremented after each instruction fetch.
So we can for example reset R twice in sequence, which will output a recognizable pattern, something like:
XOR A -=> A = 0
LD I, A -=> I = 0
LD R,A -=> I = 0, R = 0
LD R,A -=> I = 0, R = 1
LD A,(#0000) -=> I = 0, R = 0, Address = #0000
Or something similar to this ... I just have to work out the sequences, and check them with a data analyser connected to ZX expansion port, and selected a pattern that is easy to detect in hardware and control in software, without being a common pattern.
Read/write ROM addresses with I and R set to ZERO :)
Similar condition to power up/reset, except that in this case it's a fetch at #0000 instead of a read.
There is also a special bit (R7), that doesn't change, even after R register increment/overflow, which can be put to good use. :) (we can toggle it consecutively, to signal something)
The first problem to overcome, is to be able to distinguish which address values are refresh addresses, without depending on the values, or at most, depend on (A15, A14) = (0, 0) being consecutively identical, one for address and then refresh, or one for refresh followed by a read or write.
There is something else that we can infer, which is the fact that an instruction fetch is always 4 clock cycles, and a read or write is 3 clock cycles (not counting IO).
Since there is a dependent relation between CLOCK line and MREQ line behaviour, in these situations, instruction length can probably be inferred from MREQ, which will allow us to know, when address lines are in fact a fetch or a refresh address.
LD A,I
LD (SAVE_I),A
XOR A
LD L,A
LD H,A
DI
LD I, A -=> I = 0
CPL; #FF
LD R,A
XOR #80
LD R,A
LD A,(HL); Trigger Command (Bank Change)
LD A, (SAVE_I)
LD I,A
EI
Fundamental part is on these lines:
LD R,A ; toggles R7 Bit, and sets a specific value for R register, that will be checked on next instruction.
LD A,(HL) ; This instruction is a single byte, which allows cartridge logic to check refresh address, immediately followed by HL address (which is #0000).
So in terms of logic, I need to:
A - Detect R7 toggle to trigger detection circuit
This is extremely uncommon to happen, since changing R register might wrack your memory refresh operation and crash ZX. So this register is usually untouched. (still have to check which value, the ROM typically sets it to)
Current R value can also be saved, and reloaded after bank switching.
B - Once A condition is met/triggered, just have to check the next 3 addresses in sequence:
1 - LD A,(HL) instruction byte fetch (this can probably be ignored, instead of checked)
2 - Refresh address being something like 00000000 0bbbbbb in binary, where "bbbbbbb" is a bank command, whatever is defined later.
Rationale: 7 bits = 128 combinations.
Use one combination to disable banks.
Use 127 for bank switching. 127*16KB = 2032KB of ROM addressable.
3 - Read Memory address equal to #0000
Only when both conditions A and B trigger in strict sequence, will the logic operate on the ROM bank.
I have been checking emulator support & correctness of the R register behaviour.
From Z80 Manual
Memory Refresh (R) Register.
The Z80 CPU contains a memory refresh counter, enabling dynamic memories to be used with the same ease as static memories. Seven bits of this 8-bit register are automatically incremented after each instruction fetch. The eight bit remains as programmed, resulting from an LD R, A instruction.
The data in the refresh counter is sent out on the lower portion of the address bus along with a refresh control signal while the CPU is decoding and executing the fetched instruction.
From this, we know that:
1 - R register, bit 7 must not change without being explicitly changed by an instruction.
NOTE: Reset also sets R register to zero, hence also changes bit 7.
2 - R register is incremented just after any instruction fetch. Hence R register can be considered a simple counter (instruction counter). It makes sense to do sequential increments, since when refreshing memory we don't want to skip any addresses, in order to guarantee that all addresses are refreshed equally and in time.
NOTE: There is large margin on the refresh timing, so it doesn't matter if we hold it for some time.
3 - R register content is dumped on the address bus lower byte, just after fetch, i.e. just after being incremented.
Having said 3 points above, I find it strange, that at least 2 emulators (the 2 I tested on), increment twice for "LD A,R" instruction, and once for all other instructions that I tested.
NOTE: All Z80 repeat instructions, like LDIR, LDDR, CPIR, and CPDR are re-fetched for each iteration (while BC is not 0), hence each iteration implies an increment.
So is the double increment correct, due to some Z80 quirk ?
Or
Emulator authors, inherit most of this base emulation, and hence this bug propagated ?
Do any of you know any special reason for this behaviour ?
Resuming I found the following in "Z80 documented V0.91":
During every First machine cycle (beginning of an instruction or part of it - prefixes have their own M1 two), the memory refresh cycle is issued.
... Instructions without a prefix increase R by one. Instructions with an ED, CB, DD, FD prefix, increase R by two, and so do the DDCBxxxx and FDCBxxxx instructions (weird enough). Just a stray DD or FD increases the R by one. LD A,R and LD R,A access the R register after it is increased (by the instruction itself).
A - Detect R7 toggle to trigger detection circuit
This is extremely uncommon to happen, since changing R register might wrack your memory refresh operation and crash ZX. So this register is usually untouched. (still have to check which value, the ROM typically sets it to)
Current R value can also be saved, and reloaded after bank switching.
I'm not sure this assumption is true, plenty of Z80 coders relied on the behaviour of the R register (including knowing that you can safely "save" a bit value in the upper bit), Speedlock and many other protection systems were all built around it, as were a great many "random" number generators. If you weren't changing it continuously, the machine wasn't going to crash so nobody worried about it.
It's uncommon, to have I register set to #00, and toggling bit 7, R register in sequence.
I defined something that I think is uncommon enough, to not trigger it unintentionally in your code.
This is relevant, for your own code, or some code that uses this cartridge.
But if in any other case, this uncommon trigger sequence does happen, it will only affect this cartridge if it's plugged in, and it will only affect the software if it uses the Sinclair ROM (many don't).
You usually will not use this cartridge with other software, unless you are trying to fit existing software into a ROM, in which case you will have to make some conversion already, if it's larger than 16KB.
Hopefully, all 10 Sinclair originally released cartridges, will be portable without problems.
Actually 11,1 Kb used and only rest to do enemy's movement and "objects". There is no need to use decompression from ROM to RAM. The game works in a 16K ZX Spectrum without problem. And have animated tiles for map!
When finished I will see to publish it on cartridge, thanks for your offer. Game in TAP format would be free download too (it required 48K ZX Spectrum instead).
On bank switching logic - if there has to be some logic on the cartridge anyway, wouldn't an easier solution be just reserving one address in the ROM (probably the last) to be the bank switch address; any access to said address would switch banks?
That wouldn't even necessarily waste the byte, as it would return the byte contents before bank switch =)
For a more random access a range of bytes would need to be reserved, 64 bytes would give a meg(-4k of 64 bytes per bank), which would still be usable.
This approach would have the benefit of being simpler and more robust, at the expense of making the data in those bank switch bytes harder to access.
Actually the 16k bytes cart limit is not as bad as it sounds. The 16k is ROM and unlike a tape for a 16k Spectrum, you do not have the screen area eating up a large chunk of the memory for code. Yes, of course the screen still takes up the area in the RAM, but with a full 16k of code in ROM, that is not an issue.
The limit of 16k bytes does however mean that you have to be creative and careful with graphics data. And normally, no loading screen.
! Standby alert !
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb!
Looking forward to summer in Somerset later in the year :)
On bank switching logic - if there has to be some logic on the cartridge anyway, wouldn't an easier solution be just reserving one address in the ROM (probably the last) to be the bank switch address; any access to said address would switch banks?
The trouble is, how does the circuitry on the cart tell if it is a write or a read?
If the CPU writes data via the data bus, a write strobe is needed to tell the device on the cart to catch/latch this data. But no such signal is available on the cartridge connector.
! Standby alert !
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb!
Looking forward to summer in Somerset later in the year :)
On bank switching logic - if there has to be some logic on the cartridge anyway, wouldn't an easier solution be just reserving one address in the ROM (probably the last) to be the bank switch address; any access to said address would switch banks?
And how would you inform the Cartridge of which bank to select ?
I believe that what you are missing, is the fact that there is no way to determine, if it's a read or a write.
Cartridge slot has a limited number of connections, and those valuable lines (READ and WRITE) are not present unfortunately.
If you try to write to ROM, what happens is a DATA bus collision, i.e. CPU is forcing data lines at the same time that ROM is forcing the address data output.
So there is no way to get any usable data from DATA bus lines, from the Cartridge perspective.
So the only means of actual communication we do have, is through ADDRESS bus lines, which cartridge can see without problems, since it's always a read from the Cartridge perspective.
But then you have to devise a one way communication CPU --> Cartridge, that includes some kind of in band signaling, to trigger the latching of the info, by Cartridge.
That's what I did using the refresh address, by controlling I and R register.
P.S.
Your suggested solution for bank switching, although possible, is not very portable for pré-existing ROMS or any other software, since you have to make sure that those specific addresses are never accessed, except in those specific situations.
It also doesn't allow you to switch the ROM OFF, so that ZX ROM or IF1 ROM can be used.
Since ROM looks like an instant load, in most situations a "Loading Screen" is not required, since we don't need to entertain the user while "loading" :)
Since ROM looks like an instant load, in most situations a "Loading Screen" is not required, since we don't need to entertain the user while "loading" :)
Yes, but some "loading" screens are nice to look at, and others sometimes tell you the keys/controls or how to play the game :)
These can still be done, but you are now eating into the 16k bytes of ROM space.
! Standby alert !
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb!
Looking forward to summer in Somerset later in the year :)
On bank switching logic - if there has to be some logic on the cartridge anyway, wouldn't an easier solution be just reserving one address in the ROM (probably the last) to be the bank switch address; any access to said address would switch banks?
And how would you inform the Cartridge of which bank to select ?
No read/write signal needed.
In the one byte case, each access would cycle through the pages. In N byte case, each byte would switch to a specific page. The cycle-through-pages case would require a lot of bookkeeping (and cause bugs) on software side, but would only "waste" one byte.
Or, one could say that the one address is write-only and then use the data to pick the page.
None of these possibilities is timing-critical, making them more robust, and easy to use from software.
In the one byte case, each access would cycle through the pages. In N byte case, each byte would switch to a specific page. The cycle-through-pages case would require a lot of bookkeeping (and cause bugs) on software side, but would only "waste" one byte.
Or, one could say that the one address is write-only and then use the data to pick the page.
None of these possibilities is timing-critical, making them more robust, and easy to use from software.
I understand your idea, and it seems simple enough, which is good, but as some drawbacks.
Assuming, we reserve an address for write only, lets assume #03FF (the end of 16KB block), and that we tri-state cartridge output for this address, to prevent data bus collision (requires looking at the full 16 bit address, to match just one address).
Then a write by CPU, like you suggest, would allow us to read DATA bus value, and cartridege would switch to the correct page.
Great!
However, this requires huge logical device, since we need full 16 bit address decoding, and 8 bits (could be less) for page register.
But what happens:
- if someone reads that address ?
It would select a random page, since no one is driving the DATA bus on read (NOTE: cartridge should be driving it, but we are trying to read it, so we read garbage/random value).
NOTE 1: There are no pull-up or pull-down on data bus.
NOTE 2: ROM is usually used for RANDOM input, for beeper audio for example, making it very likely to have regular reads by many software.
- If (I + R) refresh address, match our special address ?
Random page selection again.
NOTE: Even if we use weak internal pull downs, to select page #00, a random read would switch the bank unwillingly.
So, in fact, although it seems an easier solution, it adds complexity by requiring more (data) lines to look at (current solution, only uses address lines, and doesn't need all of them), and has some problems like mentioned above.
We need some kind of trigger that is not common, plus some address at the same time, to validate the trigger, and avoid any common cases.
I have been thinking how to implement my solution, by looking at the CPU fetch and read cycles.
And I think that maybe I found a simpler solution, would be something as simple as:
LD A, bankNumber
LD R,A
LD R,A
LD R,A
This will output a refresh address sequence like:
X, X+1, A, A+1, A, A+1
Where X is unkown, and A is the contents of register A (bankNumber).
A sequence like this is certainly not common, since it's not useful to repeat this instruction 3 times in sequence.
Note however that this data appears interleaved with instruction fetch (M1) cycles.
LD R,A is an extended instruction, so it uses two M1 cycles in sequence (4T+5T).
So what actually is seen on the address bus is:
ADDR, X, ADDR+1, X+1, ADDR+2, A, ADDR+3, A+1, ADDR+4, A, ADDR+5, A+1
For this to work, I need to be sure that what I'm looking at is the REFRESH address, and not a common address from an instruction fetch, a read or a write, because then this sequence might be valid programming.
The problem to detect a REFRESH cycle is that the only difference between a refresh cycle and the other cycles (M1/fetch, read or write) with the available data lines (MREQ), is just that it has a smaller duration (1T), instead of 1.5T (fetch) or 2.5T (read or write).
Another approach could be to use an extra instruction, to give a second trigger (like before).
NOTE: LD A,(HL), is a single byte instruction, with an M1 and REFRESH cycle (4T) and a READ cycle (3T).
Then we would get the following on the address bus.
ADDR, X, ADDR+1, X+1, ADDR+2, A, ADDR+3, A+1, ADDR+4, A, ADDR+5, A+1, ADDR+6, A+2, HL
It's not that easy to detect this behaviour after all :(
Another idea I had, was to: place an instruction, in a specific location in RAM or ROM, so that all consecutive bus addresses would be equal
example: fetching an instruction that reads, like LD A,(HL)
ADDR, REFRESH, READ
if ADDR = REFRESH = READ address , all equal, we would select the bank given by a sub part of the address bits.
This has the advantage of being very easy to detect (exactly consecutive addresses, signaled consecutively by MREQ), and from what I believe, impossible to happen anywhere else in the address space.
Problem: is that these positions will be very specific, and a bit difficult to manage.
A possible solution to reduce this problem, would be to consider only REFRESH and READ address, but then it can happen in regular code, to eventually access the same memory that is being read.Not very common, but could happen in a loop for example.
Another possibility, could be ADDR = REFRESH, but than can also happen in regular code.
Two immediately consecutive "targetAddress", followed by BankCommand (A+2-2 = Banknumber)
However, in some weird cases this could happen in self modifying code.
But it's uncommon to modify the next instruction, since you usually modify the operand part an not the instruction per se, except when you have nops, and replace them with single byte instructions, exactly before, executing it.
But what happens:
- if someone reads that address ?
It would select a random page, since no one is driving the DATA bus on read (NOTE: cartridge should be driving it, but we are trying to read it, so we read garbage/random value).
NOTE 1: There are no pull-up or pull-down on data bus.
NOTE 2: ROM is usually used for RANDOM input, for beeper audio for example, making it very likely to have regular reads by many software.
These are software limitations. The software written for those cardridges would have to deal with said limitations.
- If (I + R) refresh address, match our special address ?
Random page selection again.
NOTE: Even if we use weak internal pull downs, to select page #00, a random read would switch the bank unwillingly.
Now this is something I really didn't think about, and pretty much kills my idea. That would require the cardridge to somehow detect the refresh reads, and I'm not sure if that's possible with the connections it has.
But it's uncommon to modify the next instruction, since you usually modify the operand part an not the instruction per se, except when you have nops, and replace them with single byte instructions, exactly before, executing it.
Any thoughts against this ?
Writing out full sequences of instructions is not uncommon in dynamic sprite scaling routines, although that being the "next" instruction is probably likely (it would only work in very specific circumstances and is unlikely to be the efficient method)
- If (I + R) refresh address, match our special address ?
Random page selection again.
NOTE: Even if we use weak internal pull downs, to select page #00, a random read would switch the bank unwillingly.
Now this is something I really didn't think about, and pretty much kills my idea. That would require the cardridge to somehow detect the refresh reads, and I'm not sure if that's possible with the connections it has.
Could it not be avoided by simply not setting I to the appropriate value, except during paging operations? Or am I missing something?
- If (I + R) refresh address, match our special address ?
Random page selection again.
NOTE: Even if we use weak internal pull downs, to select page #00, a random read would switch the bank unwillingly.
Now this is something I really didn't think about, and pretty much kills my idea. That would require the cardridge to somehow detect the refresh reads, and I'm not sure if that's possible with the connections it has.
Could it not be avoided by simply not setting I to the appropriate value, except during paging operations? Or am I missing something?
Yes you can, however, lots of software, use and abuse the I register, when exceptions are disabled, or when in IM mode 1, the ZX ROM default, so I register might end up with a value that combined with R (which is typically looping) will match the special address,
To trigger on a simple fixed address ist just not secure enough.
Remember, that a Cartridge can be use for other cases then just running your software.
Examples:
- Replace you Spectrum ROM, so that you can use another implementation or fixed ROM, (gosh wonderful, for example), but still run your everyday software over it.
- Adding extra features to your system, print driver, Serial protocol (IF1 has this), hardware driver, etc ...
- Re-issue previous ROM formats, like the 10 Sinclair Cartridges, or other software already created.
and the list goes on ...
So it's useful that paging mechanism is secure and not unintentionally.triggerable with typiical code structures.
However, a "silver bullet" probably doesn't exist, specially with such a limited set of control signals.
I'm just trying to find the safest way, I can think of, while not being too complex to implement, so that it can squeeze it to fit in a standard sized cartridge case.
It's also my intention, to be able to use a single cartridge, to contain multiple 16K cartridge software.
First ROM bank, or standard bank 0, can be used to provide a menu, to select one of the other banks, and "re-boot". i.e. re-start Z80 (but not use RESET) into a similar state to a power up (default register/state values), but with selected bank already paged in, and then do a jump to #0000.
Current idea, of checking sequential addresses, seems simple, but has a huge drawback, which is:
it requires a large register (a set of flip-flops), to keep last address, so that it can be compared with current bus address.
This is a big no-no for me, since I'm looking to use a GAL20V10 or similar device as logic implementation.
I need something simpler, so it will take me back to verifying a specific address like #0000, since it's easy to spot, with a logic OR.
Writing that last sentence, made me have another idea.
Instead of hacking the address bus, to send info to the cartridge, we can use an address not in the cartridge address range, so that we don't take over the data bus. Making it available for data input :)
Using an address like #FFFF (easy to detect, a simple AND will do), we can try and see what is returned on data bus, plus some special trigger setup, and we have a way to send valid input into cartridge.
Since it's RAM, any willing application, can always READ/WRITE to that MAGIC address, even if contains code, since after page bank operation is done, the original value can be restored. This is why we need an extra trigger to protect the Magic/Banking Address.
PS. It's unfortunate, that we don't have access to IORQ line, or it would be a lot easier, by using a specific port, although it would be more tricky to remain compatible with not fully decoded logic interfaces (joystick interfaces and similar).
So back to the drawing board :)
NOTE: I have connected a data-analyser, to some signall lines (still need to do some spy board, so that it's easy to connect to ROM socket signals), so that I can check timings implemented by CPU, in a real sample.
It has 512K, so you can put up to 32 cartridge games/ROMs or 10 snapshots (only 48K games). Sources, Eagle schematics & PCBs are available, so you can do it yourself.
It has 512K, so you can put up to 32 cartridge games/ROMs or 10 snapshots (only 48K games). Sources, Eagle schematics & PCBs are available, so you can do it yourself.
Olá António,
Thank you for your input, and also for making it freely available.
I 'll see if there is any documentation on engineering part of it.
I know that there a few versions out there, this I believe is the 4th I know about.
However, what I'm trying to achieve, is dynamic configuration and eventually in situ reprogramability, that other have already accomplished in several distinct ways, but, and this is most important, still fit in a regular Sinclair standard sized cartridge case.
EDIT: Lots of stuff in those files, and even some code for menu generation and some ASM, cool :D
The PCB is compatible with standard Sinclair IF2. But I did't try if fit in a cartridge case, probably not. But you can change the PCB design to fit in the case.
Comments
For example, we can FORCE the I and R register to become ZERO.
The R register is incremented after each instruction fetch.
So we can for example reset R twice in sequence, which will output a recognizable pattern, something like:
XOR A -=> A = 0
LD I, A -=> I = 0
LD R,A -=> I = 0, R = 0
LD R,A -=> I = 0, R = 1
LD A,(#0000) -=> I = 0, R = 0, Address = #0000
Or something similar to this ... I just have to work out the sequences, and check them with a data analyser connected to ZX expansion port, and selected a pattern that is easy to detect in hardware and control in software, without being a common pattern.
Read/write ROM addresses with I and R set to ZERO :)
Similar condition to power up/reset, except that in this case it's a fetch at #0000 instead of a read.
There is also a special bit (R7), that doesn't change, even after R register increment/overflow, which can be put to good use. :) (we can toggle it consecutively, to signal something)
The first problem to overcome, is to be able to distinguish which address values are refresh addresses, without depending on the values, or at most, depend on (A15, A14) = (0, 0) being consecutively identical, one for address and then refresh, or one for refresh followed by a read or write.
There is something else that we can infer, which is the fact that an instruction fetch is always 4 clock cycles, and a read or write is 3 clock cycles (not counting IO).
Since there is a dependent relation between CLOCK line and MREQ line behaviour, in these situations, instruction length can probably be inferred from MREQ, which will allow us to know, when address lines are in fact a fetch or a refresh address.
Fundamental part is on these lines:
So in terms of logic, I need to:
A - Detect R7 toggle to trigger detection circuit
This is extremely uncommon to happen, since changing R register might wrack your memory refresh operation and crash ZX. So this register is usually untouched. (still have to check which value, the ROM typically sets it to)
Current R value can also be saved, and reloaded after bank switching.
B - Once A condition is met/triggered, just have to check the next 3 addresses in sequence:
1 - LD A,(HL) instruction byte fetch (this can probably be ignored, instead of checked)
2 - Refresh address being something like 00000000 0bbbbbb in binary, where "bbbbbbb" is a bank command, whatever is defined later.
Rationale: 7 bits = 128 combinations.
Use one combination to disable banks.
Use 127 for bank switching. 127*16KB = 2032KB of ROM addressable.
3 - Read Memory address equal to #0000
Only when both conditions A and B trigger in strict sequence, will the logic operate on the ROM bank.
From Z80 Manual
From this, we know that:
1 - R register, bit 7 must not change without being explicitly changed by an instruction.
NOTE: Reset also sets R register to zero, hence also changes bit 7.
2 - R register is incremented just after any instruction fetch. Hence R register can be considered a simple counter (instruction counter). It makes sense to do sequential increments, since when refreshing memory we don't want to skip any addresses, in order to guarantee that all addresses are refreshed equally and in time.
NOTE: There is large margin on the refresh timing, so it doesn't matter if we hold it for some time.
3 - R register content is dumped on the address bus lower byte, just after fetch, i.e. just after being incremented.
Having said 3 points above, I find it strange, that at least 2 emulators (the 2 I tested on), increment twice for "LD A,R" instruction, and once for all other instructions that I tested.
NOTE: All Z80 repeat instructions, like LDIR, LDDR, CPIR, and CPDR are re-fetched for each iteration (while BC is not 0), hence each iteration implies an increment.
So is the double increment correct, due to some Z80 quirk ?
Or
Emulator authors, inherit most of this base emulation, and hence this bug propagated ?
Do any of you know any special reason for this behaviour ?
Resuming I found the following in "Z80 documented V0.91":
I'm not sure this assumption is true, plenty of Z80 coders relied on the behaviour of the R register (including knowing that you can safely "save" a bit value in the upper bit), Speedlock and many other protection systems were all built around it, as were a great many "random" number generators. If you weren't changing it continuously, the machine wasn't going to crash so nobody worried about it.
I defined something that I think is uncommon enough, to not trigger it unintentionally in your code.
This is relevant, for your own code, or some code that uses this cartridge.
But if in any other case, this uncommon trigger sequence does happen, it will only affect this cartridge if it's plugged in, and it will only affect the software if it uses the Sinclair ROM (many don't).
You usually will not use this cartridge with other software, unless you are trying to fit existing software into a ROM, in which case you will have to make some conversion already, if it's larger than 16KB.
Hopefully, all 10 Sinclair originally released cartridges, will be portable without problems.
About 70% of game finished.
You should probably make good use of compression, and decompress from ROM to RAM, in chunks to make maximum usage of 16KB ROM.
Would you be interested in a Cartridge, when it's done ?
- Map of 64 screens
- 47 tiles
- 11 sprites
- Routines
Actually 11,1 Kb used and only rest to do enemy's movement and "objects". There is no need to use decompression from ROM to RAM. The game works in a 16K ZX Spectrum without problem. And have animated tiles for map!
When finished I will see to publish it on cartridge, thanks for your offer. Game in TAP format would be free download too (it required 48K ZX Spectrum instead).
That wouldn't even necessarily waste the byte, as it would return the byte contents before bank switch =)
For a more random access a range of bytes would need to be reserved, 64 bytes would give a meg(-4k of 64 bytes per bank), which would still be usable.
This approach would have the benefit of being simpler and more robust, at the expense of making the data in those bank switch bytes harder to access.
http://goo.gl/q2j0NZ - make ZX Spectrum choose your own adventure games, no programming needed (release 3)
The limit of 16k bytes does however mean that you have to be creative and careful with graphics data. And normally, no loading screen.
Mark
Repair Guides. Spanish Hardware site.
WoS - can't download? Info here...
former Meulie Spectrum Archive but no longer available :-(
Spectranet: the TNFS directory thread
! Standby alert !
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb!
Looking forward to summer in Somerset later in the year :)
If the CPU writes data via the data bus, a write strobe is needed to tell the device on the cart to catch/latch this data. But no such signal is available on the cartridge connector.
Mark
Repair Guides. Spanish Hardware site.
WoS - can't download? Info here...
former Meulie Spectrum Archive but no longer available :-(
Spectranet: the TNFS directory thread
! Standby alert !
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb!
Looking forward to summer in Somerset later in the year :)
And how would you inform the Cartridge of which bank to select ?
I believe that what you are missing, is the fact that there is no way to determine, if it's a read or a write.
Cartridge slot has a limited number of connections, and those valuable lines (READ and WRITE) are not present unfortunately.
If you try to write to ROM, what happens is a DATA bus collision, i.e. CPU is forcing data lines at the same time that ROM is forcing the address data output.
So there is no way to get any usable data from DATA bus lines, from the Cartridge perspective.
So the only means of actual communication we do have, is through ADDRESS bus lines, which cartridge can see without problems, since it's always a read from the Cartridge perspective.
But then you have to devise a one way communication CPU --> Cartridge, that includes some kind of in band signaling, to trigger the latching of the info, by Cartridge.
That's what I did using the refresh address, by controlling I and R register.
P.S.
Your suggested solution for bank switching, although possible, is not very portable for pré-existing ROMS or any other software, since you have to make sure that those specific addresses are never accessed, except in those specific situations.
It also doesn't allow you to switch the ROM OFF, so that ZX ROM or IF1 ROM can be used.
Since ROM looks like an instant load, in most situations a "Loading Screen" is not required, since we don't need to entertain the user while "loading" :)
These can still be done, but you are now eating into the 16k bytes of ROM space.
Mark
Repair Guides. Spanish Hardware site.
WoS - can't download? Info here...
former Meulie Spectrum Archive but no longer available :-(
Spectranet: the TNFS directory thread
! Standby alert !
“There are four lights!”
Step up to red alert. Sir, are you absolutely sure? It does mean changing the bulb!
Looking forward to summer in Somerset later in the year :)
No read/write signal needed.
In the one byte case, each access would cycle through the pages. In N byte case, each byte would switch to a specific page. The cycle-through-pages case would require a lot of bookkeeping (and cause bugs) on software side, but would only "waste" one byte.
Or, one could say that the one address is write-only and then use the data to pick the page.
None of these possibilities is timing-critical, making them more robust, and easy to use from software.
http://goo.gl/q2j0NZ - make ZX Spectrum choose your own adventure games, no programming needed (release 3)
I understand your idea, and it seems simple enough, which is good, but as some drawbacks.
Assuming, we reserve an address for write only, lets assume #03FF (the end of 16KB block), and that we tri-state cartridge output for this address, to prevent data bus collision (requires looking at the full 16 bit address, to match just one address).
Then a write by CPU, like you suggest, would allow us to read DATA bus value, and cartridege would switch to the correct page.
Great!
However, this requires huge logical device, since we need full 16 bit address decoding, and 8 bits (could be less) for page register.
But what happens:
- if someone reads that address ?
It would select a random page, since no one is driving the DATA bus on read (NOTE: cartridge should be driving it, but we are trying to read it, so we read garbage/random value).
NOTE 1: There are no pull-up or pull-down on data bus.
NOTE 2: ROM is usually used for RANDOM input, for beeper audio for example, making it very likely to have regular reads by many software.
- If (I + R) refresh address, match our special address ?
Random page selection again.
NOTE: Even if we use weak internal pull downs, to select page #00, a random read would switch the bank unwillingly.
So, in fact, although it seems an easier solution, it adds complexity by requiring more (data) lines to look at (current solution, only uses address lines, and doesn't need all of them), and has some problems like mentioned above.
We need some kind of trigger that is not common, plus some address at the same time, to validate the trigger, and avoid any common cases.
And I think that maybe I found a simpler solution, would be something as simple as:
This will output a refresh address sequence like:
X, X+1, A, A+1, A, A+1
Where X is unkown, and A is the contents of register A (bankNumber).
A sequence like this is certainly not common, since it's not useful to repeat this instruction 3 times in sequence.
Note however that this data appears interleaved with instruction fetch (M1) cycles.
LD R,A is an extended instruction, so it uses two M1 cycles in sequence (4T+5T).
So what actually is seen on the address bus is:
ADDR, X, ADDR+1, X+1, ADDR+2, A, ADDR+3, A+1, ADDR+4, A, ADDR+5, A+1
For this to work, I need to be sure that what I'm looking at is the REFRESH address, and not a common address from an instruction fetch, a read or a write, because then this sequence might be valid programming.
The problem to detect a REFRESH cycle is that the only difference between a refresh cycle and the other cycles (M1/fetch, read or write) with the available data lines (MREQ), is just that it has a smaller duration (1T), instead of 1.5T (fetch) or 2.5T (read or write).
Another approach could be to use an extra instruction, to give a second trigger (like before).
NOTE: LD A,(HL), is a single byte instruction, with an M1 and REFRESH cycle (4T) and a READ cycle (3T).
Then we would get the following on the address bus.
ADDR, X, ADDR+1, X+1, ADDR+2, A, ADDR+3, A+1, ADDR+4, A, ADDR+5, A+1, ADDR+6, A+2, HL
It's not that easy to detect this behaviour after all :(
Another idea I had, was to:
place an instruction, in a specific location in RAM or ROM, so that all consecutive bus addresses would be equal
example: fetching an instruction that reads, like LD A,(HL)
ADDR, REFRESH, READ
if ADDR = REFRESH = READ address , all equal, we would select the bank given by a sub part of the address bits.
This has the advantage of being very easy to detect (exactly consecutive addresses, signaled consecutively by MREQ), and from what I believe, impossible to happen anywhere else in the address space.
Problem: is that these positions will be very specific, and a bit difficult to manage.
A possible solution to reduce this problem, would be to consider only REFRESH and READ address, but then it can happen in regular code, to eventually access the same memory that is being read.Not very common, but could happen in a loop for example.
Another possibility, could be ADDR = REFRESH, but than can also happen in regular code.
What if just ADDR = READ, and use REFRESH as bank command...
In fact, just use an instruction that reads it self ...
Can this happen in regular code ?
Examples ?
However, this requires to have extra register, to keep the previous REFRESH, since detection only triggers on READ cycle.
LD HL, targetAddr LD A,BankNumber-2 LD R,A LD A,(HL) targetAddress NOP.It would output the following addresses in sequence, when LD A,(HL) and NOP:
targetAddress-1, I/A+1, targetAddress, targetAddress, I/A+2
Two immediately consecutive "targetAddress", followed by BankCommand (A+2-2 = Banknumber)
However, in some weird cases this could happen in self modifying code.
But it's uncommon to modify the next instruction, since you usually modify the operand part an not the instruction per se, except when you have nops, and replace them with single byte instructions, exactly before, executing it.
Any thoughts against this ?
These are software limitations. The software written for those cardridges would have to deal with said limitations.
Now this is something I really didn't think about, and pretty much kills my idea. That would require the cardridge to somehow detect the refresh reads, and I'm not sure if that's possible with the connections it has.
http://goo.gl/q2j0NZ - make ZX Spectrum choose your own adventure games, no programming needed (release 3)
Writing out full sequences of instructions is not uncommon in dynamic sprite scaling routines, although that being the "next" instruction is probably likely (it would only work in very specific circumstances and is unlikely to be the efficient method)
Yes you can, however, lots of software, use and abuse the I register, when exceptions are disabled, or when in IM mode 1, the ZX ROM default, so I register might end up with a value that combined with R (which is typically looping) will match the special address,
To trigger on a simple fixed address ist just not secure enough.
Examples:
- Replace you Spectrum ROM, so that you can use another implementation or fixed ROM, (gosh wonderful, for example), but still run your everyday software over it.
- Adding extra features to your system, print driver, Serial protocol (IF1 has this), hardware driver, etc ...
- Re-issue previous ROM formats, like the 10 Sinclair Cartridges, or other software already created.
and the list goes on ...
So it's useful that paging mechanism is secure and not unintentionally.triggerable with typiical code structures.
However, a "silver bullet" probably doesn't exist, specially with such a limited set of control signals.
I'm just trying to find the safest way, I can think of, while not being too complex to implement, so that it can squeeze it to fit in a standard sized cartridge case.
First ROM bank, or standard bank 0, can be used to provide a menu, to select one of the other banks, and "re-boot". i.e. re-start Z80 (but not use RESET) into a similar state to a power up (default register/state values), but with selected bank already paged in, and then do a jump to #0000.
it requires a large register (a set of flip-flops), to keep last address, so that it can be compared with current bus address.
This is a big no-no for me, since I'm looking to use a GAL20V10 or similar device as logic implementation.
I need something simpler, so it will take me back to verifying a specific address like #0000, since it's easy to spot, with a logic OR.
Writing that last sentence, made me have another idea.
Instead of hacking the address bus, to send info to the cartridge, we can use an address not in the cartridge address range, so that we don't take over the data bus. Making it available for data input :)
Using an address like #FFFF (easy to detect, a simple AND will do), we can try and see what is returned on data bus, plus some special trigger setup, and we have a way to send valid input into cartridge.
Since it's RAM, any willing application, can always READ/WRITE to that MAGIC address, even if contains code, since after page bank operation is done, the original value can be restored. This is why we need an extra trigger to protect the Magic/Banking Address.
PS. It's unfortunate, that we don't have access to IORQ line, or it would be a lot easier, by using a specific port, although it would be more tricky to remain compatible with not fully decoded logic interfaces (joystick interfaces and similar).
So back to the drawing board :)
NOTE: I have connected a data-analyser, to some signall lines (still need to do some spy board, so that it's easy to connect to ROM socket signals), so that I can check timings implemented by CPU, in a real sample.
http://www.va-de-retro.com/foros/viewtopic.php?f=50&t=4553
It has 512K, so you can put up to 32 cartridge games/ROMs or 10 snapshots (only 48K games). Sources, Eagle schematics & PCBs are available, so you can do it yourself.
Olá António,
Thank you for your input, and also for making it freely available.
I 'll see if there is any documentation on engineering part of it.
I know that there a few versions out there, this I believe is the 4th I know about.
However, what I'm trying to achieve, is dynamic configuration and eventually in situ reprogramability, that other have already accomplished in several distinct ways, but, and this is most important, still fit in a regular Sinclair standard sized cartridge case.
EDIT: Lots of stuff in those files, and even some code for menu generation and some ASM, cool :D