MyHDL: a Python-Based HDL

Con l’affermarsi del movimento dei makers abbiamo assisitito al diffondersi di piattaforme embedded (ARM), che prima erano ad appannaggio dei soli specialisti, presso un pubblico più vasto composto da hobbisti e persone non del settore.
Questo cambiamento ha portato alla ribalta linguaggi di programmazione ad alto livello come Python che è noto per la sua facilità di apprendimento, di leggibilità e scrittura. Secondo lo IEEE Spectrum, Python è al primo posto nelle categorie Web e Enterprise come  linguaggio di programmazione del 2017, ma come ci ricorda Luigi F. Cerfeda (“The rise of Python for Embedded Systems”, 2017) deve ancora emergere nella categoria Embedded nonostante progetti come Zerynth e MicroPython.


        Image Credits: Zerynth blog

Questo perché la programmazione di un micro-controllore si effettua con modalità più o meno uguali a quelle utilizzate una decina di anni fa cioè utilizzando ancora il C. Secondo un recente studio del Barr Group oltre il 95% dei sistemi embedded oggi è programmato in C o C++.

Perché utilizzare Python come HDL?

Possiamo utilizzare Python come linguaggio di descrizione Hardware (HDL)? Partendo dal presupposto che la prima delle golden rules è che descrivere hardware non è come sviluppare software? Jan Decaluwe,  lo sviluppatore di MyHDL,  ci ricorda in una serie di articoli  che basta anche solo cercare delle analogie fra hardware e software per generare reazioni irrazionali non cogliendone così i vantaggi.  Allo stesso modo con cui la retroguardia dei programmatori fanno fatica ad accettare che si possa programmare un micro-controllore in Python anziché in C.
MyHDL è una libreria open-source che permette di sfruttare l’eleganza e la semplicità di Python come astrazione di un linguaggio di descrizione hardware (HDL) tradizionale (VHDL o Verilog).

Tech Stack

[1] Installazione del modulo open source MyHDL
[2] Utilizzo di Python 3.5 installato attraverso Anaconda per Windows, nonostante il dichiarato supporto per Python 3 con cui però personalmente ho avuto alcuni problemi.
[3] Conda per la creazione di un nuovo ambiente di lavoro pulito, in modo da avere solo le librerie necessarie.
[4] Utilizzo come IDE Visual Studio 2017 con estensione Python Developments Tools.
[5] Aver installato GIT in caso di utilizzo della versione di sviluppo di MyHDL

Installazione MyHDL

Per installare la versione  stabile 0.9.0  di MyHDL ci basta digitare:

Anche se raccomando di utilizzare quella in sviluppo 1.0dev dal master branch che introduce una nuova sintassi presente in molti esempi del repository:

Controlliamo la versione installata

Multiplexer

Prima di poter utilizzare MyHDL dev’essere chiaro che bisogna studiare e comprendere affondo la logica digitale e la descrizione hardware. Per questo motivo  utilizzeremo il Multiplexer (o selettore) a due ingressi come semplice esempio.
Il Multiplexer ( spesso abbrevviato con la sigla Mux) è un circuito combinatorio composto da $${ 2 }^{ n }$$ linee di Input di dati ($${ I }_{ 0 } , { I }_{ 1 } , { I }_{2 } , .. $$) più $$n$$ linee di input di selezione ($${ S }_{ 0 } , { S }_{ 1 } , {S}_{2 } , .. , { S }_{ n-1 }$$) e un singolo output.
Se il multiplexer ha $$n$$ linee dati, necessita quindi di almeno $$log _{ 2 }{ n }$$ linee di selezione.

Possiamo immaginare il funzionamento di un Multiplexer come una specie di “interrutore rotante” che collega all’uscita uno (e uno solo) degli ingressi di dati in base alla configurazione degli ingressi di selezione. Anche se l’idea di multiplexing può sembrarvi relativamente recente, ne troviamo traccia già a partire dal 1800 con l’invenzione del telegrafo.
Nel 1853 il fisico austriaco Julius Wilhelm Gintl (1804-1883) introduce il telegrafo duplex che permetteva di comunicare contemporaneamente nelle due direzioni di una stessa linea. Più tardi  Thomas Edison (1847-1931) con il telegrafo quadruplex arriverà a quaduplicarne la capacità di trasmissione.

MUX 2:1

Il MUX più semplice è quello a 2 ingressi e 1 linea di selezione che richiede un 1 bit di selezione:
$$ n=2\quad S=\log _{ 2 }{ n } =\log _{ 2 }{ 2=1 } $$

Tutte queste informazioni ci torneranno utili quando andremo a descrivere il nostro primo circuito con Python.

Possiamo implentare un MUX con tante porte AND quanti sono i min-termini del segnale di controllo, 1 porta OR e $$n$$ porte NOT per generare tutti i termini negati nei min-termini.

La funzione logica che esprime l’uscita:

$$ O\quad =\quad ({ I }_{ 0 }\bullet \bar { S) } +({ I }_{ 1 }\bullet { S }) $$

Dalla Truth Table possiamo ricavarne il comportamento ( “il che cosa deve fare”) infatti sappiamo che se  $$S=0$$ in uscita avremo il valore di $${ I }_{ 0 }$$ e invece con  $$S=1$$ l’uscita sarà $${ I }_{ 1 }$$.

Iniziamo a descrivere hardware con Python 

The goal of the MyHDL project is to empower hardware designers with the elegance and simplicity of the Python language
Jan Decaluwe

I decoratori, introdotti dal carattere @ (“at”,presso) , sono alla base del funzionamento di MyHDL difatti vengono utilizzati per creare moduli hardware o generatori che avendo la capacità di mantere uno stato interno permettono la concurrency e la simulazione.
Nel primo caso si utilizza  @block per definire quale funzione Python verrà decorata per creare un nuovo modulo o blocco hardware. A volte si usano queste due espressioni indistinamente per riferirsi al design entity in VHDL.

Nel secondo caso si utilizzano i decoratori per avere come funzione di ritorno un generatore per ottenere circuiti combinatori e sequenziali.

Step 1 – Modeling Components

Quando si  descrive hardware hanno un ruolo fondamentale i segnali perché spostano i dati tra parti diverse del sistema. Le porte sono quindi segnali particolari perché spostano i dati da e verso l’esterno dell’interfaccia (interface) del  componente.
Una funzione in Python prevede un’intestazione costituita dalla parola def seguita dal nome della funzione e da delle parentesi tonde che possono contenerne o meno i parametri.

Per MyHDL il nome della funzione equivale a quello del modulo hardware e i parametri sono le porte di solo ingresso (in), solo uscita (out), ingresso/uscita (inout) con cui nella entity descriviamo l’interfaccia.

Il corrispettivo codice in VHDL sarebbe stato il seguente:

Nel nostro caso passeremo alla funzione MUX i 4 parametri che corrispondeanno alle 4 porte dell’interfaccia con segnali da 1 bit (std_logic):
– tre ingressi $${ I }_{ 0 } , { I }_{ 1 } , { S }$$
– uscita $${ O }$$

Possiamo rappresentare un segnale in VHDL come una  connessione singola (wire) di tipo std_logic o come un’insieme di connessioni  (bus) cioè un array monodimensionale di tipo std_logic_vector.

Step 2 – Modeling Processes

Abbiamo appena visto come i componenti vengono modellati utilizzando le funzioni  e i parametri ne mappano le porte (port maps) descrivendo così l’interfaccia esterna del componente. Ora dobbiamo però descriverne l’architettura cioè “il che cosa deve fare” l’intero sistema.
Il decoratore @always_comb() viene impiegato per descrivere la logica combinatoria di un circuito controllando se nei segnali di ingresso c’è stato un cambio di stato a causa di un evento e restituendo un generatore. MyHDL si basa quindi sullo stesso paradigma event-driven di VHDL e Verilog.

Se andiamo a controllare come MyHDL converte il codice in VHDL otterremo un listato come il seguente:

Notiamo che il nome del processo è composto dal nome della funzione mux() con cui abbiamo creato la entity più quello della funzione  logic()  con cui abbiamo descritto il comportamento del sistema.  Invece i  3 segnali in ingresso del Multiplexer $${ I }_{ 0 } , { I }_{ 1 } , { S }$$ vengono elencati dopo la parola chiave process nella sensitivity list ovvero nella lista dei segnali in grado di attivare il processo.
Tutte le volte che si verifica un evento, e soltanto in questo caso, su uno dei segnali della sensitivity list il processo è in esecuzione  altrimenti esso rimane in sospensione.

Una particolare categoria di iteratori: generators

Prima di procedere oltre, dobbiamo aver ben chiaro che cos’è un generatore in Python e il meccanismo di esecuzione e sospensione dell’iteratore.
Immaginiamo di dover scrivere una funzione che generi il quadrato di alcuni numeri contenuti in una lista, la funzione restituisce immediatamente tutti i valori:

Se volessimo creare un generatore dalla stessa funzione la sintassi cambierebbe in questo modo:

La funzione generaQuadrati() , quando invocata, crea un’istanza dell’oggetto generatore, ma non genera alcun numero.
I generatori sono un tipo particolare di iteratori perché sospendono la propria esecuzione una volta che raggiungono l’istruzione yield,  restituendo al chiamante un valore. Ma mantenendo  lo stato del generatore cioè il valore delle variabili locali e la posizione corrente. Dando così la possibilità di riprendere l’esecuzione dallo stato precendente  in caso venisse richiesto con l’istruzione next() il  valore successivo.
Possiamo interrompere la generazione dei valori con l’istruzione return.  .

Concurrency e sensitivity list

La capacità dei generatori di mantenere lo stato e di poterlo riprendere é alla base di MyHDL perché equiparabile al funzionamento di un  processo in VHDL che  nel suo complesso è uno statement concorrente cioè che si attiva ogni volta che uno dei segnali della sensitivity list cambia.
L’analogia software-hardware fra generatore-processo ci fornisce  la possibilità di eseguire istruzioni in parallelo (concurrency) e di utilizzare i valori restituiti dall’istruzione yield come i segnali elencati nella sensitivity list.
Rivediamo per maggior chiarezza la sintassi per la dichiarazione di un processo:

Un processo entra in esecuzione quando uno dei segnali elencati nella sensitivity list cambia il proprio valore-stato a causa di un evento. A seguito di questo evento, le istruzioni  contenute nel process body vengono eseguite in sequenza fino a giungere all’end process.
Eseguito l’ultima istruzione, il processo viene sospeso. Se, a seguito dell’esecuzione del processo, qualche segnale della sensitivity list cambia il proprio stato, il processo viene eseguito nuovamente, fin quando tutti i segnali della sensitivity list non hanno raggiunto uno stato stazionario.

Bibliografia

[1] Magnus Lie Hetlnd, Beginnig Python, 2nd edition, Apress,2008
Cap 9: Generators, pag.194
[2] Mark Lutz ,Learning Python,O’Reilly, 4th ed, 2009
Part 2: Iterators Revisited: Generators, pag 492
[3] David Beazley, Python Cookbook, 3rd edition, O’Really, 2013
Cap 4: Iterators and generators, pag 113
[4] Dusty Phillips, Python 3 Object-Oriented Programming, 2nd edition, Packt, 2015
Cap. 9: Generators, pag 279

FPGA
[1] David Romano, FPGAs: Turning Software into Hardware with Eight Fun and Easy DIY Projects, Maker Media, 2016

Decorators
[1] Kevin D. Smith, Decorators for Functions and Methods, PEP 318, 2003
[2] Bruce Eckel, Decorators I: Introduction to Python Decorators, Artima, 2008
[3] Bruce Eckel, Decorators II: Decorator arguments, Artima, 2008
[4] David Isaacson, Python Decorators Don’t Have to be (that) Scary, 2010
[5] Alessio Antonielli, Python – Decorator, Alla fine del palo, 2016

Embedded-systems
[1] Ron Wilson, Is Tomorrow’s Embedded-Systems Programming Language Still C?, System Design Journal, 2016
[2] Luigi F. Cerfeda, The rise of Python for Embedded Systems, Zerynth blog,2017
[3] Michael Domingo, Why Do Some Developers Prefer Python?, Visual Studio Magazine, 2017

MyHDL:articoli e documentazione
[1] Jan Decaluwe, The Case for a Better HDL, 2013
[2] Jan Decaluwe, Why HDL designers should learn Python, 2015
[3] Jan Decaluwe, The block decorator, MEP 114, 2016
[4] MyHDL documentation, Release 1.0dev

MyHDL:documentazione
[1] Jan Decaluwe, Creating generators with decorators , MEP 100, 2005
[2] Jan Decaluwe, The block decorator, MEP 114, 2016
[3] MyHDL documentation, Release 1.0dev