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.
process(gpio_spp_read, sigmadelta_spp_data, timers_pwm, spi2_mosi, spi2_sck, sync_tx_o, sync_tx_clk_i, sync_rc_i, sync_rc_clk_i) begin 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.