User Tools

Site Tools


usersguide:devguide

Differences

This shows you the differences between two versions of the page.

Link to this comparison view

Both sides previous revision Previous revision
Next revision
Previous revision
Next revision Both sides next revision
usersguide:devguide [2022/03/11 17:45]
dpisuperadmin
usersguide:devguide [2022/03/22 04:36]
dpisuperadmin
Line 5: Line 5:
   *  how to get started with Verilog,   *  how to get started with Verilog,
   *  how to create a new Wishbone peripheral,   *  how to create a new Wishbone peripheral,
-  *  how to add a new peripheral driver module, and +  *  how the DPCore build system works, 
-  *  how the DPCore host interface works+  *  how to add a new peripheral driver module
  
  
Line 16: Line 16:
   * compile your design and test it on the Baseboard   * compile your design and test it on the Baseboard
  
-=== "Hello World" in Verilog ===+==== "Hello World" in Verilog ====
 Most programming languages have a sample application that prints the phrase "Hello, World!" to the console. This application is often used to validate the installation of the language and its tool chain.  For microcontrollers and FPGAs the equivalent application usually flashes an LED on the development board.  The Verilog program below implements a counter on the Baseboard LEDs.  Save the following as counter.v Most programming languages have a sample application that prints the phrase "Hello, World!" to the console. This application is often used to validate the installation of the language and its tool chain.  For microcontrollers and FPGAs the equivalent application usually flashes an LED on the development board.  The Verilog program below implements a counter on the Baseboard LEDs.  Save the following as counter.v
     // Simple up counter for the Baseboard.     // Simple up counter for the Baseboard.
Line 118: Line 118:
 {{ :usersguide:counter_leds.png?600 |}} {{ :usersguide:counter_leds.png?600 |}}
  
-=== Install and Test the Xilinx Toolchain ===+==== Install and Test the Xilinx Toolchain ====
 Once your simulation output is correct you are ready to compile and download your design to the FPGA. Once your simulation output is correct you are ready to compile and download your design to the FPGA.
 This section describes how to install the Xilinx FPGA design tools, how to use the Xilinx command line tools to compile a Verilog design, and how to download the compiled code to the Baseboard.  A later section will describe how to automate all these steps in a Makefile. This section describes how to install the Xilinx FPGA design tools, how to use the Xilinx command line tools to compile a Verilog design, and how to download the compiled code to the Baseboard.  A later section will describe how to automate all these steps in a Makefile.
Line 128: Line 128:
 Start by going to the Xilinx download site at: http://www.xilinx.com/support/download/index.htm.  Click on "ISE Archive" link and select "14.7" and then "Full Installer for Linux" This will take you to a login page where you can select "Create Account" (since you probably don't already have a Xilinx account).  You activate the account using a token sent in email.  Your first login will present a page asking you to verify your name and address.  The download starts automatically after selecting Download at the bottom of the name verification page. Start by going to the Xilinx download site at: http://www.xilinx.com/support/download/index.htm.  Click on "ISE Archive" link and select "14.7" and then "Full Installer for Linux" This will take you to a login page where you can select "Create Account" (since you probably don't already have a Xilinx account).  You activate the account using a token sent in email.  Your first login will present a page asking you to verify your name and address.  The download starts automatically after selecting Download at the bottom of the name verification page.
  
-Install the software by untarring the download file and running the "xsetup" script in the top level directory.  If installing as a non-root user, you might want to create /opt/Xilinx/14.7 beforehand and give yourself write permission on it.+Install the software by untarring the download file and running the "xsetup" script in the top level directory.  If installing as a non-root user, you might want to create /opt/Xilinx/14.7 beforehand and give yourself write permission on it.  You should be able to install ISE in a virtual machine but it might not install correctly in a docker image.
  
 The installation will ask which products to install.  We suggest the "ISE WebPACK" as it is the smallest and has everything you'll need.  You need to "Acquire or Manage a License Key" but you do need to install the Cable Drivers.  Selecting Next then Install should start the installation. The installation will ask which products to install.  We suggest the "ISE WebPACK" as it is the smallest and has everything you'll need.  You need to "Acquire or Manage a License Key" but you do need to install the Cable Drivers.  Selecting Next then Install should start the installation.
Line 134: Line 134:
 Once the installation is complete you can add the Xilinx Verilog compiler toolchain to you path and verify that it can be found with the commands: Once the installation is complete you can add the Xilinx Verilog compiler toolchain to you path and verify that it can be found with the commands:
     export PATH=$PATH:/opt/Xilinx/14.7/ISE_DS/ISE/bin/lin64     export PATH=$PATH:/opt/Xilinx/14.7/ISE_DS/ISE/bin/lin64
-    which ise+    which xst
  
 By default, ise opens a graphical integrated development environment.  DPcore is //make// based and you do not need to learn the IDE.  You may recall that compiling a C++ or C program is broken into the steps of preprocessing, compiler pass 1, compiler pass 2, assembly, and linking.  All these steps occur even though you only type g++ or gcc.  In the same way, Verilog is compiled to binary in several steps. By default, ise opens a graphical integrated development environment.  DPcore is //make// based and you do not need to learn the IDE.  You may recall that compiling a C++ or C program is broken into the steps of preprocessing, compiler pass 1, compiler pass 2, assembly, and linking.  All these steps occur even though you only type g++ or gcc.  In the same way, Verilog is compiled to binary in several steps.
Line 191: Line 191:
     promgen -w -p bin -o counter.bin -u 0 counter.bit     promgen -w -p bin -o counter.bin -u 0 counter.bit
  
-=== Download Your Design to the Baseboard === +==== Download Your Design to the Baseboard ==== 
-When the Baseboard powers up or after pressing the reset button the FPGA waits for an binary image from the serial port. Linux serial port drivers can suppress certain characters from an output stream.  To prevent this you need to turn off post processing on the serial port.with the command+When the Baseboard powers up or after pressing the reset button the FPGA waits for an binary image from the serial port. Linux serial port drivers can suppress certain characters from an output stream.  To prevent this you need to turn off post processing on the serial port.with the commands
-     sudo stty --file=/dev/ttyUSB0 -opost  # We want raw output+    sudo addgroup $LOGNAME dialout 
 +    stty --file=/dev/ttyUSB0 -opost  # We want raw output
  
 Press the reset button and send the FPGA binary to the Baseboard with the command: Press the reset button and send the FPGA binary to the Baseboard with the command:
Line 201: Line 202:
  
  
-==== How to Write a Wishbone Peripheral ====+===== How to Write a Wishbone Peripheral =====
 In this section you will see how to build your own custom Verilog peripheral.  To appreciate this section you should already be familiar with digital circuit design and Verilog. This section is broken into three topics: In this section you will see how to build your own custom Verilog peripheral.  To appreciate this section you should already be familiar with digital circuit design and Verilog. This section is broken into three topics:
   * the DPcore Wishbone bus   * the DPcore Wishbone bus
Line 209: Line 210:
  
  
-=== The DPCore Wishbone Bus ===+==== The DPCore Wishbone Bus ====
 A Wishbone Bus is a synchronous, parallel data bus intended to connect on-chip peripherals to an on-chip CPU.  Wishbone describes both the interface signals to the peripherals as well as the how the peripherals are connected to each other and to the CPU.  The full specification is at [[https://cdn.opencores.org/downloads/wbspec_b4.pdf|Wishbone specification]].  Wishbone is a common interface for many of the project at [[https://opencores.org|Opencores]]. A Wishbone Bus is a synchronous, parallel data bus intended to connect on-chip peripherals to an on-chip CPU.  Wishbone describes both the interface signals to the peripherals as well as the how the peripherals are connected to each other and to the CPU.  The full specification is at [[https://cdn.opencores.org/downloads/wbspec_b4.pdf|Wishbone specification]].  Wishbone is a common interface for many of the project at [[https://opencores.org|Opencores]].
  
Line 266: Line 267:
  
  
-=== Clone an Existing Peripheral ===+==== Clone an Existing Peripheral ====
 It should be no surprise that the easiest way to build a new peripherals is to base it on an existing one.  This section shows you how to do this. It should be no surprise that the easiest way to build a new peripherals is to base it on an existing one.  This section shows you how to do this.
  
Line 282: Line 283:
     sudo make install     sudo make install
     # start dpdaemon and test Baseboard LEDs     # start dpdaemon and test Baseboard LEDs
 +    # (use sudo for the following if not in dialout group)
     dpdaemon -l /usr/local/lib/DPCore.bin -s /dev/ttyUSB0     dpdaemon -l /usr/local/lib/DPCore.bin -s /dev/ttyUSB0
-    /usr/local/bin/dpset bb4io leds 55+    dpset bb4io leds 55
  
 With everything built from source, you can now start adding your own code.  Move to the DPCore.src directory and copy gpio4.v to myperi.v, where you can replace "myperi" with the name for your new peripheral. Edit the file to change all references of "gpio4" to "myperi". With everything built from source, you can now start adding your own code.  Move to the DPCore.src directory and copy gpio4.v to myperi.v, where you can replace "myperi" with the name for your new peripheral. Edit the file to change all references of "gpio4" to "myperi".
Line 302: Line 304:
     pslot->name = "myperi";     pslot->name = "myperi";
  
-Build, install, and run dpdaemon as you did earlier.  Be sure to kill any running instances of dpdaemon before starting a new instance.+Build, install, and run dpdaemon as you did earlier.  Be sure to kill any running instances of dpdaemon before starting a new instance.  Use sudo to run dpdaemon or add yourself to the dialout group.
     cd dpdaemon     cd dpdaemon
     make     make
     sudo make install     sudo make install
-    dpdaemon -l /usr/local/lib/DPCore.bin -s /dev/ttyUSB0+    dpdaemon -l /usr/local/lib/DPCore.bin
     dplist     dplist
  
 If all has gone well the list of peripherals should now include your new peripheral name.  This might a good time to do a git commit. If all has gone well the list of peripherals should now include your new peripheral name.  This might a good time to do a git commit.
  
-=== Design Tips for a DPCore Peripheral ===+==== Design Tips for a DPCore Peripheral ====
 This guide can not give you specific advice about your new peripheral but we can give some tips for its design and coding. This guide can not give you specific advice about your new peripheral but we can give some tips for its design and coding.
  
Line 362: Line 364:
 and is equal to 8 if it is a poll bus cycle and there is data ready for the host.  The"8" in the above code tell the bus controller how many bytes to send to the host.  It might not be obvious but the peripheral returns zero on a poll when it does not have data ready for the host.  In an auto send the bus controller reads consecutive registers starting at register zero.  For the above example this would mean reading registers 0 through 7 in the auto send response. and is equal to 8 if it is a poll bus cycle and there is data ready for the host.  The"8" in the above code tell the bus controller how many bytes to send to the host.  It might not be obvious but the peripheral returns zero on a poll when it does not have data ready for the host.  In an auto send the bus controller reads consecutive registers starting at register zero.  For the above example this would mean reading registers 0 through 7 in the auto send response.
  
-=== Debug Your Peripheral with Iverilog ===+==== Debug Your Peripheral with Iverilog ====
 Having done it both ways, this author can attest to the fact that it is //much// easier to debug a new peripheral using a simulator.  This section gives sample code and a few tips for debugging you peripheral using iverilog. Having done it both ways, this author can attest to the fact that it is //much// easier to debug a new peripheral using a simulator.  This section gives sample code and a few tips for debugging you peripheral using iverilog.
  
Line 480: Line 482:
 The code in this section has been take in part from the sr04 test bench.  Hopefully you will not have too much difficulty modifying it for your peripheral. The code in this section has been take in part from the sr04 test bench.  Hopefully you will not have too much difficulty modifying it for your peripheral.
  
-===== How to Add a New Peripheral Driver Module ===== 
  
-===== How the DPCore Host Interface works =====+ 
 +/* 
 +===== How the DPCore Build System Works =====
  
 {{ :usersguide:pc_interconnect.png?400 |}} {{ :usersguide:pc_interconnect.png?400 |}}
 +*/
 +
 +
 +
 +===== How to Add a New Peripheral Driver Module =====
 +The next step after adding and testing your Verilog peripheral is to write a driver for it.  This section describes the common features of the drivers and offers some tips that might simplify your driver.
 +
 +We use the term "driver" but do not confuse these with real Linux kernel drivers.  Driver is the right concept but technically our drivers are loadable plug-in modules implemented as shared-object files.  Our existing drivers all use C but you can use any language that can produce a shared-object file.  C, C++, and Rust are all good choices.
 +
 +The code structure of drivers is fairly consistent from one driver to the next.  This make the documentation describing it all the more important.  Your file header block should start with copyright and license information.  Since the driver connects the dpset/dpget API to the registers you should include a description of the API as if you were describing to someone who had never seen it before.  This is where you answer the reader's question of "what does it do?" Next describe the registers and the meaning, if appropriate, of all of the bits in the registers.  The final piece is a description of how the API values relate to the register values.  The API-to-register documentation will make your driver much easier to maintain when you come back to it later.
 +
 +DPCore drivers are event driven and have to deal with three events: creation, an API command from the user, and arrival of a packet from the FPGA. These three events are handled by Initialize(), which is executed when the module is attached to the daemon, usercmd() which is a callback invoked for the API commands dpset, dpget, and dpcat, and packet_hdlr() which is a callback that is executed when a packet arrives from the FPGA.
 +
 +
 +
 +==== Initialize() ====
 +To understand how to load a driver into dpdaemon you should, perhaps, have some understanding of how dpdaemon works.
 +
 +The core of dpdaemon is a list of slots.  Each slot has a SLOT structure (includes/daemon.h) which has the information needed to manage the peripheral in that slot.  SLOT has the number of the slot, the name of the shared object file, and an array of resources (RSC in includes/daemon.h) for the peripheral. //Resources//, you may recall, is the generic term given to the attributes and data endpoints of the peripheral. 
 +
 +Peripheral #0 in the FPGA binary is the //enumerator//. This  is just a copy of the perilist configuration file used to build the FPGA binary.  When dpdaemon starts it loads the enumerator driver and reads the list of peripherals in the FPGA. It then loops through the list trying to load the shared object driver for each peripheral.  When the driver is loaded dpdaemon looks up and calls the Initialize() routine in the driver. (Look for dlsym() in daemon/ui.c to see how this works.)  The goal of Initialize() is to give dpdaemon (i.e. the SLOT structure) everything it needs to manage the peripheral.
 +
 +Dpdaemon can have multiple instances of the same peripheral.  This implies that an instance's internal state must be kept separate from the internal state of all other instances.  To do this you should create a structure or object that holds your peripheral internal state.  For example, the gpio4 peripheral keeps the following state information:
 +        // All state info for an instance of an gpio4
 +    typedef struct
 +    {
 +        void    *pslot;    // handle to peripheral's slot info
 +        int      pinval;   // value of the (output) pins
 +        int      dir;      // pin direction (in=0, out=1)
 +        int      intr;     // autosend on change (no=0, yes=1)
 +        void    *ptimer;   // timer to watch for dropped ACK packets
 +    } GPIO4DEV;
 +
 +The Initialize() routine is passed a pointer to its allocated SLOT structure (SLOT *pslot). 
 +Allocate memory for your peripheral state information and attach it to the SLOT structure with:
 +    MYPERIDEV *pctx;    // our local device context
 +    
 +    // Allocate memory for this peripheral
 +    pctx = (MYPERIDEV *) malloc(sizeof(MYPERIDEV));
 +    if (pctx == (MYPERIDEV *) 0) {
 +        // Malloc failure this early?
 +        dplog("memory allocation failure in myperi initialization");
 +        return (-1);
 +    }
 +    pslot->priv = pctx;
 +
 +While not a hard requirement, generally the above is the only time your driver should allocate memory.
 +
 +    // Register this slot's packet callback (pcb).
 +    // Set its name, description and help text.
 +    (pslot->pcore)->pcb  = packet_hdlr;
 +    pslot->name = "myperi";
 +    pslot->desc = "Quad General Purpose Great Peripheral";
 +    pslot->help = README;
 +
 +The help text is stored in the readme.txt file and is converted to readme.h as part of the build process.  Be sure to give your readme.txt file a high level description of the peripheral and a detailed description of all of your peripherals resources.  You can help your users a lot by including examples that can be cut-and-pasted in a shell and will always work.
 +
 +The Initialize() routine is where you set the name and properties of your resources.  The pointer to the get/set callback (pgscb) can be unique to each resource or can point to one routine that handles all user API calls.  Over time we have found that having one API callback is easier to understand and maintain, especially for simple peripherals.  Your resource definitions might appear something like this:    
 +    // Add the handlers for the user visible resources
 +    pslot->rsc[RSC_PINS].name = FN_PINS;
 +    pslot->rsc[RSC_PINS].flags = IS_READABLE | IS_WRITABLE | CAN_BROADCAST;
 +    pslot->rsc[RSC_PINS].bkey = 0;
 +    pslot->rsc[RSC_PINS].pgscb = usercmd;
 +    pslot->rsc[RSC_PINS].uilock = -1;
 +    pslot->rsc[RSC_PINS].slot = pslot;
 +    pslot->rsc[RSC_DIR].name = FN_DIR;
 +    pslot->rsc[RSC_DIR].flags = IS_READABLE | IS_WRITABLE;
 +    pslot->rsc[RSC_DIR].bkey = 0;
 +    pslot->rsc[RSC_DIR].pgscb = usercmd;
 +    pslot->rsc[RSC_DIR].uilock = -1;
 +    pslot->rsc[RSC_DIR].slot = pslot;
 +    pslot->rsc[RSC_INTR].name = FN_INTR;
 +    pslot->rsc[RSC_INTR].flags = IS_READABLE | IS_WRITABLE;
 +    pslot->rsc[RSC_INTR].bkey = 0;
 +    pslot->rsc[RSC_INTR].pgscb = usercmd;
 +    pslot->rsc[RSC_INTR].uilock = -1;
 +    pslot->rsc[RSC_INTR].slot = pslot;
 +
 +==== usercmd() ====
 +The usercmd() routine is where you convert your API calls to read and write resources into packets of register reads and writes.  
 +
 +The interface to dpdaemon is a TCP socket.  The daemon listens on the socket and accepts connections from application programs.  The application program sends lines of text in the form 
 +    [dpset|dpget|dpcat] [peri_name|slot_id] resource_name [resource_values]
 +The daemon parses line of input and rejects lines that do not match the above format.  The daemon checks for a valid peripheral name or slot ID, checks for a valid resource name, and verifies that the command (get/set/cat) is appropriate for the resource.  If everything is valid, the daemon calls your get/set callback.
 +
 +The daemon passs a lot of information into your callback, including the command (DPGET, DPSET, or DPCAT), the resource index you set in Initialize(), and the string of the new value,  There can be many instances of your peripheral, so the callback includes a SLOT pointer from which you can the the instance's private data structre.  Your response the the application that issued the command should be a newline terminated line of ASCII text.  The text goes into the 'buf' parameter and before returning you set *plen to the number of characters you put in buf.  You should be able to use the following exactly as it for your usercmd() callback.
 +    static void usercmd(
 +        int      cmd,        //==DPGET if a read, ==DPSET on write
 +        int      rscid,      // ID of resource being accessed
 +        char    *val,        // new value for the resource
 +        SLOT    *pslot,      // pointer to slot info.
 +        int      cn,         // Index into UI table for requesting conn
 +        int     *plen,       // size of buf on input, #char in buf on output
 +        char    *buf)
 +    {
 +
 +Usually the first thing to do is get the "local context" for this instance of your peripheral.  Do this with:
 +    pctx = (MYPERIDEV *) pslot->priv;
 +
 +Your code now needs to switch based on the resource and command.  A switch() statement works as does a string of if()/else if() statement.  Use your preferred coding style.  Long or complex calculations based on the user input might be moved to a separate routine to keep usercmd() simple and readable.  Your code might look something like the following:
 +
 +    if ((cmd == DPGET) && (rcsid == RSC_MYRSC1)) {
 +        ret = snprintf(buf, *plen, "%1x\n", pctx->intr);
 +        *plen = ret;  // (errors are handled in calling routine)
 +        return;
 +    }
 +    else if ((cmd == DPSET) && (rcsid == RSC_MYRSC1)) {
 +        ret = sscanf(val, "%x", &newrsc1);
 +        if ((ret != 1) || (newrsc1 < 0) || (newrsc1 > 0xf)) {
 +            ret = snprintf(buf, *plen,  E_BDVAL, pslot->rsc[rscid].name);
 +            *plen = ret;
 +            return;
 +        }
 +        pctx->rsc1 = newrsc1;
 +        sendconfigtofpga(pctx, plen, buf);  // send rsc1 and rsc2 to FPGA
 +    }
 +    else if ((cmd == DPSET) && (rcsid == RSC_MYRSC2)) {
 +        // Do a long or complex calculation in another routine
 +        newrsc2 = getrsc2(val);
 +    }
 +    else if ((cmd == DPCAT) && (rcsid == RSC_MYRSC3)) {
 +    .....
 +    }
 +
 +The above code shows how to respond to resource values that are out of range or otherwise invalid.  This code hides sending the packets to the FPGA.
 +
 +==== Sending Packets to the FPGA ====
 +The daemon and DPCore communicate using a packet based protocol which is defined in include/fpga.h.  You build a packet by setting the command, specifying the slot number, the register address, and the number of bytes in the data part of the packet.  Your code to build a packet might appear as follows:
 +    static void sendconfigtofpga(
 +        MYPERIDEV *pctx,   // This peripheral's context
 +        int     *plen,     // size of buf on input, #char in buf on output
 +        char    *buf)      // where to store user visible error messages
 +    {
 +        DP_PKT   pkt;      // send write and read cmds to the gpio4
 +        SLOT    *pslot;    // This peripheral's slot info
 +        CORE    *pmycore;  // FPGA peripheral info
 +        int      txret;    // ==0 if the packet went out OK
 +        int      ret;      // generic return value
 +    
 +        pslot = pctx->pslot;
 +        pmycore = pslot->pcore;
 +    
 +        // Write the values for the pins, direction, and interrupt mask
 +        // down to the card.
 +        pkt.cmd = DP_CMD_OP_WRITE | DP_CMD_AUTOINC;
 +        pkt.core = pmycore->core_id;
 +        pkt.reg = MYPERI_REG_RSC1;   // the first reg of the three
 +        pkt.data[0] = pctx->rsc1;
 +        pkt.data[1] = pctx->rsc2;
 +        pkt.data[2] = pctx->rsc3;
 +        pkt.count = 3;
 +        txret = dpi_tx_pkt(pmycore, &pkt, 4 + pkt.count); // 4 header + data
 +
 +
 +Some peripherals use a FIFO as a data endpoint.  In this case you would want to write all the bytes to one register.  Other peripherals have a string of registers that should be written secuentially.  This is referred to as "autoincrement" or "no autoincrement" Autoincrement can apply to both reading and writing registers so the four possibilities for the command are: 
 +    pkt.cmd = DP_CMD_OP_WRITE | DP_CMD_AUTOINC;
 +    pkt.cmd = DP_CMD_OP_WRITE | DP_CMD_NOAUTOINC;
 +    pkt.cmd = DP_CMD_OP_READ  | DP_CMD_AUTOINC;
 +    pkt.cmd = DP_CMD_OP_READ  | DP_CMD_NOAUTOINC;
 +
 +The routine to send a packet to the FPGA is dpi_tx_pkt().  You give it the peripheral address, the packet to send, and the total number of byte in the packet.  Dpi_tx_pkd() returns a success or failure indication.  You can use this to warn the user or to schedule another attempt.  Generally, something is seriously wrong if dpi_tx_pkt returns an error.
 +
 +
 +==== Handling Packets from the FPGA ====
 +When you initialized your peripheral instance you specified a packet receive callback.  Your callback should be able to handle three types of packets from the FPGA.  The first is an acknowledgement for a packet you sent.  Use this packet to stop the timeout timers if you haveset one.  Otherwise the acknowledgement can be ignored.
 +
 +The second kind of packet is a read response.  Validate the packet and then read and format the packet data to send to the application.  Data to the application must be formatted as an ASCII string terminated by a newline.  When an application gives a DP_GET command the daemon marks the TCP connection as waiting for data from your peripheral.  You send data back to the application using a call to send_ui().
 +
 +The third kind of packet is an autosend packet.  Recall that the FPGA does not have a interrupt line to the CPU and instead can automatically send packets up to the host.  The autosend packet is similar in structure to a read response packet.  The difference is the high bit of the ''cmd'' byte.  In a read response the bit is set and in an autosend packet the bit is cleared.  Autosend data is most often used with resources that support the DP_CAT command.  The publish subscribe system in dpdaemon allows multiple TCP connections to subscribe to the same resource.   The routine to publish autosend data is the bcst_ui() routine.  Your code for read responses and autosend data might look like:
 +
 +        // If a read response from a user dpget command, send value to UI
 +        if ((pkt->cmd & DP_CMD_AUTO_MASK) != DP_CMD_AUTO_DATA) {
 +            pinlen = sprintf(pinstr, "%1x\n", (pkt->data[0] & 0x0f));
 +            send_ui(pinstr, pinlen, prsc->uilock);
 +            prompt(prsc->uilock);
 +    
 +            // Response sent so clear the lock
 +            prsc->uilock = -1;
 +            del_timer(pctx->ptimer);  //Got the response
 +            pctx->ptimer = 0;
 +            return;
 +        }
 +    
 +        // Process of elimination makes this an autosend packet.
 +        // Broadcast it if any UI are monitoring it.
 +        if (prsc->bkey != 0) {
 +            pinlen = sprintf(pinstr, "%1x\n", (pkt->data[0] & 0x0f));
 +            // bkey will return cleared if UIs are no longer monitoring us
 +            bcst_ui(pinstr, pinlen, &(prsc->bkey));
 +            return;
 +        }
 +
 +You can see some of the internal working of the daemon in the above code.  The uilock tied to a resourse tells your driver that it is in a state of waiting for a read response from the FPGA.  The resource 'broadcast key', bkey, tells if any applications have subscribed to the stream of data offered by the resource.
 +
 +
 +==== Non-FPGA Based Peripherals ====
 +If you have ever built an application using dpdaemon then you have most likely come to appreciate the clean, simple, publish-subscribe API that it offers.  This section describes how you can use the dpdaemon and its API for non-FPGA based peripherals.  Let's start with an example of how it works.
 +
 +Dpdaemon comes with several examples of non-FPGA peripherals.  The first one to test is the 'hello_world' demo.  Start dpdaemon with any DPCore binary you have available.  Then at a command prompt enter:
 +    dploadso hellodemo.so
 +    dplist
 +    dplist hellodemo
 +You should see the new peripheral listed in slot #11.  The help text displays the reqsources available to the peripheral.  Test it with the commands:
 +    dpget hellodemo messagetext
 +    dpset hellodemo messagetext "Hello, again!"
 +    dpset hellodemo period 5
 +    dpcat hellodemo message
 +
 +The structure of non-FPGA based drivers is almost identical to FPGA based ones.  You will still need the Initialize() and usercmd() routines.  One difference is that non-FPGA based peripherals do not need a packet handler.  However they may need the ability to respond to data arriving from a file descriptor.  Working code for this is in the gamepad driver.  If you have as device or socket that you want to use as a data source you can add a callback for your file descriptor with a call to add_fd().  An example taken from the gamepad driver Initialize routine is shown below:
 +    // Init our GAMEPAD structure
 +    pctx->pslot = pslot;       // this instance of the hello demo
 +    pctx->period = 0;          // default state update on event
 +    pctx->filter = 0;          // default is to report all controls
 +    pctx->indx = 0;            // no bytes in gamepad event structure yet
 +    (void) strncpy(pctx->device, DEFDEV, PATH_MAX);
 +    // now open and register the gamepad device
 +    pctx->gpfd = open(pctx->device, (O_RDONLY | O_NONBLOCK));
 +    if (pctx->gpfd != -1) {
 +        add_fd(pctx->gpfd, DP_READ, getevents, (void *) pctx);
 +    }
 +
 +In the above case the callback getevents() is called when the file descriptor is readable.  Callbacks are given the file descriptor that generated the callback as well as the transparent data pointer passed in when add_fd() is called.  In the above example the tranparent data is a pointer to the GAMEPAD pctx structure.  The getevents() routine shows the callback structure.
 +    static void getevents(
 +        int       fd_in,         // FD with data to read,
 +        void     *cb_data)       // callback date (==*GAMEPAD)
 +    {
 +
 +You can think of dpdaemon as having two parts, the daemon part and the FPGA part.  The FPGA part is actually started as if it were a non-FPGA driver.  As mentioned above, dpdaemon load the driver for the "enumerator" peripheral and then the enumerator driver loads drivers for the peripherals found in the list from the FPGA.  You can easily make dpdaemon entirely ////non-FPGA//// based by a small change in main() of daemon/main.c.
 +    // Add drivers here to always have them when the program starts
 +    // The first loaded is in slot 0, the next in slot 1, ...
 +    (void) add_so("enumerator.so");   // slot 0
 +    //(void) add_so("tts.so");      // first available slot after FPGA slots
 +
 +
 +To better understand this you might want to comment our the enumerator and add tts and gamepad to main.c and see how the resulting system is all non-FPGA peripherals.
  
  
usersguide/devguide.txt · Last modified: 2022/07/14 23:40 by dpisuperadmin