Continuation of Learning AMD Zynq: a project to generate a set of PWM signals. 1 - problem statement and possible approach .
yepe has a goal to create a set of signals for an ultrasone pulse generator.

Status after Post 1
In Post 1, we got the PWM and PWMN signals, and a first stab at the PULSE signal. Let's start at that stable point here, and try to add
- the initial delay between PRF falling edge and the start of the pulse train
- a PULSE signal that starts and ends exactly when the pulse train starts and ends.
Here's the design from the 1st blog:

The clocking wizard generates a 320 MHz clock. High enough to give the PWM signal a decent granularity for the dead times (2.5 ns steps).
The PWM generator generates 2 complementary signals, PWM and PWMN, with configurable deadband.
The PWM generator has an enable pin. When that pin is low, it will set PWM low and PWMN high, else it outputs the train of pulses
 
 
Goal of this post:
- Build a control block that will tell the PWM when to generate pulses (control its enable)
- let that block also control the PULSE signal
- establish the configurable? delay at the start
- maybe handle PRF?
What this session will not do: handle GATE
Same as in post 1, the action will be in the comments. It's a collaborative exercise.
Update 4/5/2023:
Possible signal controller design (partly - not all states and signals implemented)

library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
package PulsePckg is
    component signal_controller is
        generic (
            start_delay_resolution : integer := 6;  -- bit width of the counter used delaying pulse train start
            train_length           : integer := 7;  -- bit width of the PWM burst train counter
            gate_delay_resolution  : integer := 6;  -- bit width of the counter used delaying the gate signal start 
            counter_resolution     : integer := 7   -- big enought o hold the largest of above
        );
        port (
            n_reset_i     : in   std_logic;                                                -- async reset
            clk_i         : in   std_logic;                                                -- Input clock.
            start_delay_i : in   std_logic_vector (start_delay_resolution - 1 downto 0);   -- Duty-cycle input.
            train_length_i: in   std_logic_vector (train_length - 1 downto 0);             -- how many PWMs in a burst train.
            prime_i       : in   std_logic;                                                -- allow cycle start
            fire_i        : in   std_logic;                                                -- start a cycle
            prf_o         : out  std_logic;                                                -- PRF
            enable_o      : out  std_logic;                                                -- enable pwm
            pulse_o       : out  std_logic;                                                -- PULSE
            gate_o        : out  std_logic;                                                -- GATE
            leds_o        : out std_logic_vector (3 downto 0)
        );
    end component;
end package;
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;
entity signal_controller is
    generic (
        train_length           : integer := 7;  -- bit width of the PWM burst train counter
        start_delay_resolution : integer := 6;  -- bit width of the counter used delaying pulse train start
        gate_delay_resolution  : integer := 6;  -- bit width of the counter used delaying the gate signal start 
        counter_resolution     : integer := 7   -- big enought o hold the largest of above
    );
    port (
        n_reset_i     : in   std_logic;                                                -- async reset
        clk_i         : in   std_logic;                                                -- Input clock.
        start_delay_i : in   std_logic_vector (start_delay_resolution - 1 downto 0);   -- Duty-cycle input.
        train_length_i: in   std_logic_vector (train_length - 1 downto 0);             -- how many PWMs in a burst train.
        prime_i       : in   std_logic;                                                -- allow cycle start
        fire_i        : in   std_logic;                                                -- start a cycle
        prf_o         : out  std_logic;                                                -- PRF
        enable_o      : out  std_logic;                                                -- enable pwm
        pulse_o       : out  std_logic;                                                -- PULSE
        gate_o        : out  std_logic;                                                -- GATE
        leds_o        : out std_logic_vector (3 downto 0)
    );
end signal_controller;
architecture arch of signal_controller is
    type states is (ready, delay, pulse, gate_delay, gate, done );
    signal counter : integer range 0 to 2**counter_resolution-1;
    signal state  : states;
begin
    clocked: process(clk_i)
    begin
        if rising_edge(clk_i) then
            -- sync reset
            if n_reset_i = '0' then
                counter <= 0;
                state <= ready;
                -- reset output states
                prf_o <= '1';
                enable_o <= '0';
                pulse_o <= '0';
                gate_o <= '0';
                leds_o <= (others => '0');
            else
                counter <= counter + 1;
                -- default output states
                prf_o <= '1';
                enable_o <= '0';
                pulse_o <= '0';
                gate_o <= '0';
                leds_o <= (others => '0');
                case State is
                    when ready =>  -- start a pulse group as soon as the fire bit is set
                        leds_o <= (0 => '1', others => '0');
                        -- check if the caller has reset the prime bit
                        if prime_i = '0' then
                            -- if yes, start if the fire bit is set.
                            if fire_i = '1' then
                                counter <= 0;
                                state <= delay;
                            end if;
                        end if;
                    when delay =>
                        leds_o <= (0 => '0', 1 => '1', others => '0');
                        prf_o <= '0';
                        if counter >= to_integer(unsigned(start_delay_i)) - 1 then
                            counter <= 0;
                            enable_o <= '1';
                            state <= pulse;
                        end if;
                    when pulse =>
                        leds_o <= (0 => '1', 1 => '1', others => '0');
                        enable_o <= '1';
                        prf_o <= '0';
                        if counter < to_integer(unsigned(train_length_i)) - 1 then  -- let the pulse change sync with last edge of PWM
                            pulse_o <= '1';
                        elsif counter = to_integer(unsigned(train_length_i)) - 1 then
                            pulse_o <= '1';
                            counter <= 0;
                            state <= gate_delay;
                        end if;
                    when gate_delay =>
                        leds_o <= (0 => '0', 1 => '0', 2 => '1', others => '0');
                        state <= gate;  -- todo implement
                    when gate =>
                        leds_o <= (0 => '1', 1 => '0', 2 => '1', others => '0');
                        state <= done;  -- todo implement
                    when done =>
                        leds_o <= (0 => '1', 1 => '1', 2 => '1', others => '0');
                        -- check if the caller has reset the fire bit
                        if fire_i = '0' then
                            -- if yes, prime if the prime_bit is set.
                            if prime_i = '1' then
                                counter <= 0;
                                state <= ready;
                            end if;
                        end if;
                end case;
            end if; -- sync reset
        end if; -- rising_edge
    end process clocked;
end architecture;
warning: the train length should always result in full PWM cycles. If you disable the PWM module before it had the chance to generate a full cycle, both PWM and PWMN will switch at the same time and not respect the deadband.
Block diagram:

Capture:

Jupyter:

Jupyter source:
{
 "cells": [
  {
   "cell_type": "markdown",
   "metadata": {},
   "source": [
    "# PWM with generic dead band and duty cycle resolution\n",
    "\n",
    "https://community.element14.com/technologies/fpga-group/b/blog/posts/learning-xilinx-zynq-a-school-project-to-generate-a-set-of-pwm-signals-1---problem-statement-and-possible-approach\n",
    "\n",
    "pwm_ultrasound_pulser.xpr"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 70,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pynq import Overlay\n",
    "ol=Overlay(\"pwm_ultrasound_pulser.bit\")"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 71,
   "metadata": {},
   "outputs": [],
   "source": [
    "from pynq import MMIO\n",
    "RANGE = 8 # Number of bytes; 8/4 = 2x 32-bit locations which is all we need for this example"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 72,
   "metadata": {},
   "outputs": [],
   "source": [
    "duty_address = ol.ip_dict['axi_gpio_duty']['phys_addr']\n",
    "duty_register = MMIO(duty_address, RANGE) \n",
    "# Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs.\n",
    "duty_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 73,
   "metadata": {},
   "outputs": [],
   "source": [
    "band_address = ol.ip_dict['axi_gpio_band']['phys_addr']\n",
    "band_register = MMIO(band_address, RANGE) \n",
    "# Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs.\n",
    "band_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 74,
   "metadata": {},
   "outputs": [],
   "source": [
    "flags_address = ol.ip_dict['axi_gpio_flags']['phys_addr']\n",
    "flags_register = MMIO(flags_address, RANGE) \n",
    "# Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs.\n",
    "flags_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 75,
   "metadata": {},
   "outputs": [],
   "source": [
    "start_delay_address = ol.ip_dict['axi_gpio_start_delay']['phys_addr']\n",
    "start_delay_register = MMIO(start_delay_address, RANGE) \n",
    "# Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs.\n",
    "start_delay_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 76,
   "metadata": {},
   "outputs": [],
   "source": [
    "train_length_address = ol.ip_dict['axi_gpio_train_length']['phys_addr']\n",
    "train_length_register = MMIO(train_length_address, RANGE) \n",
    "# Write 0x00 to the tri-state register at offset 0x4 to configure the IO as outputs.\n",
    "train_length_register.write(0x4, 0x0) # Write 0x0 to location 0x4; Set tri-state to output"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 77,
   "metadata": {},
   "outputs": [],
   "source": [
    "def duty(duty):\n",
    "    duty_register.write(0x00, duty)\n",
    "    \n",
    "def band(band):\n",
    "    band_register.write(0x00, band)\n",
    "    \n",
    "def dutypct(duty):\n",
    "    duty_register.write(0x00, round((0x1F*2)/(100/duty)))\n",
    "    \n",
    "def fire():\n",
    "    flags_register.write(0x00, 1) # bit 0\n",
    "    flags_register.write(0x00, 0)\n",
    "\n",
    "def prime():\n",
    "    flags_register.write(0x00, 2) # bit 1\n",
    "    flags_register.write(0x00, 0)\n",
    "    \n",
    "def startdelay(startdelay):\n",
    "    start_delay_register.write(0x0, startdelay);\n",
    "\n",
    "def trainlength(trainlength):\n",
    "    train_length_register.write(0x0, trainlength);\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 7,
   "metadata": {},
   "outputs": [],
   "source": [
    "duty(0x1F)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 8,
   "metadata": {},
   "outputs": [],
   "source": [
    "dutypct(50)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 110,
   "metadata": {},
   "outputs": [],
   "source": [
    "band(2)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 10,
   "metadata": {},
   "outputs": [],
   "source": [
    "startdelay(20)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 87,
   "metadata": {},
   "outputs": [],
   "source": [
    "trainlength(125)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 90,
   "metadata": {},
   "outputs": [],
   "source": [
    "prime()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 91,
   "metadata": {},
   "outputs": [],
   "source": [
    "fire()"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 115,
   "metadata": {},
   "outputs": [],
   "source": [
    "trainlength(125)\n",
    "prime()\n",
    "fire()"
   ]
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "Python 3 (ipykernel)",
   "language": "python",
   "name": "python3"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.10.4"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 2
}
Scope capture (PWM, PWMN, PULSE, PRF)
0 dead time:
2 dead time:
Compare to requirement:

Example with longer initial delay and a longer pulse train, 5 pulses of dead band. The FPGA design is the same. This is done by writing different values to the registers:
Study of the edges. You see the PWM and PULSE signal, 0 dead band.
A few example settings that generate complete pulses. The first two generate 2 pulses, with a short start delay. The second 2 have a longer start delay and generate 5 pulses. The last one is retriggered in a software loop. I've also played with the deadbands.
Vivado project and Jupyter notebook are attached. They are for version 2022.1 and PYNQ-Z2 board
pwm_ultrasound_pulser_and_jupyter_20230506.zip
 
			     
             
				 
	
Top Comments