How does a CPU interface with GPIO pins?
What you want is a "memory mapped peripheral". You add some logic to the address bus so that, if the address matches a specific pattern, instead of read/write being routed to the memory they are routed to a peripheral.

The peripheral then contains one or more registers, which can be directly connected to pieces of the peripheral. So for example when reading from address 0x1234 you might read directly from the GPIO.

Interrupts are useful but not essential. 

You might find it convenient to implement "AXI" or "AMBA" bus protocols, especially if you want to connect to pre-existing IP blocks for peripherals.

(I learned this from Micro Interfacing Circuits by RA Penfold, but that's an extremely old book)