Page 1 of 1

VIA anomaly ~ single-clock single-step (blind interface)

Posted: Wed Feb 18, 2026 4:05 pm
by Michael
I use a microcontroller to access RAM and ROM a single clock cycle at a time on my ROM Emulator project and I noticed a problem accessing the VIA chip that way. I issue clock cycles that start and end in the high state to keep non-WDC CPUs static, which also leaves the CPU address and r/w lines active until the next clock cycle comes along. During memory read/write clock cycles the chip enable line goes low and high within the clock cycle which works fine for RAM and ROM but a read/write VIA clock cycle doesn't complete until I send another clock cycle of some sort.

So, the VIA clock cycle takes the clock lo. The CPU sets the address and r/w lines. The memory enable (RAM, ROM, or I/O) line goes low. The clock line goes high. The CPU puts data on the bus for a write cycle or reads the bus for a read cycle. The memory enable line (RAM, ROM, or I/O) goes high. The clock line remains high and the CPU address and r/w lines are still active.

Does the 65C22 VIA complete read/write transactions when the clock goes low rather than when the chip enable line is disabled?

TIA. Cheerful regards.
6502 Blind Interface Notes 1.png
6502 RE #2a.png

Re: VIA anomaly ~ single-clock single-step (blind interface)

Posted: Wed Feb 18, 2026 9:01 pm
by Dr Jefyll
Michael wrote:
During memory read/write clock cycles the chip enable line goes low and high within the clock cycle which works fine for RAM and ROM but a read/write VIA clock cycle doesn't complete until I send another clock cycle of some sort.
If you mean the VIA's chip enable line goes low and high within the time Phi2 is high, then that's a problem. The address decoder which drives a VIA's CS mustn't be qualified by Phi2. (Edit: hmm, your diagrams show it's NOT qualified by Phi2.

-- Jeff

Re: VIA anomaly ~ single-clock single-step (blind interface)

Posted: Wed Feb 18, 2026 9:07 pm
by SamCoVT
You should review the timing diagrams on page 46 of WDC's w65c22 datasheet (the values for the timings are in the preceding pages). The address, chip select, and R/W* should be set BEFORE the rising edge of PHI2 and the data will be captured on the falling edge of PHI2. I think your NOP is just creating the falling edge the 65C22 is waiting for.
I think what you are doing is not a standard bus cycle the 65C22 understands, so the expected behavior is undefined. When you have PHI2 high, you are in the middle of a cycle, not between cycles.

Re: VIA anomaly ~ single-clock single-step (blind interface)

Posted: Wed Feb 18, 2026 10:12 pm
by BigDumbDinosaur
Dr Jefyll wrote:
If you mean the VIA's chip enable line goes low and high within the time Phi2 is high, then that's a problem. The address decoder which drives a VIA's CS mustn't be qualified by Phi2. (Edit: hmm, your diagrams show it's NOT qualified by Phi2.
True dat, but if Ø2 is halted in the high phase and the VIA’s control inputs are manipulated by the glue logic, the VIA has no frame of reference and I would expect it to be unresponsive.  The VIA is a synchronous device and must be clocked in order to function.  Sam points that out as well in his reply.

Re: VIA anomaly ~ single-clock single-step (blind interface)

Posted: Thu Feb 19, 2026 7:54 pm
by Michael
Thank you for helping me think through this, guys. Turns out the VIA chip enable is available past the end of a 'pull' clock sequence when ϕ2 remains high and the address lines remain active because the I/O chip enable comes from the address decoder and not from the low-level 'pull' function. So I just need to generate a single-cycle 'nop' at the end of a series of I/O operations. My test ROM Emulator LCD driver code (below) contains a strategically placed 'nop' instruction and now I can write to the LCD display from the uC in ROM Emulator mode as well as from the 6502 in normal 6502 RUN mode.

Cheerful regards...

Code: Select all

     #define line1 (0x80+0x00)        // set DDRAM command + line 1 address
     #define line2 (0x80+0x40)        // set DDRAM command + line 2 address
     #define line3 (0x80+0x14)        // set DDRAM command + line 3 address
     #define line4 (0x80+0x54)        // set DDRAM command + line 4 address

     LCD lcd;                         // create 'lcd' instance of 'LCD' class

     lcd.init();                      // 8-bit mode, cursor inc & off
     _delay_ms(1000);                 // wait 1-second 
     lcd.print("3P8B Backpack v1");   // splash screen
     lcd.cmd(line2+2);                // line 2, tab 2
     lcd.print(F("8-MHz 65C02"));     // a "flash string helper" ROM string

Code: Select all

  /******************************************************************************        ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   *  test LCD class - ROM Emulator mode - writing to LCD serial backpack.      *        ;~~~  6502 low level 3P8B LCD backpack drivers ~ M.McLaren  ~~~
   ******************************************************************************/       ;~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

   #define VIAPORTB  0xA000           //                                                 ;~~~        X         XXXXX   XXXXX                         ~~~
   #define VIAPORTA  0xA001           //                                                 ;~~~        X        X     X  X    X         (C) 2026       ~~~
   #define VIADDIRB  0xA002           //                                                 ;~~~        X        X        X     X      Mike McLaren     ~~~
   #define VIADDIRA  0xA003           //                                                 ;~~~        X        X        X     X          K8LH         ~~~                                    
                                                                                         ;~~~        X        X        X     X                       ~~~
   #define clk 4                      // index for VIA PA4                               ;~~~        X        X     X  X    X       CA65 v2.13.3     ~~~
   #define stb 2                      // index for VIA PA2                               ;~~~        XXXXXXX   XXXXX   XXXXX                         ~~~
   #define dat 0                      // index for VIA PA0
                                                                                         .define dat     0           ; PA0 bit index
   class LCD : public Print           // use 'Print' features *******************        .define clk     1           ; PA1 bit index
   { public:                          //                                        *        .define stb     2           ; PA2 bit index
       void init();                   //                                        *        .define swx     3           ; PA3 bit index
       void cmd(uint8_t);             //                                        *
       size_t write(uint8_t);         //                                        *        lcdInit:                    ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     private:                         //                                        *                lda DDRA            ; read current DDRA state       (4)
       bool rs = 0;                   // lcd 'RS' flag                          *                ora #(1<<stb | 1<<clk | 1<<dat)  ;                  (2)
   };                                 // ****************************************                sta DDRA            ; clk/dat/stb pins to outputs   (4)
                                                                                                 delayCy(100*msecs)  ; lcd power-on delay       (100000) 1-MHz clock
   void LCD::init()                   // ****************************************                wrCmd #$38          ; 8-bit, 2-line, 5x7 font     (234)
   { uReset();                        // reset 6502 (synchronize uC to cpu)     *                wrCmd #$0C          ; display on, cursor off      (234)
     run(0);                          // insert I/O into address space          *                wrCmd #$06          ; cursor inc, shift off       (234)
     wrRAM(VIADDIRA, (1 << clk) | (1 << stb) | (1 << dat));  // VIA outputs     *                wrCmd #$01          ; clear display               (234)
     _delay_ms(100);                  // LCD power-on delay                     *                delayCy(2*msecs)    ; required delay             (2000) 1-MHz clock
     cmd(0x38);                       // 8-bit interface, 2-lines, 5x7 font     *                rts                 ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     cmd(0x0C);                       // display on, cursor & blink off         *        
     cmd(0x06);                       // cursor inc, shift off                  *        lcdCmd:                     ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
     cmd(0x01);                       // clear display                          *                clc                 ; rs = 0 'command reg           (2)
     _delay_ms(2);                    // required delay                         *               .byte $89            ; skip over 'sec' instruction   (2) bit <imm>
   }                                  // ****************************************        lcdDat:                     ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
                                                                                                 sec                 ; rs = 1 'data reg              (2)
   void LCD::cmd(uint8_t c)           // ****************************************                sta work            ;                               (3)zp
   { rs = 0; write(c); rs = 1;        //                                        *        ;~~~                                                        ~~~
   }                                  // ****************************************        ;~~~   clock 8-bit 'work' byte into the shift register ic   ~~~
                                                                                         ;~~~                                                        ~~~
   size_t LCD::write(uint8_t data)    // ****************************************                ldx #8              ; bit counter                   (2)
   { for(uint8_t i = 8; i > 0 ; i--)  // load 74HC164 shift register            *                lda PORTA           ;                               (4)
     { wrRAM(VIAPORTA, 0 << clk | (bool)(data & 128) << dat);  //               *        lcd1:   and #~(1<<stb | 1<<clk | 1<<dat)  ;                 (2)
       wrRAM(VIAPORTA, 1 << clk | (bool)(data & 128) << dat);  //               *                rol work            ; move bit 7 into Carry         (5)zp
       data <<= 1;                    // prep next bit                          *                adc #$00            ; add bit, dat = 0 or 1         (2)
     }                                // re-task 'clk' pin for LCD 'RS' input   *                sta PORTA           ; clk = 0, dat = 0 or 1         (4)
     wrRAM(VIAPORTA, rs << clk | 1 << stb);  // strobe LCD 'E' pin              *                ora #(1<<clk)       ;                               (2)
     wrRAM(VIAPORTA, rs << clk | 0 << stb);  //  "                              *                sta PORTA           ; clk = 1, dat = 0 or 1         (4)
     uPush(0x03);                     // single-cycle 'nop' (I/O work-around)   *                dex                 ; done?                         (2)
     return 1;                        //                                        *                bne lcd1            ; no, branch (loop), else       (2)(3)
   }                                  // ****************************************        ;~~~                                                        ~~~
                                                                                         ;~~~   re-task 'clk' pin for LCD 'rs' input & strobe LCD    ~~~
                                                                                         ;~~~                                                        ~~~
                                                                                                 bit work            ; rs = 1 (data)?                (3)zp
                                                                                                 bmi strobe          ; yes, skip, else               (2)(3)
                                                                                                 eor #(1<<clk)       ; rs = 0 (command)              (2)
                                                                                         strobe: ora #(1<<stb)       ; lcd 'e' = 1                   (2)
                                                                                                 sta PORTA           ; strobe LCD 'E' pin            (4)
                                                                                                 eor #(1<<stb)       ;  "                            (2)
                                                                                                 sta PORTA           ;  "                            (4)
                                                                                                 rts                 ; ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
6502 Blind Interface Notes 2.png
LCD Backpack SWX.png