Skip to content
Vedant Paranjape edited this page Aug 16, 2021 · 33 revisions

simpPRU

This is Google Summer of Code 2020 project under beagleboard.org aimed at achieving:

  1. Provide a simple to use language to write code for the PRU
  2. Provide a simple to use way to start/stop PRU and send/receive messages through RPMSG framework, without the need to write to various sysfs interfaces

Project Details

Table of contents

Brief overview of PRU-ICSS

The PRU is a dual core micro-controller system present on the AM335x SoC which powers the BeagleBone. It is meant to be used for high speed jitter free IO control. Being independent from the linux scheduler and having direct access to the IO pins of the BeagleBone Black, the PRU is ideal for offloading IO intensive tasks.

It is a low-latency micro-controller subsystem, which provides real-time processing capability lacking in Linux. PRUs are part of the PRU-ICSS, “Industrial Communications Subsystem”. They are free from running on an operating system, and thus can be dedicated to a single function.

The PRU cores were created to be non-pipelined, single-cycle execution engines. This means that all instructions (except for memory reads and writes) complete in a single cycle. Since there is no pipeline, the PRU cores are completely deterministic: the PRU cores will not rearrange instructions during execution.

The PRU-ICSS can also read from or write to external pins in a single cycle using a PRU core register (R30/R31). With a PRU core running at 200 MHz, this means that an external signal change can be detected in as little as 5 nanoseconds. This also means that the PRU can toggle external pins with as fast as 5 nanoseconds.

The PRU-ICSS also has an interrupt controller, it does not support asynchronous interrupts as they introduce jitter in execution time and reduce determinism. Instead it supports an efficient polling system.

What was the need for simpPRU ?

Programming the PRU is a uphill task for a beginner, since it involves several steps, writing the firmware for the PRU, writing a loader program. This can be a easy task for a experienced developer, but intimidating for a beginner.

This problem was solved earlier by pruspeaks project, but it inflicts serious restrictions on PRU, as PRU0 runs a bot speaks interpreter and PRU1 takes care of generating PWM signals. Already PRU has meagre resources, so emulating a VM on top of PRU leaves very less room to use PRU for what it is meant, deterministic control and fast read writes.

How does simpPRU simplify things ?

simpPRU simplifies this process to a great extent. Since it has a syntax quite similar to python, it is easier to code in simpPRU. It emits code in C, which is then compiled into binary using pru-gcc. So, it is as good as writing code in PRU C in terms of efficiency and functionality and far less in complexity.

Since, loading the firmware using sysfs class is a challenging and time consuming task, simppru helps in doing that by loading the firmware into /lib/firmware. Since, using sysfs classes to use RPROC and RPMSG is challenging, simppru-console takes care of it, and provides a intuitive and usable GUI to start/stop PRU and read/write to PRU using remoteproc.

Implementation details of simpPRU

simpPRU is a compiler which uses flex to generate lexical analyser, and bison to generate a parser. It uses custom code printer written in C. It emits code in PRU C.

A compiler has 3 main components:

  • Lexical analyser
  • Parser
  • Code printer

Lexical analyser

This is the simplest step. Given our grammar, we need to break down our input into a list of known tokens. As mentioned before, our grammar has tokens, namely, identifiers, numbers (integers and floats), the mathematical operators, parentheses, braces, keywords, etc.

This step basically reads from the input files, and generates tokens according to the regex exp specified in the flex file. The tokens are defined in lexer.l, and the actions that need to be taken after detecting a token is specified in this file itself. For example, if it finds, "int" in the input file, lexer will return a token say KW_INT to the parser.

Parser

This is the most challenging part. We need to parse the list of tokens that we received from lexical analyzer, and evaluate these using language grammar to make sense of the tokens. The language grammar is defined in a bison file named parser.y.

We use a GLR Parser, all tokens are stored in the symbol table, which is a hash table which stores values of the variables, with the variable name as the key, symbol table is defined in symbol_table.c.

After this step, we do semantic parsing whose result is an abstract syntax tree, AST is a tree data structure which stores various token as it nodes, such that it can represent the code in an abstract way in memory. AST is what represents our language in memory, AST related functions are defined in ast.c.

Code printer

The next step in a compiler is to naturally take this AST and turn it into machine code. This means converting each semantic node into the equivalent machine instruction or equivalent C code in this case.

For this step, we traverse the AST in a breadth first depth first approach, and print the equivalent C code in a output file. In this step it also keeps track of the GPO/GPI pins used, and sets them to appropriate mode using config-pin, relevant functions are defined in pin_config.c.

It prints wrapper functions in the generated code, like simple functions to digital read, digital write, send rpmsg, start/stop counter, delay, read counter, etc.

This code printer is defined in code_printer.c

Building from source

Dependencies

  • flex
  • bison
  • gcc
  • gcc-pru
  • cmake

Build

git clone https://github.com/VedantParanjape/simpPRU.git
cd simpPRU
mkdir build
cd build
cmake ..
make 

Install

sudo make install

Build debian package

sudo make package

Installation from debian package

Download

Download the latest debian package from: https://github.com/VedantParanjape/simpPRU/releases/latest
Download amd64 package for x86_64 systems and armhf package for arm32v7 systems

Install

sudo dpkg -i simppru-<version_number>-amd64.deb

replace <version_number> with the appropriate version of the downloaded package

Work Summary

The following major features have been implemented by me:

simpPRU

  • simpPRU compiler supports statically typed variables.
  • It supports integer and Boolean variables.
  • It has ability to assign variables during declaration.
  • Variables can be re-declared after declaration.
  • It supports if-else-elseif loop.
  • It supports while loop.
  • It supports range based for loop.
  • It supports arithmetic operators.
  • It supports comparison operators.
  • It supports logical operators.
  • It supports unary operators.
  • It supports functions, with return types int, bool and void.
  • It supports control statements like break and continue.
  • It fully supports expressions (arithmetic and logical).
  • It can load binary into /lib/firmware according to selected PRU.
  • It can auto set pin-mux using config-pin, by extracting the pins used in the code.
  • It auto detects Beagle Board model and manages the pin-mux settings, this doesn't work in BB AI.
  • Provides pin numbers as constant variables, like if P1_31 points to PRU R30 register bit 12, then one can use P1_31 and 12 interchangeably.
  • It has native support for PRU-ICSS peripherals, namely GPO and GPI control using R30/R31 registers, Delay function, IEP's hardware counter, and RPMSG API to communicate with ARM core.

simpPRU-console

  • simpPRU-console is a console GUI application, it can start/stop the PRU's, and also send/receive integers to the PRU using RPMSG, It displays the received messages in a serial monitor like fashion.
  • It auto detects Beagle Board model and accordingly updates the number of PRU's.

CI/CD for simppru debian package

  • Created docker files for building simppru for armhf and amd64 systems.
  • Setup CI/CD using GitHub actions, on every push/pull request simpPRU is built for arm32 and amd64 arch on GitHub actions using docker images.
  • New Debian packages are released upon sending a POST request to a web-hook. Clean docker images are pulled and generated Debian package is pushed to GitHub artifacts, after which a GitHub action releases those packages.

Other than that, I also contributed to two open source tools that made this project possible

pru-gcc

Commit logs: https://github.com/dinuxbg/gnuprumcu/commits?author=VedantParanjape

  • Added PRU I/O register definitions for AM572x processors.
  • Updated Register definitions of AM335x according to the Technical Reference Manual Revision 3.
  • Fixed minor mistakes in AM335x Register definitions.

FTXUI

Commit logs: https://github.com/ArthurSonzogni/FTXUI/commits?author=VedantParanjape

  • Added on_enter method to toggle component.
  • Added tests for OnEnter event in toggle component.

Things to be implemented in Future

  • Add support for using PWM using eCAP module.
  • Better error handler in bison parser.
  • Update grammar such that control statements (break/continue) can only be called inside loops.
  • Update grammar such that return statements can be called anywhere inside function definitions and not only at the end.
  • Add support for directly calling C code in simppru.
  • Add unit tests which run using pru-sim.

Conclusion

Best to my understanding all goals that were originally proposed have been completed, and several extra features have been implemented. I will continue to support this project in the future, take care of any bugs, issues and will continue implementing additional features whenever required.

I have created a to-do list, which lists several features that will improve this project further and would be implemented in the future as time permits.

It has been a really amazing experience for me. I have learned so much in these 15 weeks, which will surely help me achieve my future goals in life. I would like to thank beagleboard.org and Google for giving me such an amazing opportunity. I would also like to thanks my mentors Abhishek, Pratim and Andrew for helping me out and supporting me through out the project, also all the other mentors and organization admin, namely Cathleen, Jason and Saketh who supported me throughout the period, and making sure that I received all the required hardware on time.

Last but not the least, I would like to thank all my peers, namely Deepankar, Deepak and Niklas for helping me out with issues. On a ending note, if my words or actions hurt anyone, I am extremely sorry 😃.

Known Issues

  • Control statements (break/continue) can be called inside any compound statement, so calling it inside if loop will throw a error while compiling the generated C code.
  • Return statements can only be called at the end of the function, i.e, it must be the last statement in the function declaration, other wise it won't register a return statement and either throw a error or exhibit undefined behaviour.

Notes

When we pass -mmcu=am335x.pru0 to pru-gcc compiler, a macro is defined by the compiler which can be used in the code. Macro is __AM335X_PRU0__. It is set to 1 if pru0 option is passed, and it is undefined if pru1 option is used. It can be used as follows:

#ifdef __AM335X_PRU0__
    #define PRU_NUM 0 
#else
    #define PRU_NUM 1
#endif

Refer: https://github.com/dinuxbg/gnupru/issues/36

Resources that helped a lot