Enabling PPS For Your Zpuino Peripherals

One of the things I really like about the Zpuino soft-core is its ability to map peripheral IO to any of the available pins. This capability is known as Peripheral Pin Select (PPS) and means that hardware designed for use with the Zpuino does not have to be tied to specific pins. You can specify the pins in your source code and even change them on the fly.

I’m working on my VHDL skills by designing new peripherals and integrating them into the empty slots of a Zpuino core. I wanted to have my peripherals capable of using PPS but while I could find examples of how to use PPS with existing peripherals, there was no mention of how to update the core itself to enable PPS for newly integrated ones. Luckily it wasn’t too difficult to figure it out from the source VHDL.

The steps described here are particular to a Papilio One board and assume you’ve already integrated your peripheral using the instructions on the Zpuino website. If you are looking at these instructions for the first time, you should recognize that the reference to zpuino_io.vhd in the “Connecting Your Device” section is slightly misleading. Look instead at the top level file included in the source code for the board you are working on. In my case the file was named papilio_one_top.vhd. Other boards have similar names (e.g., s3e_eval_zpuino_top.vhd for Xilinx S3E Starter Board).

Modifying the Zpuino VHDL to Enable PPS

To enable PPS for your peripheral, first identify the signals being used for IO. Below is an example port mapping for a synchronous receiver/transmitter module I’ve written.

slot10: synchronous_rt port map (
  -- Wishbone interface.
  wb_clk_i  = wb_clk_i,
  wb_rst_i  = wb_rst_i,
  wb_dat_o  = slot_read(10),
  wb_dat_i  = slot_write(10),
  wb_adr_i  = slot_address(10),
  wb_we_i   = slot_we(10),
  wb_cyc_i  = slot_cyc(10),
  wb_stb_i  = slot_stb(10),
  wb_ack_o  = slot_ack(10),
  wb_inta_o = slot_interrupt(10),

  -- tx/rc interface.
  sync_tx_o = sync_tx_o,
  sync_tx_clk_i = sync_tx_clk_i,
  sync_rc_i = sync_rc_i,
  sync_rc_clk_i = sync_rc_clk_i

In this example the signals of interest are those in the tx/rc interface block. Signals ending in _o are outputs, signals ending in _i are inputs.

Open the papilio_one_top.vhd file in a text editor and scroll to the bottom where the process governing GPIO is located. This process contains two code blocks, one for the input pins, the other for the output pins. To use PPS with your peripheral, add each of the IO signals to the appropriate code block. Keep track of the indices assigned to the signals, you’ll need them to enable the pins in your source code. The process in my current core is show below as an example.

  gpio_spp_data = (others => DontCareValue);

  -- GPIO output pins.
  gpio_spp_data(0) = sigmadelta_spp_data(0); -- PPS0 : SIGMADELTA DATA
  gpio_spp_data(1) = timers_pwm(0); -- PPS1 : TIMER0
  gpio_spp_data(2) = timers_pwm(1); -- PPS2 : TIMER1
  gpio_spp_data(3) = spi2_mosi; -- PPS3 : USPI MOSI
  gpio_spp_data(4) = spi2_sck; -- PPS4 : USPI SCK
  gpio_spp_data(5) = sigmadelta_spp_data(1); -- PPS5 : SIGMADELTA1 DATA
  gpio_spp_data(6) = uart2_tx; -- PPS6 : UART2 DATA
  gpio_spp_data(7) = sync_tx_o; -- PPS7 : Synchronous transmit data
  gpio_spp_data(8) = adc_cs_o; -- PPS8 : ADC chip select
  gpio_spp_data(9) = adc_clk_o; -- PPS9 : ADC clock

  -- GPIO input pins.
  spi2_miso = gpio_spp_read(0); -- PPS0 : USPI MISO
  uart2_rx  = gpio_spp_read(1); -- PPS1 : UART2 receive
  sync_tx_clk_i = gpio_spp_read(2); -- PPS2 : Synchronous transmit clock
  sync_rc_i = gpio_spp_read(3); -- PPS3 : Synchronous receive data
  sync_rc_clk_i = gpio_spp_read(4); -- PPS4 : Synchronous receive clock
  adc_data0_i = gpio_spp_read(5); -- PPS5 : ADC data stream 0
  adc_data1_i = gpio_spp_read(6); -- PPS6 : ADC data stream 1
end process;

Using PPS In Your Source Code

Examples of how to use PPS in your source code are included in the Zpuino manual. The indices assigned to your peripheral’s signals are used in the outputPinForFunction() and inputPinForFunction() routines. So for example, in the VHDL code above the sync_tx_o signal was assigned index 7 and the sync_rc_i signal was assigned index 3. Therefore, your source code will have something like:

#define SYNC_TX 7
#define SYNC_RC 3

// Map SYNC_TX to pin 30
outputPinForFunction (30 , SYNC_TX);

// Map pin 10 to SYNC_RC
inputPinForFunction (10 , SYNC_RC);

Enabling PPS for your peripherals is a little bit of work but will make using them easier in the long run. This is especially true when it comes to developing hardware to go along with them so I encourage you to try it.

Tabula Candida

Doodles of a distracted historian


A blog about RTL-SDR (RTL2832) and cheap software defined radio

DuWayne's Place

Computers, Electronics, and Amateur Radio from KC3XM

QRP HomeBuilder - QRPHB -

Computers, Electronics, and Amateur Radio from KC3XM

Open Emitter

Computers, Electronics, and Amateur Radio from KC3XM

Ripples in the Ether

Emanations from Amateur Radio Station NT7S

m0xpd's 'Shack Nasties'

Computers, Electronics, and Amateur Radio from KC3XM

%d bloggers like this: