Skip to content

Phase 2 - Output Buffer, Fixed Logic, and Clock

VS Code tasks

Choose Tasks: Run Task in the Z80ROMlessSBC workspace.

Diagnostic Build task Deployment task
Phase 2 buffers and clock Build Pico - Phase 2 buffers and clock Flash Pico - Phase 2 buffers and clock

Deployment tasks force-update without checking the previous firmware phase. Complete this phase's hardware prerequisites and save work before flashing. See Pico tools.

Tick setup items when complete and tests only after recording the measured result. Progress is saved in this browser; ticks are not automatic verification.

Phase Setup Checklist

Done Requirement
Phase 1 pass gate completed
CPU, SRAM and data translators remain absent
U4 SN74AHCT244N fitted after the wiring checks below pass, with external power and USB disconnected
U3 SN74HCT32N fitted after the wiring checks below pass, with external power and USB disconnected

Wiring - Output Buffer and Fixed Logic

Disconnect external power and USB. Keep U3/U4 out of their sockets while wiring and checking the tables below. CPU, SRAM and later-phase devices also remain absent. Connections already fitted in Phases 0/1 need checking, not a second wire. The buffer and gate reference explains the channel functions.

U4 AHCT244 input connections

Done Signal Source socket/header contact Destination socket contact
CLK Pico GP2 - header pin 4 U4 AHCT244 input - pin 2
BUSREQ# Pico GP4 - header pin 6 U4 AHCT244 input - pin 4
BOOT_READ_DISABLE Pico GP5 - header pin 7 U4 AHCT244 input - pin 6
MREQ# U1 Z80 MREQ# - pin 19 U4 AHCT244 input - pin 8
WR# U1 Z80 WR# - pin 22 U4 AHCT244 input - pin 11
RD# U1 Z80 RD# - pin 21 U4 AHCT244 input - pin 13
SRAM bank select Pico GP8 - header pin 11 U4 AHCT244 input - pin 17

U4 AHCT244 outputs to CPU and SRAM

Done Signal Source socket contact Destination socket contact
SRAM WE# U4 AHCT244 output - pin 9 U2 SRAM WE# - pin 29
SRAM A16 U4 AHCT244 output - pin 3 U2 SRAM A16 - pin 2
SRAM CE# U4 AHCT244 output - pin 12 U2 SRAM CE# - pin 22
BUSREQ# U4 AHCT244 output - pin 16 U1 Z80 BUSREQ# - pin 25
CLK U4 AHCT244 output - pin 18 U1 Z80 CLK - pin 6

The remaining U4 outputs, pins 7 and 14, connect to U3 in the next table.

U3 HCT32 gate connections

Rows are ordered by U3 pin number. Inputs and outputs are identified explicitly.

Done Signal Source socket/header contact Destination socket contact
Buffered RD# U4 AHCT244 output - pin 7 U3 HCT32 input - pin 1
Buffered BOOT_READ_DISABLE U4 AHCT244 output - pin 14 U3 HCT32 input - pin 2
SRAM OE# U3 HCT32 output - pin 3 U2 SRAM OE# - pin 24
Raw IORQ# U1 Z80 IORQ# - pin 20 U3 HCT32 input - pin 4
IO_RELEASE Pico GP9 - header pin 12 U3 HCT32 input - pin 5
WAIT# U3 HCT32 output - pin 6 U1 Z80 WAIT# - pin 24
DATA_UP_OE# U3 HCT32 output - pin 8 U9 AHCT245 OE# - pin 19
Raw RD# U1 Z80 RD# - pin 21 U3 HCT32 input - pin 9
PICO_DATA_UP_OE# Pico GP6 - header pin 9 U3 HCT32 input - pin 10

Supplies, grounded pins and unused outputs

These supply and ground connections are direct links, not resistors. The local capacitors are additional to the direct supply/ground wires. R24 is a separate 10 kOhm pull-down from Pico physical pin 11 (GP8) to GND; U4 pin 17 and SRAM A16 pin 2 must not be directly grounded.

Done Device Pin / component Required connection
U4 AHCT244 Pin 1, OE# Common GND
U4 AHCT244 Pin 5, unused output Leave open
U4 AHCT244 Pin 10, power GND Common GND
U4 AHCT244 Pin 15, unused input Common GND
U4 AHCT244 Pin 19, OE# Common GND
U4 AHCT244 Pin 20, VCC Core +5 V rail
U4 AHCT244 C4, 100 nF Close to pin 20, between +5 V and nearby common GND
U3 HCT32 Pin 7, power GND Common GND
U3 HCT32 Pin 11, unused output Leave open
U3 HCT32 Pin 12, unused input Common GND
U3 HCT32 Pin 13, unused input Common GND
U3 HCT32 Pin 14, VCC Memory +5 V rail
U3 HCT32 C3, 100 nF Close to pin 14, between +5 V and nearby common GND

With power and USB disconnected, tick each row after checking continuity at both actual socket contacts and isolation from adjacent pins. Progress is saved in the Phase 2 checklist. Keep the CLK connection entirely on Core.

Final checks before inserting U3 and U4

Done Check
All listed signal and supply connections complete; U4 pin 18 to Z80 socket pin 6 stays short and entirely on Core
Both U4 OEs grounded, all unused inputs grounded, unused outputs left open
Continuity checked at every actual socket contact, no neighboring shorts, correct supply rails and pin-1 orientation verified before inserting either device

Firmware and Tests

Keep the CPU, SRAM and data translators absent throughout this phase.

Done Test Procedure and pass condition
Firmware Build z80_stage02_buffers_clock and load its UF2 from build/src/stage02_buffers_clock/. See the maintained application
Buffer levels Press d for each LOW/HIGH step and probe U4 pairs 2/18, 4/16 and 6/14. Inputs are near 0/3.3 V; outputs are near 0/+5 V, LOW below 0.3 V and HIGH at least 4.4 V. The seventh step or x restores defaults
Clock Use 1, 2, 3 for 1 kHz, 100 kHz and 1 MHz. At U4 pin 18 and Z80 socket pin 6 verify frequency, 45-55% duty, and no runt pulses or threshold-crossing ringing. HIGH must reach measured CPU VCC minus 0.5 V: at 5.0 V, at least 4.5 V
Inhibit and WAIT Drive raw RD# and IORQ# LOW one at a time through 1 kOhm. SRAM OE stays HIGH while boot inhibit is HIGH; WAIT is LOW only while IORQ and IO_RELEASE are both LOW

OR-gate truth-table fixture

For every gate, test LOW/LOW, LOW/HIGH, HIGH/LOW and HIGH/HIGH. The expected outputs are LOW, HIGH, HIGH and HIGH respectively.

Use current-limited fixture outputs referenced to common circuit GND and limited to 0 V through measured U3 VCC. Disconnect external power and USB before every fixture change, and define both inputs before powering up. Never drive a connected Pico GPIO or another active output.

Done Step
Set the fixture ground, voltage range and current limit; keep Phase 2 loaded and do not use the Phase 1 walking test
Gate 1: disconnect U4 output 7 from U3 input 1
Gate 1: disconnect U4 output 14 from U3 input 2
Gate 1: attach fixture output A to U3 input 1
Gate 1: attach fixture output B to U3 input 2
Gate 1: test all four combinations at output 3, SRAM OE#
Gate 2: disconnect Pico GP9/header 12 from input 5, attach fixture outputs to inputs 4/5, then test all four combinations at output 6, WAIT#. CPU-absent IORQ# at input 4 has no active driver
Gate 3: disconnect Pico GP6/header 9 from input 10, attach fixture outputs to inputs 9/10, then test all four combinations at output 8, DATA_UP_OE#. CPU-absent RD# at input 9 has no active driver
Unused gate: leave inputs 12/13 grounded and output 11 unconnected

Restore normal wiring

Done Step
Disconnect external power and USB, then remove the fixture outputs and ground
Reconnect U4 output 7 to U3 input 1 and U4 output 14 to U3 input 2
Reconnect Pico GP9/header 12 to U3 input 5 and Pico GP6/header 9 to U3 input 10
Check continuity and adjacent-pin isolation, power up, then press x to restore defaults

Pass Gate

  • Buffer tests and all three used-gate truth tables passed; unused gate inputs grounded and output open.
  • Clock levels measured correctly.

Start later execution at 1 MHz, not the CPU's 20 MHz rating. This phase qualifies the unloaded socket clock only; repeat with the CPU fitted.

Maintained Source

These complete files are included from the repository at documentation build time, not copied into this page.

Phase 2 application - src/stage02_buffers_clock/main.c
#include <stdio.h>
#include "pico/stdlib.h"
#include "z80sbc/clock.h"
#include "z80sbc/pins.h"
#include "z80sbc/supervisor.h"

static const uint BUFFER_INPUT_PINS[] = {PIN_CLK, PIN_BUSREQ_N, PIN_BOOT_READ_DISABLE};
static const uint BUFFER_OUTPUT_CONTACTS[] = {18, 16, 14};
static const uint BUFFER_INPUT_CONTACTS[] = {2, 4, 6};
static int held_step = -1;

static void restore_test_outputs(void) {
  z80_clock_stop();
  z80_safe_startup();
  held_step = -1;
}

static void step_buffer_output(void) {
  int next_step = held_step + 1;
  restore_test_outputs();
  if (next_step >= 6) {
    printf("DONE: safe levels restored\n");
    return;
  }
  held_step = next_step;
  unsigned index = (unsigned)held_step / 2;
  bool high = held_step % 2 != 0;
  gpio_put(BUFFER_INPUT_PINS[index], high);
  printf("GP%u -> AHCT244 input %u -> output %u: HELD %s; verification=unmeasured\n",
         BUFFER_INPUT_PINS[index], BUFFER_INPUT_CONTACTS[index],
         BUFFER_OUTPUT_CONTACTS[index], high ? "HIGH" : "LOW");
}

int main(void) {
  restore_test_outputs();
  stdio_init_all();
  printf("\nStage 2: AHCT244 clock and boot-read inhibit; CPU/SRAM absent\n");
  printf("d=held LOW/HIGH steps, 1=1kHz, 2=100kHz, 3=1MHz, x=safe restore\n");
  while (true) {
    int command = getchar_timeout_us(0);
    if (command == 'd') {
      step_buffer_output();
    } else if (command >= '1' && command <= '3') {
      restore_test_outputs();
      uint32_t hz = command == '1' ? 1000 : command == '2' ? 100000 : 1000000;
      printf(z80_clock_set_hz(hz) ? "clock_actual=%lu verification=unmeasured\n"
                                : "FAIL: clock configuration %lu\n",
             (unsigned long)z80_clock_get_hz());
    } else if (command == 'x') {
      restore_test_outputs();
    }
    tight_loop_contents();
  }
}
Shared clock generation - src/common/clock.c
#include "z80sbc/clock.h"

#include "pico/stdlib.h"
#include "z80sbc/pins.h"
#if defined(Z80_BANKED_PIO)
#include "z80sbc/banked_pio.h"
#include "z80sbc/gpio_mux.h"
#else
#include "hardware/clocks.h"
#include "hardware/pwm.h"
#endif

static volatile uint32_t actual_clock_hz;

void __not_in_flash_func(z80_clock_prepare_stepping)(void) {
  z80_clock_stop();
  gpio_put(PIN_CLK, 0);
  gpio_set_dir(PIN_CLK, GPIO_OUT);
  if (gpio_get_function(PIN_CLK) != GPIO_FUNC_SIO) {
    // Extend a frozen clock HIGH before the mux can create a falling edge.
    busy_wait_us_32(1);
#if defined(Z80_BANKED_PIO)
    z80_gpio_mux_select(PIN_CLK, GPIO_FUNC_SIO);
#else
    gpio_set_function(PIN_CLK, GPIO_FUNC_SIO);
#endif
  }
}

uint32_t z80_clock_get_hz(void) {
  return actual_clock_hz;
}

#if defined(Z80_BANKED_PIO)
bool z80_clock_set_hz(uint32_t hz) {
  if (!z80_banked_pio_clock_start(hz))
    return false;
  actual_clock_hz = hz;
  return true;
}

void __not_in_flash_func(z80_clock_stop)(void) {
  z80_banked_pio_clock_stop();
}
#else
bool z80_clock_set_hz(uint32_t hz) {
  if (hz < Z80_CLOCK_MIN_HZ || hz > Z80_CLOCK_MAX_HZ)
    return false;

  uint slice_num = pwm_gpio_to_slice_num(PIN_CLK);
  uint channel = pwm_gpio_to_channel(PIN_CLK);
  uint32_t sys_clk = clock_get_hz(clk_sys);
  uint32_t best_divider = 0;
  uint32_t best_count = 0;

  for (uint32_t divider = 1; divider <= 255; ++divider) {
    uint64_t denominator = (uint64_t)hz * divider;
    uint64_t count = ((uint64_t)sys_clk + denominator - 1u) / denominator;
    count = (count + 1u) & ~UINT64_C(1);
    if (count < 2)
      count = 2;
    if (count > 65536)
      continue;
    uint64_t product = (uint64_t)divider * count;
    uint64_t best_product = (uint64_t)best_divider * best_count;
    if (best_divider == 0 || product < best_product ||
        (product == best_product && count > best_count)) {
      best_divider = divider;
      best_count = (uint32_t)count;
    }
  }
  if (best_divider == 0)
    return false;

  z80_clock_prepare_stepping();
  busy_wait_us_32(1);
  pwm_set_clkdiv_int_frac4(slice_num, (uint8_t)best_divider, 0);
  pwm_set_wrap(slice_num, (uint16_t)(best_count - 1u));
  pwm_set_chan_level(slice_num, channel, (uint16_t)(best_count / 2u));
  pwm_set_counter(slice_num, 0);
  gpio_set_function(PIN_CLK, GPIO_FUNC_PWM);
  pwm_set_enabled(slice_num, true);
  uint64_t product = (uint64_t)best_divider * best_count;
  actual_clock_hz = (uint32_t)(((uint64_t)sys_clk + product / 2u) / product);
  return true;
}

void __not_in_flash_func(z80_clock_stop)(void) {
  pwm_set_enabled(pwm_gpio_to_slice_num(PIN_CLK), false);
}

void __not_in_flash_func(z80_clock_resume)(void) {
  pwm_set_counter(pwm_gpio_to_slice_num(PIN_CLK), 0);
  gpio_set_function(PIN_CLK, GPIO_FUNC_PWM);
  pwm_set_enabled(pwm_gpio_to_slice_num(PIN_CLK), true);
}
#endif

void z80_clock_one_cycle(uint32_t half_period_us) {
  z80_clock_prepare_stepping();
  z80_clock_step_prepared(half_period_us);
}

void __not_in_flash_func(z80_clock_step_prepared)(uint32_t half_period_us) {
  busy_wait_us_32(half_period_us);
  gpio_put(PIN_CLK, 1);
  busy_wait_us_32(half_period_us);
  gpio_put(PIN_CLK, 0);
  // Let CPU controls, translators and input synchronizers settle before return.
  busy_wait_us_32(1);
}

void z80_reset_with_clock_cycles(unsigned int cycles,
                                 uint32_t half_period_us) {
  gpio_put(PIN_RESET_N, 0);
  for (unsigned int cycle = 0; cycle < cycles; ++cycle)
    z80_clock_one_cycle(half_period_us);
  z80_clock_stop();
}