Extending the ZPUino

Posted by: Dave Vandenbout 5 years, 6 months ago

(2 comments)

In previous posts, I've introduced the ZPUino softcore processor and shown how to build the base instance for an FPGA. But the real advantage of the ZPUino is being able to extend it by attaching new peripherals to the core processor in the VHDL description. The best way to learn something is by doing it, so in this post I'll show how to integrate a simple RGB LED driver peripheral into the ZPUino.

The ZPUino architecture incorporates 16 slots that can be populated with peripherals. A particular slot is activated when the ZPUino core processor accesses an I/O address within the range of addresses assigned to that slot. Then the peripheral in that slot can pass data back-and-forth with the processor through a Wishbone interface that consists of parallel address and data buses along with control signals and a handshake synchronization protocol.

The Wishbone interfaces to peripherals are instantiated in the top-level module of the ZPUino VHDL code. Within the module are empty slots like this:

--
-- IO SLOT 9
--

slot9: zpuino_empty_device
port map (
  wb_clk_i  => wb_clk_i,          -- Clock to peripheral.
  wb_rst_i  => wb_rst_i,          -- Reset to peripheral.
  wb_dat_o  => slot_read(9),      -- Data from peripheral.
  wb_dat_i  => slot_write(9),     -- Data to peripheral.
  wb_adr_i  => slot_address(9),   -- Address to peripheral.
  wb_we_i   => slot_we(9),        -- Write-enable to peripheral.
  wb_cyc_i  => slot_cyc(9),       -- Cycle handshake to peripheral.
  wb_stb_i  => slot_stb(9),       -- Chip-select to peripheral.
  wb_ack_o  => slot_ack(9),       -- handshake acknowledge from peripheral.
  wb_inta_o => slot_interrupt(9), -- Interrupt from peripheral.
  id        => slot_ids(9)        -- Peripheral ID (not part of Wishbone).
);

All the signals are part of the Wishbone interface except for id which outputs a peripheral-specific code that the ZPUino processor can use to associate a peripheral performing a specific function (e.g., a UART) with software drivers for that function.

I constructed the RGB peripheral according to the following block diagram:

Block diagram for a Wishbone-compliant RGB LED driver peripheral.

The Wishbone interface allows the processor to read or write a single 32-bit register at address 0. Each of the lower three bytes of this register controls the duty-cycle of a pulse-width modulator (PWM). Varying the duty cycles of the PWMs will change the intensities of the red, green and blue components which changes the overall color of the RGB LED. For the excruciating operational details, check out the VHDL for the PWM and the RGB peripheral.

Next, I instantiated the RGB peripheral in the top-level ZPUino module as follows:

--
-- IO SLOT 9
--

slot9: WbRgbLed
port map (
  wb_clk_i      => wb_clk_i,
  wb_rst_i      => wb_rst_i,
  wb_dat_o      => slot_read(9),
  wb_dat_i      => slot_write(9),
  wb_adr_i      => slot_address(9),
  wb_we_i       => slot_we(9),
  wb_cyc_i      => slot_cyc(9),
  wb_stb_i      => slot_stb(9),
  wb_ack_o      => slot_ack(9),
  wb_inta_o     => slot_interrupt(9),
  id            => slot_ids(9),
  redLed_o      => led_r, -- Output for driving red led.
  grnLed_o      => led_g, -- Output for driving green led.
  bluLed_o      => led_b  -- Output for driving blue led.
);

The led_r, led_g, and led_b output signals have to be attached to pins of the ZPUino in order to connect to the physical RGB LED. I could have made these pin assignments statically, but the ZPUino provides a much more flexible solution with its Peripheral Pin Select (PPS) mechanism. The PPS is basically a collection of multiplexers that allow any one of N outputs to be attached to any one of M pins under software control. (There is an equivalent PPS block for redirecting the inputs.) So with the PPS, I can assign the ZPUino pins that output the RGB PWM signals at run-time.

PPS output block diagram.

Within the top-level ZPUino VHDL module, I attached the RGB peripheral outputs to the output PPS as follows:

process(gpio_spp_read, spi_pf_mosi, spi_pf_sck,
        sigmadelta_spp_data,timers_pwm,
        spi2_mosi, spi2_sck, led_r, led_g, led_b )
begin

  gpio_spp_data <= (others => DontCareValue);

  -- PPS Outputs
  gpio_spp_data(0)  <= sigmadelta_spp_data(0);   -- PPS0 : SIGMADELTA DATA
  ppsout_info_slot(0) <= 5; -- Slot 5
  ppsout_info_pin(0) <= 0;  -- PPS OUT pin 0 (Channel 0)
  .
  .
  .
  gpio_spp_data(6)  <= uart2_tx;   -- PPS6 : UART2 TX
  ppsout_info_slot(6) <= 8; -- Slot 8
  ppsout_info_pin(6) <= 0;  -- PPS OUT pin 0 (Channel 1)

  -- Attach the RGB LED driver outputs to the PPS outputs. 
  gpio_spp_data(7)  <= led_r;   -- PPS7 : Red LED driver
  gpio_spp_data(8)  <= led_g;   -- PPS8 : Green LED driver
  gpio_spp_data(9)  <= led_b;   -- PPS9 : Blue LED driver

  -- PPS inputs
  spi2_miso         <= gpio_spp_read(0);         -- PPS0 : USPI MISO
  ppsin_info_slot(0) <= 6;                    -- USPI is in slot 6
  ppsin_info_pin(0) <= 0;                     -- PPS pin of USPI is 0

  uart2_rx          <= gpio_spp_read(1);         -- PPS1 : UART2 RX
  ppsin_info_slot(1) <= 8;                    -- USPI is in slot 6
  ppsin_info_pin(1) <= 0;                     -- PPS pin of USPI is 0

end process;

In order to make room in the PPS for the new outputs, I bumped-up the PPSCOUNT_OUT constant in the zpuino_config.vhd file:

-- Set this to the max. number of output pps on the system
constant PPSCOUNT_OUT: integer := 10; -- Increased to handle RGB LED drivers.

After making these changes, I rebuilt the ZPUino FPGA bitstream with the newly-added RGB peripheral. Now it's just a matter of writing the software for accessing the new peripheral. I'll demonstrate this by writing an example program that ramps-up and then fades an RGB LED over a sequence of colors.

The program starts (as most do) with a set of definitions. This is where I tell the program about the decisions I made in the VHDL code: the location of the RGB peripheral (slot 9), the address offset of the color register (0), and the position of the red, green and blue outputs going into the PPS block (7, 8, and 9). I also specify the ZPUino pins that the RGB LED will be connected to (5, 4 and 3).

// Slot the RGB peripheral is assigned to.
#define RGB_SLOT 9
// Address of the color register in the RGB peripheral.
#define COLOR_REG 0
// Position of the red, green and blue PWM outputs
// within the PPS output block.
#define RED_PPS_POS 7
#define GRN_PPS_POS 8
#define BLU_PPS_POS 9
// ZPUino pins for driving an RGB LED.
#define RED_PIN 5
#define GRN_PIN 4
#define BLU_PIN 3

Next, I use these definitions in the setup() routine. The ZPUino pins driving the RGB LED are enabled as outputs, and these outputs are driven by the multiplexers from the PPS. Then each multiplexer is programmed to output one of the PWM signals from the RGB peripheral.

void setup() {
  // Enable the outputs to the RGB LED.
  pinMode(RED_PIN, OUTPUT);
  pinMode(GRN_PIN, OUTPUT);
  pinMode(BLU_PIN, OUTPUT);
  // Enable the peripheral pin multiplexer for the outputs.
  pinModePPS(RED_PIN, HIGH);
  pinModePPS(GRN_PIN, HIGH);
  pinModePPS(BLU_PIN, HIGH);
  // Map the PWM outputs to the RGB output pins.
  outputPinForFunction(RED_PIN, RED_PPS_POS);
  outputPinForFunction(GRN_PIN, GRN_PPS_POS);
  outputPinForFunction(BLU_PIN, BLU_PPS_POS);
}

Then there's the little set_rgb_color() subroutine that accepts a 24-bit color value and writes it to the color register within the RGB peripheral. The IO_SLOT(RGB_SLOT) macro-call generates the base address for the RGB peripheral. Then the REGISTER(...) macro-call adds the offset of the color register to the base address to generate the final address where the new color value is written.

void set_rgb_color(unsigned int color)
{
  REGISTER(IO_SLOT(RGB_SLOT), COLOR_REG) = color;
}

The meat of the example program is contained in the brighten_then_fade() subroutine that ramps the RGB LED up to its maximum color intensity and then down to zero. It accepts a color_increment argument that contains either a 0 or a 1 in each of its eight-bit fields. For example, the value 0x000101 would change both the red and green fields while leaving the blue component alone. Starting from zero, this argument is added to the RGB color until it reaches its maximum intensity (0x00FFFF for the example value of the color_increment). Then the argument is subtracted from the color until it again reaches zero.

void brighten_then_fade(unsigned int color_increment)
{
  // Start at black and ramp up color intensity.
  unsigned int color = 0;
  for(int i=0; i<256; i++)
  {
    set_rgb_color(color);
    color += color_increment;
    delay(3);
  }
  // Stay at maximum intensity for a while.
  delay(200);
  // Ramp back down to black.
  for(int i=0; i<255; i++)
  {
    color -= color_increment;
    set_rgb_color(color);
    delay(3);
  }
}

Finally, there's the never-ending loop() routine that applies the previous subroutine to a sequence of colors:

void loop() {
  // Color sequence: red, green, blue, yellow, magenta, cyan and white. 
  unsigned int color_increment[] = {0x000001, 0x000100, 0x010000, 0x000101, 0x010001, 0x010100, 0x010101};
  // Brighten and then fade each color in the sequence.
  for(int i=0; i<sizeof(color_increment)/sizeof(unsigned int); i++)
    brighten_then_fade(color_increment[i]);
}

When I run the program on the extended ZPUino, I get the expected output on the LED as shown here:

So to recap, I added a new peripheral to the ZPUino by:

  • Describing the peripheral using VHDL.
  • Putting a Wishbone interface on it.
  • Instantiating the peripheral in one of the empty slots in the top-level ZPUino module.
  • Adding input and output signals of the peripheral to the PPS blocks.
  • Writing ZPUino code using the IO_SLOT and REGISTER macros to access the peripheral.

That's it!

Current rating: 5


Comments

  • Robert Pflaum 5 years, 5 months ago

    Dave,

    I have been following this series on ZPUINO 2.0 . However the source code I have does not appear to be the same as you describe. I do not see the ID output in the instantiation. I used the link you provided earlier in this series to download the source. It appears to be the source for SOURCE 1.0 not 2.0. What have I done wrong?

    Link / Reply
  • Dave Vandenbout 5 years, 5 months ago

    Hi, Robert. I got bit by this same problem. Before you download the source, make sure to select the correct branch in the drop-down list in the upper left of the webpage. For the RGB LED example, that would be "RGB_LED_Example". If you want one of the previous implementations, then the "zpuino2_xula2_usb_uart" would also contain the ZPUino 2.0 code.

    Link / Reply

New Comment

required
required (not published)
optional

Recent Posts

Archive

2016
2015
2014
2013
2012
2011

Categories

Authors

Feeds

RSS / Atom