This is the third part of my ‘live blogging’ series as I create a replacement gate array for the Amstrad CPC using a Raspberry Pi RP2350B microcontroller.
In this part I work on the /CAS, /244EN and /MWE signals. /244EN and /MWE are only asserted during a fixed portion of the gate array cycle and only if a particular input signal is asserted (or not). /CAS is similar but is also asserted twice at fixed times during the gate array cycle.
I’ll be using a PIO program to generate all of these signals (one program per signal) and, as you might guess, each of these programs will be fairly similar. I’ll begin with /244EN as the simplest of the three.
Copying and Syncing
Each of these signals requires asserting an output pin based the value of a single input pin. A naive implementation of this would be to wait until a fixed point in the gate array cycle, read the input signal and use a conditional to either assert the output signal or not. The flaw in that design is that it depends on the input signal being asserted at the appropriate moment. The input signals originate with the Z80 and their timings are not guaranteed1 and such a strategy could miss the signal being asserted.
A better strategy is to wait until the appropriate point within the gate array cycle (during which the signal can be asserted) then enter a loop which repeatedly copies the input pin to the output one for the required time period (by setting the number of iterations).
I also need to ensure the signal timing is correctly sync’ed to the rest of the gate array cycle. My code does that by listening to /CCLK signal output by the FSIGS program. The choice of signal to listen to is somewhat arbitrary, it could be any of the signals which is asserted once per cycle. /CCLK is a signal which changes state shortly prior to any of the CSIGS signals. This reduces the need to insert delays prior to asserting the output signal. The downside of /CCLK is that it de-asserts after some of the CSIGS signals and I’ll need to ensure none of the programs retrigger.
/244EN – ‘244 Enable
/244EN is only asserted if the /IORQ input is low. Here is the resulting PIO code:
.define cclk_pin 1 ;Index of CCLK pin
.program en244
set pins,1 ;Set /244EN pin high
wait 1 gpio cclk_pin ;Wait until CCLK is high
set x,19 ;Load the loop counter
;We need to generate the signal for about 7 16MHz clocks,
;about 56 system clocks (@160MHz). The three instructions
;in the loop take three clocks to execute. The loop needs
;to execute for 56/3 (19) repetitions.
wait 0 gpio cclk_pin [10] ;Wait for the CCLK signal to transition low,
;then wait ten clocks (one 16MHz clock cycle)
loop:
mov y,pins ;/IORQ pin state -> Y register
mov pins,y ;Y register -> /244EN pin state
jmp x--,loop ;Dec X and loop it was not zero prior to the decrement
The first line defines the pin index of the /CCLK signal.
The program itself:
- Sets the output pin to it’s default (resting) state.
- Waits until the /CCLK GPIO pin is high. This instruction really belongs at the end of the loop, where it waits for /CCLK to de-assert, but putting it the start of the program also helps the PIO to sync at startup.
- Sets the loop count value into the X register.
- Waits for the /CCLK signal to go low. The [10] suffix causes the PIO to pause for ten clock cycles. This sets the start time for /244EN output to be asserted.
- The loop body reads the state of the /IORQ pin into the Y register and then writes it straight back to the /244EN pin. The Y register itself is 32 bits wide but the config for the program specifies how many pins to write to. Any extra pins which might have been be read in are simply ignored.
- The JMP instruction decrements the loop counter in X and jumps if the value was zero before it was decremented.
- Finally the program automatically wraps to the start.
I described a typical PIO initialisation function in the previous article. The key changes here are that I’m using both an input and output pin, and I’m specifying the pin configuration for the SET
, OUT
(which also configures the MOV
to pins), and IN
(which also configures the MOV
from pins).
void en244_program_init(PIO pio, uint sm, uint offset, uint in_pin, uint out_pin) {
//Set up pins. CCLK will have been set up elsewhere, we're just listening to it
pio_gpio_init(pio, in_pin);
pio_sm_set_consecutive_pindirs(pio, sm, in_pin, 1, false);
pio_gpio_init(pio, out_pin);
pio_sm_set_consecutive_pindirs(pio, sm, out_pin, 1, true);
pio_sm_config c = en244_program_get_default_config(offset);
//Pins for SET PINS instruction
sm_config_set_set_pins(&c, out_pin, 1);
//Pins for OUT and MOV PINS,_
sm_config_set_out_pins(&c, out_pin, 1);
//First pin for IN and MOV _,PINS
sm_config_set_in_pins(&c, in_pin);
pio_sm_init(pio, sm, offset, &c);
}
/MWE – Memory Write Enable
/MWE is only asserted if the /RD input is high (not asserted) and has slightly different timing to /244EN.
As might be expected the PIO program for /MWE is almost identical to that for /244EN. The timing changes affect both the loop counter (SET X) and the pause before the loop starts (the value in square brackets after the WAIT 0 GPIO CCLK_PIN
instruction).
The other change is in the loop itself. /244EN is set low if the input is low – a direct copy. /MWE is set low if the input is high – inverted. That’s done with by adding an ‘!’ to the MOV
instruction to give MOV Y,!PINS
.
.define cclk_pin 1 ;Index of CCLK pin
.program mwe
set pins,1 ;Set /244EN pin high
wait 1 gpio cclk_pin ;Wait until CCLK is high
set x,10 ;Load the loop counter
;We need to generate the signal for about 7 16MHz clocks,
;about 56 system clocks (@160MHz). The three instructions
;in the loop take three clocks to execute. The loop needs
;to execute for 56/3 (19) repetitions.
wait 0 gpio cclk_pin [31] ;Wait for the CCLK signal to transition low,
;then wait 31 clocks (one 16MHz clock cycle)
loop:
mov y,!pins ;inverted /RD pin state -> Y register
mov pins,y ;Y register -> inverted RD pin state
jmp x--,loop ;Dec X and loop it was not zero prior to the decrement
/CAS – Column Address Select
/CAS is asserted during the CPU portion of the gate array cycle if the /MREQ input is asserted. /CAS is also asserted twice during the gate array portion of the cycle (when the gate array reads video data).
The program reflects this with the conditional part of the cycle being the same as that for /244EN apart from timing differences. The fixed, video data, part of the cycle is just four instructions to set the output pin with pauses for timing.
.define cclk_pin 1 ;Index of CCLK pin
.program cas
set pins,1 ;Set /CAS pin high
set x,10 ;Load the loop counter
;We need to generate the signal for about ?? 16MHz clocks,
;about 30 system clocks (@160MHz). The three instructions
;in the loop take three clocks to execute. The loop needs
;to execute for 10 repetitions.
wait 0 gpio cclk_pin [25] ;Wait for the CCLK signal to transition low,
;then wait 25 clocks (2.5 16MHz clock cycles)
loop:
mov y,pins ;/MREQ pin state -> Y register
mov pins,y ;Y register -> /CAS pin state
jmp x--,loop ;Dec X and loop it was not zero prior to the decrement
set pins,1 [24] ;Set /CAS high, wait for 24 cycles
set pins,0 [24] ;Set /CAS low, wait for 24 cycles
set pins,1 [14] ;Set /CAS high, wait for 14 cycles
set pins,0 [29] ;Set /CAS low, wait for 29 cycles
The End Result
Running the code results in the following oscilloscope trace when none of the outputs is being triggered.
And the following when all three signals are being triggered (which would never simultaneously happen in a real CPC – this is out of circuit testing).
Final Thoughts
The choice of /CCLK as sync signal is somewhat arbitrary. I could probably save a couple of instructions by syncing to /CPU_ADDR instead. But the code above works and fits within the instruction space available in a single RP2350 PIO. I’m happy with it for now but may modify if space gets tight.
- The gate array functions by pausing the Z80 (with the READY signal) if it tries to access memory while the gate array itself needs to access memory. That pausing has the ultimate effect of syncing the CPU to the gate array cycle but there are plenty of times when the CPU is not synced to the gate array. Therefore exact signal timings cannot be guaranteed.