MEGA65 / mega65-core

MEGA65 FPGA core
Other
240 stars 85 forks source link

CPU: Implement more Q opcodes #645

Open lydon42 opened 1 year ago

lydon42 commented 1 year ago

Implement some more Q opcodes:

Wishlist:

lydon42 commented 1 year ago

Thoughts: 42 42 42 for NEGQ could be problematic, perhaps we need a different opcode here. ZERQ Q should just 0 all four registers. So no option to load an immediate value to Q. But that would also mean that this should be some 8bit opcode that takes no parameters.

lydon42 commented 1 year ago

The T.. opcodes are pretty useless for Q ops, so they could be a perfect candidate for these special needs functions.

lydon42 commented 1 year ago

Paul did something that implements NEG NEG NEG as NEGQ 6725b354e70a7ee519e1ebc06d788480443ff2f2 2f353056c7b74a3e356369d46affce9b917dc96a

dansanderson commented 2 weeks ago

I'd love a DEQ. Symmetrical with INQ, including the non-implied addressing modes. Currently only possible with SBCQ (or a series of SBC). I use 32-bit countdowns sometimes.

I wonder if reserved Q opcodes should BRK, i.e. NEG NEG followed by anything that isn't already a documented instruction. This would discourage accidental dependencies on undocumented behavior.

If NEGQ is complete and in a stable release, we should document it!

lgblgblgb commented 2 weeks ago

Also, interesting to think about to have SBCQ #$nn and things like that, having only a single byte immediate value (since IIRC we talked about already that we don't like to have a 32 bit immediate value too much in opcodes) which is meant to be zero (or sign?) extended to 32 bit to do the math with the Q register. Not sure how useful it is ... ZERQ is very handy and can be a short form for the task to set A/X/Y/Z to all zero, even if no further Q opcodes are used at all.

dansanderson commented 2 weeks ago

Some thoughts:

TMQ / TQM

RTSM

This makes me wonder about an atomic MAP-with-JMP operation that uses the stack. Consider: RTSM is an instruction that pulls a two-byte PC value, pulls an eight-byte MAP value, sets the MAP, then sets the PC.

Currently, it is impractical to change the map for the region containing the code that is changing the map (where the PC is). Programs that use more than one memory map need a "dispatch" area of the code in a reserved region for this purpose. With RTSM, any code can push a new MAP, push a new PC, then RTSM to change MAP and PC at the same time, without pulling the rug out from under itself.

map_a: !32 $xxxxxxxx, $xxxxxxxx
map_b: !32 $xxxxxxxx, $xxxxxxxx

* = $2000
; With MAP = map_a...
  ldq map_b
  phq
  phw #routine
  rtsm

* = $2000
; With MAP = map_b...
routine:
  ...

Doing it via an RTS-like instruction also allows for doing this with subroutines. This could be accomplished with just PHM and RTSM:

* = $2000
; With MAP = map_a...
  phm
  phw #returnlabel
  ldq map_b
  phq
  phw #subroutine
  rtsm
returnlabel:
  ...

* = $2000
; With MAP = map_b...
subroutine:
  ...
  rtsm

One could imagine a "JSRM" that does this more simply, but it's not clear how it should accept the MAP argument. 10 bytes of immediate? Pulls the new MAP from the stack, and takes the PC immediate? Combinations of taking either argument as a pointer (...)? Note that the subroutine being called would have to know it was called this way and must return with RTSM, not just RTS, but maybe that's acceptable.

* = $2000
; With MAP = map_a...
  ldq map_b
  phq
  jsrm subroutine_via_jsrm
  ...

; Or maybe...
  jsrm (map_b), subroutine_via_jsrm

* = $2000
; With MAP = map_b...
subroutine_via_jsrm:
  jsr local_subroutine
  rtsm

local_subroutine:
  ...
  rts

PHQ / PLQ

ZEROQ

lydon42 commented 2 weeks ago

ZERQ (five is to long) with memory access is not in the sense of the cpu I think. You rather would ZERQ/LDQ #0 and then do the STQ to memory, which is already there. I can't think of an other 6502 based opcode that would store a predefined value into memory. That would always come from registers.

dansanderson commented 2 weeks ago

I think ZERQ to set CPU registers to a predefined value is similarly weird. I suggested ZERQ could write directly to memory because INQ already does this: it does not affect Q in those addressing modes. So if ZERQ makes sense for Q, I think it would make sense in those other modes. If it doesn't make sense for memory, then I don't think it makes sense for Q.

In general, it would be useful to have just a few more ways to manipulate 32-bit values in memory without nuking all of the CPU registers. If I want to update a 32-bit value in memory but I have a CPU register in use, I either have to do it the long way and funnel each byte through a single register, or I have to push my active CPU registers and use Q. The existing INQ register is one way to modify a 32-bit value in memory without disturbing CPU registers. DEQ and ZERQ ZP/Addr would be two new ways that I think pair well with INQ.

lydon42 commented 2 weeks ago

Hmmm... Isn't that the same?

LDQ #0
STQ (zp)/$aaaa/$aa
DEQ (zp)/$aaaa/$aa

That way we don't need to add a set zero that we currently don't have.

dansanderson commented 2 weeks ago

Yes, I said this. ZEROQ wasn't my idea. :) ZEROQ Q = LDQ #0, or LDA #0 : TAX : TAY : TAZ. ZEROQ Addr = LDQ #0 : STQ Addr.

I also said: it would be useful to have just a few more ways to manipulate 32-bit values in memory without nuking all of the CPU registers. Right now, any use of Q requires:

  1. Pushing and pulling the accumulator and index registers on the stack
  2. Stashing the accumulator and index registers on the ZP
  3. Only using Q at moments when none of the CPU registers are assigned a purpose
  4. Avoiding Q entirely

That makes Q difficult to use in tight loops and call patterns, such as a 32-bit loop index. If ZEROQ is useful at all, it would be useful to give it addressing modes symmetric with INQ and DEQ. If there's an objection to an instruction encapsulating the number zero, then ZEROQ Q is similarly objectionable.

For what it's worth, LDQ #Imm8 also feels weird for the same reason. I understand the operand width constraint, and maybe it's a bit more useful than ZEROQ Q : LDA #Imm8, but it feels like it encapsulates zeroes as much as ZEROQ does. Sign extension is an interesting idea but with no way to disable it I think the semantics are weird, as if LDQ #Imm8 is primarily intended for setting a 32-bit register to a value in -128 to 127, and as a side effect can fill Q with zeroes or ones.

dansanderson commented 2 weeks ago

I have moved my JMPM/JSRM/RTSM idea to a separate feature request. https://github.com/MEGA65/mega65-core/issues/819 I like it and don't want it to get lost in discussion of other Q operations.

gardners commented 2 weeks ago

let all invalid extended opcodes BRK instead? (dan)

I'd instead suggest that they should all trigger an "reserved instruction hypertrap", so that people don't go using them as BRK in the the meantime.

Rhialto commented 2 weeks ago

And how does the user know that the trap has triggered? I vote for showing an animation of nasal demons[1]...

[1] http://catb.org/jargon/html/N/nasal-demons.html

dansanderson commented 2 weeks ago

🤦‍♂️ There's already a DEQ. Please remove this from the list. 😅