velxio/test/test_intel/test_buses/8255-ppi.c

219 lines
7.5 KiB
C

/*
* Intel 8255 Programmable Peripheral Interface — Mode 0 only.
*
* The 8255 is a 40-pin DIP that gives a CPU three 8-bit ports (A, B,
* C) with programmable direction. Mode 0 is the simplest: each port
* (and the upper/lower halves of port C independently) can be set to
* input or output via writes to the control register.
*
* Source: Intel 8255A Datasheet (public domain mirror on bitsavers).
*
* Pin contract (40-pin DIP):
* D0..D7 bidirectional 8-bit data bus
* PA0..PA7 bidirectional — direction set by control register
* PB0..PB7 bidirectional
* PC0..PC7 bidirectional — upper and lower halves independent
* A0, A1 input — register select:
* 00 = port A, 01 = port B, 10 = port C, 11 = control
* CS̅ input — active-low chip enable
* RD̅ input — active-low read strobe
* WR̅ input — active-low write strobe
* RESET input — active-high; clears all ports to input mode
* VCC, GND
*
* Control word format (Mode 0 only — bit 7 = 1):
* bit 7: 1 = set mode (0 = bit set/reset on port C — not supported)
* bit 6,5: group-A mode (00 = mode 0)
* bit 4: PA direction (1 = input, 0 = output)
* bit 3: PC upper (PC4..PC7) direction
* bit 2: group-B mode (0 = mode 0)
* bit 1: PB direction
* bit 0: PC lower (PC0..PC3) direction
*
* Mode 1 (strobed I/O) and Mode 2 (bidirectional) are NOT implemented.
* Bit set/reset operations on port C (control byte with bit 7 = 0)
* are NOT implemented yet.
*/
#include "velxio-chip.h"
#include <stdint.h>
#include <stdbool.h>
typedef struct {
vx_pin d[8];
vx_pin pa[8];
vx_pin pb[8];
vx_pin pc[8];
vx_pin a0, a1;
vx_pin cs, rd, wr, reset_;
vx_pin vcc, gnd;
/* Direction flags: 1 = input (we don't drive), 0 = output (we drive) */
bool pa_input;
bool pb_input;
bool pc_low_input; /* PC0..PC3 */
bool pc_high_input; /* PC4..PC7 */
/* Latched output values per port (used when in output mode) */
uint8_t pa_out;
uint8_t pb_out;
uint8_t pc_out;
bool driving_d;
int wr_last;
int rd_last;
} chip_t;
static chip_t G;
/* ─── Helpers ───────────────────────────────────────────────────────────── */
static uint8_t read_d_byte(void) {
uint8_t v = 0;
for (int i = 0; i < 8; i++) if (vx_pin_read(G.d[i])) v |= (1u << i);
return v;
}
static void drive_d(uint8_t v) {
for (int i = 0; i < 8; i++) {
vx_pin_set_mode(G.d[i], VX_OUTPUT);
vx_pin_write(G.d[i], (v >> i) & 1);
}
G.driving_d = true;
}
static void release_d(void) {
if (!G.driving_d) return;
for (int i = 0; i < 8; i++) vx_pin_set_mode(G.d[i], VX_INPUT);
G.driving_d = false;
}
static uint8_t read_port(vx_pin* port) {
uint8_t v = 0;
for (int i = 0; i < 8; i++) if (vx_pin_read(port[i])) v |= (1u << i);
return v;
}
static void drive_port(vx_pin* port, uint8_t v, uint8_t mask_output) {
/* Drive only the bits marked as output (mask_output=1 → output) */
for (int i = 0; i < 8; i++) {
if (mask_output & (1 << i)) {
vx_pin_set_mode(port[i], VX_OUTPUT);
vx_pin_write(port[i], (v >> i) & 1);
} else {
vx_pin_set_mode(port[i], VX_INPUT);
}
}
}
static uint8_t cur_port_addr(void) {
return (vx_pin_read(G.a1) ? 2 : 0) | (vx_pin_read(G.a0) ? 1 : 0);
}
static void apply_directions(void) {
drive_port(G.pa, G.pa_out, G.pa_input ? 0x00 : 0xFF);
drive_port(G.pb, G.pb_out, G.pb_input ? 0x00 : 0xFF);
uint8_t pc_mask = 0;
if (!G.pc_low_input) pc_mask |= 0x0F;
if (!G.pc_high_input) pc_mask |= 0xF0;
drive_port(G.pc, G.pc_out, pc_mask);
}
static void apply_control(uint8_t c) {
if ((c & 0x80) == 0) {
/* Bit set/reset operation — not implemented. */
return;
}
G.pc_low_input = (c & 0x01) != 0;
G.pb_input = (c & 0x02) != 0;
G.pc_high_input = (c & 0x08) != 0;
G.pa_input = (c & 0x10) != 0;
/* Reset output latches per the datasheet: control writes clear
any previously-driven output values to 0. */
G.pa_out = 0;
G.pb_out = 0;
G.pc_out = 0;
apply_directions();
}
static void reset_state(void) {
/* RESET clears the chip: all ports become inputs (Mode 0, all in). */
G.pa_input = true;
G.pb_input = true;
G.pc_low_input = true;
G.pc_high_input = true;
G.pa_out = G.pb_out = G.pc_out = 0;
G.wr_last = 1;
G.rd_last = 1;
apply_directions();
release_d();
}
/* ─── Read / Write strobes ──────────────────────────────────────────────── */
static void on_rd(void* user_data, vx_pin pin, int value) {
(void)user_data; (void)pin;
if (vx_pin_read(G.cs) != 0) { release_d(); return; }
if (value == 0) {
/* RD̅ asserted → drive D with the selected register's value */
uint8_t addr = cur_port_addr();
uint8_t v = 0;
switch (addr) {
case 0: v = G.pa_input ? read_port(G.pa) : G.pa_out; break;
case 1: v = G.pb_input ? read_port(G.pb) : G.pb_out; break;
case 2: {
uint8_t pc_lo = G.pc_low_input ? (read_port(G.pc) & 0x0F) : (G.pc_out & 0x0F);
uint8_t pc_hi = G.pc_high_input ? (read_port(G.pc) & 0xF0) : (G.pc_out & 0xF0);
v = pc_lo | pc_hi;
break;
}
case 3: v = 0; break; /* control register read returns 0 (datasheet: undefined) */
}
drive_d(v);
} else {
release_d();
}
}
static void on_wr(void* user_data, vx_pin pin, int value) {
(void)user_data; (void)pin;
if (vx_pin_read(G.cs) != 0) { G.wr_last = value; return; }
/* Latch on rising edge of WR̅ (deassert), per Intel datasheet. */
if (G.wr_last == 0 && value == 1) {
uint8_t addr = cur_port_addr();
uint8_t v = read_d_byte();
switch (addr) {
case 0: G.pa_out = v; break;
case 1: G.pb_out = v; break;
case 2: G.pc_out = v; break;
case 3: apply_control(v); break;
}
apply_directions();
}
G.wr_last = value;
}
static void on_reset(void* user_data, vx_pin pin, int value) {
(void)user_data; (void)pin;
if (value) reset_state();
}
void chip_setup(void) {
char name[5];
for (int i = 0; i < 8; i++) { name[0]='D'; name[1]='0'+i; name[2]=0; G.d[i] = vx_pin_register(name, VX_INPUT); }
for (int i = 0; i < 8; i++) { name[0]='P'; name[1]='A'; name[2]='0'+i; name[3]=0; G.pa[i] = vx_pin_register(name, VX_INPUT); }
for (int i = 0; i < 8; i++) { name[0]='P'; name[1]='B'; name[2]='0'+i; name[3]=0; G.pb[i] = vx_pin_register(name, VX_INPUT); }
for (int i = 0; i < 8; i++) { name[0]='P'; name[1]='C'; name[2]='0'+i; name[3]=0; G.pc[i] = vx_pin_register(name, VX_INPUT); }
G.a0 = vx_pin_register("A0", VX_INPUT);
G.a1 = vx_pin_register("A1", VX_INPUT);
G.cs = vx_pin_register("CS", VX_INPUT);
G.rd = vx_pin_register("RD", VX_INPUT);
G.wr = vx_pin_register("WR", VX_INPUT);
G.reset_ = vx_pin_register("RESET", VX_INPUT);
G.vcc = vx_pin_register("VCC", VX_INPUT);
G.gnd = vx_pin_register("GND", VX_INPUT);
reset_state();
vx_pin_watch(G.rd, VX_EDGE_BOTH, on_rd, 0);
vx_pin_watch(G.wr, VX_EDGE_BOTH, on_wr, 0);
vx_pin_watch(G.reset_, VX_EDGE_RISING, on_reset, 0);
}