tepples wrote:I foresee some intermodulation distortion between two sines with the "fast" PWM.
For the CICOp and it's set limitations I'll probably be limited to simpler waveforms (square, triangle, saw) anyway. If it does get a sine channel, it likely won't be more than one. I am curious to see what how well a PWM DAC (perhaps even dual-PWM) can perform on a more capable mcu in comparison to a hardware DAC. In the end, the added cost of a mcu with built in DAC isn't too much. So using a PWM DAC is best placed in the most cost sensitive/limited projects.
Thanks for the PWM analysis guys! It is nice to hear that an extra bit of edge aligned resolution stands the chance to make up for the loss of center aligned PWM!
I also foresee trouble if DMC cycle stealing happens while the CPU is accessing the CICO.
Ahh yeah. I completely forgot about that DMC guy..
Now on to how DMC cycle stealing interacts with a scheme to blindly put stuff on data bus for reading at 4, 8, and 12 cycles after the final write.
I think we can cover this case, so here goes:
So assuming
this to be accurate enough for our situation:
Likely internal implementation of the read
The following is speculation, and thus not necessarily 100% accurate. It does accurately predict observed behavior.
The 6502 cannot be pulled off of the bus normally. The 2A03 DMC gets around this by pulling RDY low internally. This causes the CPU to pause during the next read cycle, until RDY goes high again. The DMC unit holds RDY low for 4 cycles. The first three cycles it idles, as the CPU could have just started an interrupt cycle, and thus be writing for 3 consecutive cycles (and thus ignoring RDY). On the fourth cycle, the DMC unit drives the next sample address onto the address lines, and reads that byte from memory. It then drives RDY high again, and the CPU picks up where it left off.
This matters because on NTSC NES and Famicom, it can interfere with the expected operation of any register where reads have a side effect: the controller registers ($4016 and $4017), reads of the PPU status register ($2002), and reads of VRAM/VROM data ($2007) if they happen to occur in the same cycle that the DMC unit pulls RDY low.
For the controller registers, this can cause an extra rising clock edge to occur, and thus shift an extra bit out. For the others, the PPU will see multiple reads, which will cause extra increments of the address latches, or clear the vblank flag.
This problem has been fixed on the 2A07 and PAL NES is exempt of this bug.
For reference, here's my CICOp routine:
Code: Select all
sty CICOP_ADDR_EN ; 8c [bank num] [bank table low] [bank table high]
sta (cicop_reg), y ; 91 [ZP byte num] [x:regL] [x:regH] xx [x:wrL]
stx cicop_reg ; 86 [ZP byte num] [x:wrH]
ldx CICOP_PORT ; ae [addr L] [addr H] [x:rdL]
ldy CICOP_PORT ; ac [addr L] [addr H] [x:rdH]
lda CICOP_PORT ; ad [addr L] [addr H] [x:rdE]
The 6502 gets stalled on the next read cycle following the DMC pulling RDY low. And once the DMC is done with it's fetch/stealing the 6502 re-performs the read that was stalled. What I'm uncertain of is which of the reads actually gets caught by the 6502. I would guess the initial stalled read is executed, but the 6502 doesn't catch it. It's the second post-stall read that's actually caught (otherwise it probably wouldn't be done)..?
[edit sorry I've got some opcode name and cycle number errors here.. Think they're fixed now. Realizing there are some other cases I'm not detecting and differentiating between, but I think there's room to cover them]
If the stall were to occur on any of the read cycles during STA (T0-T4) or STX(T0-1), the CICOp could sense the stall as CPU R/W wouldn't be low during the expected write cycle (STA T5 and STX T2) due DMC stall & fetch. It would also be known that the CPU stalled for 4 cycles, the CICOp could potentially insert that delay to it's routine.
If the stall were to start on the final write cycle of STA, the write would occur normally. But the 3 cycle stall could be sensed as CPU R/W wouldn't be low at expected time for STX. It would be known that the CPU was stalled for 3 cycles, similarly the CICOp could delay it's routine by 3 cycles.
If the stall were to start on the final write cycle of STX, the write would occur normally. But the opcode and address fetch of LDX wouldn't be present on the bus. The CICOp could sense this case by checking LDX opcode being present on the bus @ T0 provided the written data from STX didn't also equal '0xE'. T1 would continue as open bus, and the DMC would hijack the bus on T2. So the CICOp could stop itself/delay outputing data on the bus for LDX T3. This case also stalls the CPU by 3 cycles, the CICOp could try to delay itself, but needs to differentiate this case from the ones below that have a 4 cycle stall.
The final 3 LDX/LDY/LDA all have similar behavior. The case that really needs to be covered is if the stall were to start during T0 when the opcode was being fetched. In this case the DMC will hijack the bus on the last cycle (T3) when the CICOp is also planning on driving the bus. This condition could be detected by verifying low address of CICOP_PORT being on the bus for T2. The CICOp could recover by delaying 4 cycles.
If the stall were to start during T1/T2 of LDX/LDY/LDA we're mostly safe to output data as the DMC will hijack the bus after the CICOp drives data to a stalled CPU. A stall during T2 could be sensed by CICOP_PORT high addr (T3) not being present. To support that, A0-3 of CICOP_PORT needs to differ from A8-11 (and also not equal 0xE, 0xC, 0xD to differ from LDX/LDY/LDA opcode) which is simple enough with a chosen address of something like $5A05.
If the stall were to occur on T3, the final LDX/Y/A cycle, there isn't a great way to sense that until T0 of the subsequent LDY/LDA. In that case the opcode/operand wouldn't be present for the next load. This can be easily caught for the first two loads (LDX/LDA), and CICOp routine delayed accordingly. But not for the final one assuming I'm correct that the initial stalled read isn't caught, and the subsequent second read is what's caught. This isn't necessarily an issue as this final read is intended to be the verification read. So reading back open bus would simply be a false failure. However, we could define the 6502 instruction that follows the current routine to allow for detection of this condition so the final nibble can be delayed and resent.
Phew.. Well in the end doing all this doesn't seem to unreasonable given the number of nops currently in my CICOp isr. It's certainly gets complicated quick though..
Now I'm curious how hard it would be to detect and protect if the 6502 were interrupted mid routine. It certainly wouldn't be as easy to transparently recover as there's no telling how long the 6502 interrupt will last. The above proposals will probably cover an interrupt prior to completion of STA/STX with CPU R/W prior to latching data. But I'll have to check into this more, hopefully there's a way to detect this case enough to prevent from outputting data on the bus when it shouldn't be thus preventing a crash. If that's possible, then one could set a flag to denote that a CICOp register "transfer is in progress" prior to this routine. Then the NMI/IRQ routine could update that flag to "current transfer failed". Once the 6502 returns to the CICOp routine that got interrupted, it would make a check at the end and reperform the transfer if "current transfer failed" was true.