ZX Spectrum Cartridges

2456

Comments

  • edited June 2016
    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.

    Post edited by RMartins on
  • edited June 2016
    Required Routine will be something like this
    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.
    Post edited by RMartins on
  • edited June 2016
    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 ?
    Post edited by RMartins on
  • I got two very useful tips to justify this behaviour in this dedicated thread.

    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).
  • RMartins wrote: »
    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.
  • edited June 2016
    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.

    Post edited by RMartins on
  • I am making a game for 16K ROM (Interface II cartridge). It's a videoaventure.

    captura.png

    About 70% of game finished.
  • Really nice, but a "video" adventure in 16KB, is probably a very tight squeeze.

    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 ?
  • edited June 2016
    Al graphics, sprites, and map are implemented yet. There is enought space for:

    - 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).
    Post edited by radastan on
  • 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.
    http://iki.fi/sol | http://iki.fi/sol/speccy/ | https://github.com/jarikomppa/speccy
    http://goo.gl/q2j0NZ - make ZX Spectrum choose your own adventure games, no programming needed (release 3)
  • 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.

    Mark
    Sinclair FAQ Wiki
    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 :)
  • Sol_HSA wrote: »
    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.

    Mark

    Sinclair FAQ Wiki
    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 :)
  • edited June 2016
    Sol_HSA wrote: »
    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.
    Post edited by RMartins on
  • 1024MAK wrote: »
    ... And normally, no loading screen.

    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" :)

  • RMartins wrote: »
    1024MAK wrote: »
    ... And normally, no loading screen.

    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.

    Mark
    Sinclair FAQ Wiki
    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 :)
  • RMartins wrote: »
    Sol_HSA wrote: »
    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.
    http://iki.fi/sol | http://iki.fi/sol/speccy/ | https://github.com/jarikomppa/speccy
    http://goo.gl/q2j0NZ - make ZX Spectrum choose your own adventure games, no programming needed (release 3)
  • Sol_HSA wrote: »
    ...
    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.

    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.

  • edited June 2016
    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).
    LD HL,0
    LD A, bankNumber
    LD R,A
    LD R,A
    LD R,A
    LD A,(HL)
    

    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.
    Post edited by RMartins on
  • Just kick myself :D

    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.
  • edited June 2016
    Using an instruction that reads the next byte from the next instruction, would avoid the extra register.
    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 ?





    Post edited by RMartins on
  • RMartins wrote: »
    However, this requires huge logical device, since we need full 16 bit address decoding, and 8 bits (could be less) for page register.
    This was my assumption, yes. Use some cheap microcontroller with enough flash in it as the "rom", for instance.
    RMartins wrote: »
    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.
    RMartins wrote: »
    - 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.
    http://iki.fi/sol | http://iki.fi/sol/speccy/ | https://github.com/jarikomppa/speccy
    http://goo.gl/q2j0NZ - make ZX Spectrum choose your own adventure games, no programming needed (release 3)
  • RMartins wrote: »
    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)
  • Sol_HSA wrote: »
    RMartins wrote: »
    - 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?
  • AndyC wrote: »
    Sol_HSA wrote: »
    RMartins wrote: »
    - 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.
  • edited June 2016
    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.
    Post edited by RMartins on
  • edited June 2016
    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.
    Post edited by RMartins on
  • edited June 2016
    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.
    Post edited by RMartins on
  • I sell this one for 10 EUR

    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.
    Thanked by 1RMartins
  • edited June 2016
    I sell this one for 10 EUR

    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
    Post edited by RMartins on
  • 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.
Sign In or Register to comment.