Spiegare il Modello Computazionale Qubic: Parte 2

in #abra5 years ago



Nella prima parte di questa serie è stata esaminata una panoramica concettuale della specifica Abra, che descrive il modo in cui il flusso di dati viene raggiunto all'interno del modello computazionale Qubic. In questa seconda parte, si userà la specifica Abra per iniziare ad implementare Qupla, un linguaggio di programmazione di alto livello per Qubic.

Qupla: un linguaggio di programmazione Qubic

Qupla è un acronimo che sta per Qubic programming language (linguaggio di programmazione Qubic). Gli obiettivi che si sta cercando di raggiungere con Qupla sono duplici:

  1. Fornire un linguaggio di programmazione del flusso di dati trinario secondo le specifiche Abra che può funzionare come linguaggio di programmazione di alto livello per Qubic
  2. Abbassare la barriera che impedisce ai programmatori di iniziare a programmare Qubic sfruttando il più possibile le conoscenze esistenti. Questo significa che si cerca di mantenere il linguaggio ed il suo comportamento il più familiare possibile, pur fornendo l'accesso alle nuove e sconosciute funzionalità del modello computazionale Qubic

A prima vista, Qupla ha una serie di cose in comune con i linguaggi di programmazione esistenti. Alcuni concetti, entità e costrutti sembreranno molto familiari ai programmatori, ma ci sono anche alcuni colpi di scena. La novità più ovvia, naturalmente, è che Qupla è un linguaggio di programmazione trinario.

Codifica trinaria

I sistemi binari usano i bit per rappresentare codice e dati, dove ogni bit può assumere solo uno dei 2 valori possibili. Valori più grandi possono essere rappresentati come numeri binari utilizzando una serie di bit. I valori che un singolo bit può assumere sono generalmente 0 e 1. I valori più grandi di N bit sono solitamente codificati come numeri non firmati nell'intervallo da 0 a (2^N)-1, o quali numeri firmati di numeri complemento a due nell'intervallo da -(2^(N-1))) a (2^(N-1))-1.

Allo stesso modo, i sistemi trinari usano i cosiddetti trits per rappresentare codice e dati, dove ogni trit può assumere solo uno dei 3 valori possibili. Valori più grandi possono essere rappresentati come numeri trinari utilizzando una serie di trits. Proprio come con i numeri binari, ci sono 2 modi per rappresentare valori trinari più grandi:

  • Rappresentazione trinaria sbilanciata, che permette ad un singolo trit di assumere i valori 0, 1 e 2. Utilizzando la rappresentazione trinaria sbilanciata una serie di N trits può rappresentare valori non firmati da 0 a (3^N)-1
  • Rappresentazione trinaria bilanciata, che permette ad un singolo trit di assumere i valori 0, 1 e -1. Usando la rappresentazione trinaria bilanciata una serie di N trits può rappresentare valori firmati da -((3^N)-1)/2 a ((3^N)-1)/2
Examples of upper values for representation ranges
Note that binary signed values run from -N-1 to N
Balanced trinary signed values run from -N to N
Bits/  Upper value (N)
trits  Binary  Trinary
    2       1        4
    3       3       13
    4       7       40
    5      15      121
    6      31      364
    7      63     1093
    8     127     3280
    9     255     9841
   10     511    29524

Tipi di dati

La specifica Abra permette una completa libertà di scegliere come sarà interpretato un vettore trit. Ma un linguaggio di programmazione deve fornire una serie di concetti standard per essere utile. Pertanto è stato definito che il linguaggio Qupla interpreterà un vettore trit che rappresenta un numero intero come trinario bilanciato a causa di alcune proprietà desiderabili di questa rappresentazione quando lo si utilizza per la matematica.

I vettori di trit che codificano numeri interi sono più naturalmente codificati in Qupla con il trit meno significativo per primo. Questo ci permette di estendere un valore allo stesso valore per un vettore trit più grande semplicemente riempiendo il valore con sufficienti zero-trits alla fine. Questo funziona sia per i valori positivi che per quelli negativi.

L'azione opposta, restringendo un valore maggiore per inserirsi in un vettore trit più piccolo, è altrettanto facile. Basta troncare il vettore trit alla dimensione richiesta. Naturalmente, in questo caso, se un trit diverso da zero viene troncato, il valore risultante diventerà diverso. Ma questo è inevitabile e simile a quello che succede quando si cerca di inserire un intero a 64 bit in un intero a 32 bit nei sistemi binari.

Limitare le dimensioni del tipo di dati per far corrispondere gli intervalli di valori effettivamente utilizzati consente di solito di ottenere una riduzione del fabbisogno energetico limitando la quantità di circuiti effettivamente necessari sugli FPGA. Immaginate una variabile che deve solo essere in grado di rappresentare i valori 0-10. Con la maggior parte dei linguaggi di programmazione tradizionali è necessario, come minimo, utilizzare una variabile a 8-bit (da 0 a 255) byte, dove 4 bit (da 0 a 15) sarebbero bastati, ed in ogni caso l'hardware è progettato per manipolare i byte. Con Qupla, seguendo Abra può bastare con un vettore trit di 3 trits (da -13 a 13) e alla fine su FPGA si ha solo la circuiteria necessaria per manipolare 3 trits.

Vettori di trit a dimensione fissa definiti dall'utente

Anche se Qupla ha solo il trit vector come singolo tipo di dati integrato, è comunque possibile definire comodi tipi di dati riutilizzabili con nome. Definendo il nome del tipo di dato e le sue dimensioni. In questo modo si ha un modo semplice per usare un nome simbolico per alcune dimensioni fisse di trit vettoriali, che si possono usare in tutto il codice Qupla. I principali vantaggi di questo modo di indicare la dimensione del vettore trit sono che si sta programmando utilizzando concetti invece di numeri rigidi, e che è sufficiente cambiare una sola posizione nel codice nel caso in cui si scopre che la dimensione del vettore trit dovrebbe essere cambiata.

// define some standard trit lengths we will use in our examples
// note that you can define the optimal trit size for any type's
// range to reduce energy requirements accordingly, but for
// simplicity we'll only define power-of-3 sized types for now

type Trit [1] // -/+ 1
type Tryte [3] // -/+ 13
type Tiny [9] // -/+ 9,841
type Int [27] // -/+ 3,812,798,742,493
type Huge [81] // -/+ 221,713,244,121,518,884,974,
// 124,815,309,574,946,401
type Hash [243] // standard 81 trytes hash value
type Hash3 [Hash * 3]
type Hash9 [Hash * 9]
type Signature [Hash * 27]
// define a convenience type to make code more readable
// should always be a binary boolean value 0 (false) or 1 (true)
// should never assume the value -
// (note: this convention is not (yet) enforced by Qupla)
type Bool [Trit]

Nell'esempio precedente abbiamo definito diversi tipi di dati che useremo negli esempi che seguono. La parola chiave type indica che ciò che segue è una dichiarazione di tipo trit vettoriale definita dall'utente. Poi specifichiamo il nome del tipo di vettore trit definito dall'utente, seguito dalla dimensione del tipo di vettore trit tra parentesi quadre. Abbiamo deciso di scriverlo così in Qupla a causa della somiglianza con il modo in cui altri linguaggi informatici specificano le dimensioni dell'array/vettore.

Si noti come la dimensione di un vettore trit può essere definita in termini di un tipo di dati definito in precedenza, come facciamo ad esempio con il tipo di dati Signature. Quando si usa un nome di tipo di dati in un'espressione che definisce la dimensione di un vettore trit, il nome del tipo di dati serve come valore costante che rappresenta la dimensione di quel tipo di dati.

Notare anche come possiamo usare l'aritmetica costante per calcolare le dimensioni del vettore trit come facciamo con la definizione dei tipi di dati Hash3, Hash9 e Signature nell'esempio precedente. Qupla supporta gli operatori di C: +, -, *, /, e % in espressioni costanti.

Vettori trit strutturati

Qupla permette anche di costruire un tipo di dati per un vettore trit strutturato. Un vettore trit strutturato è un vettore trit che consiste di sotto-vettori denominati. Questo trasforma essenzialmente un vettore trit in una struttura con campi denominati. In questo modo è facile accedere ai sotto-vettori senza dover tenere costantemente traccia dei rispettivi offset all'interno del vettore trit principale.

Un vettore trit strutturato è rappresentato da un unico vettore trit che concatena tutti i sotto-vettori. La sua dimensione totale è quindi la somma delle dimensioni di tutti i sotto-vettori.

type TinyFloat {
  Tiny mantissa   // -/+ 9,841
  Tryte exponent  // -/+ 3^13
}

Si noti come abbiamo definito i sotto-vettori mantissa ed exponent in termini di tipi di dati precedentemente definiti. Il vettore trit strutturato TinyFloat avrà quindi una dimensione di Tiny + Tryte o 12 trits. Si possono anche annidare i vettori di trit strutturati definendo un vettore di trit strutturato come tipo separato e poi usando quel tipo per un campo in un altro vettore di trit strutturato. Non ci sono limiti alla profondità di annidamento dei vettori di trit strutturato.

Look-up tables

Il cuore di Abra si trova la look-up table (LUT). Qupla ovviamente implementa anche le LUT, ma aggiunge un po' di colpi di scena. In Abra, una LUT ha sempre un singolo trit in output. Qupla permette ad una LUT di avere più trits come output. Dietro le quinte Qupla mapperà una output multi-trit Qupla LUT a molteplici single-trit Abra LUTs per noi.

Una LUT in Qupla è una tabella denominata di sequenze di 1, 2 o 3 valori trit in input ed uno o più valori trit corrispondenti in output. Quando si utilizza una LUT in un'operazione di ricerca, i valori di trit in uscita vengono restituiti come un singolo vettore di trit.

Tutte le sequenze di trit in input in una LUT devono essere esattamente della stessa lunghezza e non possono superare i 3 trits. Ogni sequenza di trit in input in una LUT deve avere una combinazione unica di valori. Inoltre, tutte le sequenze di trit in output devono avere esattamente la stessa lunghezza. Qupla genererà un errore di sintassi se una di queste regole viene infranta.

Qualsiasi combinazione mancante di valori di input nella LUT farà sì che la LUT restituisca un valore null per quella combinazione. Si noti che è possibile specificare solo vettori non-null trit per gli input e gli output. Questo significa che se un qualsiasi trit nel vettore di input è nullo, anche il vettore di output trit risultante sarà automaticamente nullo.

Questa proprietà in cui una LUT restituisce null in certi casi può essere usata a nostro vantaggio per implementare l'esecuzione condizionale. Spiegheremo questo aspetto in modo più dettagliato in una sezione separata.

Le LUT possono essere usate per ovviare alla necessità delle operazioni aritmetiche e logiche più comuni. Queste operazioni sono invece implementate come funzioni che avvolgono una o più operazioni LUT.

// LUT logic: return -trit1
lut neg {
  - = 1
  0 = 0
  1 = -
}
// Abra pseudo-code equivalent:
lut 10-10-10-10-10-10-10-10-10- neg_0

Nell'esempio precedente la parola chiave lut indica che quella che segue è una dichiarazione LUT. È seguita dal nome del LUT e da una o più voci LUT tra parentesi graffe.

Il neg LUT di cui sopra è uno dei più semplici. Restituisce un trit che è il valore negativo del singolo input trit. Si noti come la tabella consiste in una semplice enumerazione di quale valore di trit restituire per ogni possibile valore di trit in input. Un numero trinario bilanciato può essere reso negativo molto semplicemente, negando ogni singolo trit. Quindi, per negare un intero vettore di trit sarebbe necessario passare ogni singolo trit attraverso il neg LUT. Sugli FPGA questo può essere facilmente fatto in parallelo per tutti i trits nel vettore trit.

Si noti come la definizione del blocco LUT equivalente di Abra ripete i valori di output per riempire tutte le 27 voci della definizione LUT.

// LUT logic: return (Bool) (trit1 == trit2)
lut equal {
  -,- = true
  -,0 = false
  -,1 = false
  0,- = false
  0,0 = true
  0,1 = false
  1,- = false
  1,0 = false
  1,1 = true
}
// Abra pseudo-code equivalent:
lut 100010001100010001100010001 equal_0

La equal LUT leggermente più complessa determina se i due trit di input sono uguali. Restituisce true quando entrambi i trit di input sono uguali, altrimenti false. Poiché restituisce sempre true o false, il risultato è essenzialmente un tipo di dati Bool. Ancora una volta, si noti che questa è una semplice enumerazione di tutte le possibili combinazioni di trit in input e quale valore di trit restituire per ogni rispettiva combinazione di input.

// LUT logic: return (Bool) (trit1 != trit2)
lut unequal {
  -,- = false
  -,0 = true
  -,1 = true
  0,- = true
  0,0 = false
  0,1 = true
  1,- = true
  1,0 = true
  1,1 = false
}
// Abra pseudo-code equivalent:
lut 011101110011101110011101110 unequal_0

La unequal LUT diseguale determina se i due trit di ingresso sono diseguali. Restituisce falso quando entrambi i trit di ingresso sono uguali e vero altrimenti. Poiché restituisce sempre false o true, il risultato è essenzialmente un tipo di dati Bool. Anche se si potrebbe ottenere lo stesso risultato combinando la precedente equal LUT con una not LUT logica (vedi più sotto), la LUT unequal è più efficiente perché deve solo fare una singola ricerca.

// LUT logic: return the sum of two trits as trit plus a carry
//            return (trit1 + trit2), carry(trit1 + trit2)
lut halfAdd {
  -,- = 1,- // -1 + -1 =  1, carry -1
  -,0 = -,0 // -1 +  0 = -1, carry  0
  -,1 = 0,0 // -1 +  1 =  0, carry  0
  0,- = -,0 //  0 + -1 = -1, carry  0
  0,0 = 0,0 //  0 +  0 =  0, carry  0
  0,1 = 1,0 //  0 +  1 =  1, carry  0
  1,- = 0,0 //  1 + -1 =  0, carry  0
  1,0 = 1,0 //  1 +  0 =  1, carry  0
  1,1 = -,1 //  1 +  1 = -1, carry  1
}
// Abra pseudo-code equivalent:
lut 1-0-0101-1-0-0101-1-0-0101- halfAdd_0
lut -00000001-00000001-00000001 halfAdd_1

Questa halfAdd LUT mostra come si possono ottenere valori di uscita multi-trit. Sommerà semplicemente i due trit in input e restituirà il trit risultante più un riporto (carry). Anche in questo caso si tratta di un elenco di tutte le possibili combinazioni di input con i rispettivi output. Si noti in particolare come tutte le dimensioni degli input siano le stesse e come tutte le dimensioni delgli output siano le stesse.

Lo pseudo-codice Abra mostra come una 2-trit LUT di output viene convertita in due 1-trit LUT di output ad . Vedremo nella sezione sull'invocazione della LUT che il codice che accede a questa LUT invoca automaticamente entrambe le LUT e concatena i 2 trits risultanti in un unico vettore di trit.

// LUT logic: return (trit1 == true) ? trit2 : null;
lut nullifyTrue {
  true,- = -
  true,0 = 0
  true,1 = 1
}
// LUT logic: return (trit1 == false) ? trit2 : null;
lut nullifyFalse {
  false,- = -
  false,0 = 0
  false,1 = 1
}
// Abra pseudo-code equivalent:
lut @@-@@0@@1@@-@@0@@1@@-@@0@@1 nullifyTrue_
lut @-@@0@@1@@-@@0@@1@@-@@0@@1@ nullifyFalse_

Le LUT nullifyTrue e nullifyFalse dimostrano come possiamo usare a nostro vantaggio le combinazioni di trit di input mancanti. Restituiranno il secondo trit di input solo quando il primo trit in input è true o false, rispettivamente, e in tutti gli altri casi restituiranno nullifyTrue e nullifyFalse LUTs. Queste LUT si riveleranno fondamentali nel modo in cui creiamo la logica di decisione di Qupla, perché possono essere usate per creare funzioni che trasformano un intero vettore trit in nullo se la condizione di input non è soddisfatta. Si potrebbe leggere una nullifyTrue look-up come "nullifica il valore se la condizione di input non è true".

Qupla creerà automaticamente queste due LUT per supportare il suo operatore condizionale, come vedremo nella prossima parte di questa serie.

// ************* BINARY OPERATORS *************
// LUT logic: binary NOT
//            return !trit1
lut not {
  false = true 
  true  = false
}
// LUT logic: binary AND
//            return (trit1 & trit2)
lut and {
  false, false = false
  false, true  = false
  true,  false = false
  true,  true  = true 
}
// LUT logic: binary AND
//            return (trit1 & trit2 & trit3)
lut and3 {
  false, false, false = false
  false, false, true  = false
  false, true,  false = false
  false, true,  true  = false
  true,  false, false = false
  true,  false, true  = false
  true,  true,  false = false
  true,  true,  true  = true 
}
// LUT logic: binary OR
//            return (trit1 | trit2)
lut or {
  false, false = false
  false, true  = true 
  true,  false = true 
  true,  true  = true 
}
// LUT logic: binary OR
//            return (trit1 | trit2 | trit3)
lut or3 {
  false, false, false = false
  false, false, true  = true 
  false, true,  false = true 
  false, true,  true  = true 
  true,  false, false = true 
  true,  false, true  = true 
  true,  true,  false = true 
  true,  true,  true  = true 
}
// LUT logic: binary XOR
//            return (trit1 ^ trit2)
lut xor {
  false, false = false
  false, true  = true 
  true,  false = true 
  true,  true  = false
}
// LUT logic: binary XOR
//            return (trit1 ^ trit2 ^ trit3)
lut xor3 {
  false, false, false = false
  false, false, true  = true 
  false, true,  false = true 
  false, true,  true  = false
  true,  false, false = true 
  true,  false, true  = false
  true,  true,  false = false
  true,  true,  true  = true 
}
// LUT logic: binary NAND
//            return !(trit1 & trit2)
lut nand {
  false, false = true 
  false, true  = true 
  true,  false = true 
  true,  true  = false
}
// LUT logic: binary NAND
//            return !(trit1 & trit2 & trit3)
lut nand3 {
  false, false, false = true 
  false, false, true  = true 
  false, true,  false = true 
  false, true,  true  = true 
  true,  false, false = true 
  true,  false, true  = true 
  true,  true,  false = true 
  true,  true,  true  = false
}
// LUT logic: binary NOR
//            return !(trit1 | trit2)
lut nor {
  false, false = true 
  false, true  = false
  true,  false = false
  true,  true  = false
}
// LUT logic: binary NOR
//            return !(trit1 | trit2 | trit3)
lut nor3 {
  false, false, false = true 
  false, false, true  = false
  false, true,  false = false
  false, true,  true  = false
  true,  false, false = false
  true,  false, true  = false
  true,  true,  false = false
  true,  true,  true  = false
}
// LUT logic: binary XNOR
//            return !(trit1 ^ trit2)
lut xnor {
  false, false = true 
  false, true  = false
  true,  false = false
  true,  true  = true 
}
// LUT logic: binary XNOR
//            return !(trit1 ^ trit2 ^ trit3)
lut xnor3 {
  false, false, false = true 
  false, false, true  = false
  false, true,  false = false
  false, true,  true  = true 
  true,  false, false = false
  true,  false, true  = true 
  true,  true,  false = true 
  true,  true,  true  = false
}
// Abra pseudo-code equivalent:
lut @10@10@10@10@10@10@10@10@10 not_0
lut @@@@00@01@@@@00@01@@@@00@01 and_0
lut @@@@@@@@@@@@@00@00@@@@00@01 and3_0
lut @@@@01@11@@@@01@11@@@@01@11 or_0
lut @@@@@@@@@@@@@01@11@@@@11@11 or3_0
lut @@@@01@10@@@@01@10@@@@01@10 xor_0
lut @@@@@@@@@@@@@01@10@@@@10@01 xor3_0
lut @@@@11@10@@@@11@10@@@@11@10 nand_0
lut @@@@@@@@@@@@@11@11@@@@11@10 nand3_0
lut @@@@10@00@@@@10@00@@@@10@00 nor_0
lut @@@@@@@@@@@@@10@00@@@@00@00 nor3_0
lut @@@@10@01@@@@10@01@@@@10@01 xnor_0
lut @@@@@@@@@@@@@10@01@@@@01@10 xnor3_0

La suddetta lista di LUT implementa le porte logiche binarie standard e può essere usata insieme al tipo di dati Bool per creare espressioni logiche. Accettano solo gli input trits Bool false e true e restituiranno un Bool come output trit. Qualsiasi altro input è indefinito e quindi restituisce null.

Notare le versioni speciali and3, or3, xor3, nand3, nor3 e xnor3, che possono prendere 3 input ed eseguire la funzione logica su 3 operandi contemporaneamente.

Queste LUT binarie possono essere usate per specificare le condizioni logiche nelle espressioni di Qupla. Poiché un Bool è un singolo trit queste operazioni possono essere implementate direttamente come look-ups Abra LUT e sono quindi molto veloci.

Costanti

Gli unici tipi di costanti numeriche che attualmente possono essere utilizzate in Qupla sono i letterali intero ed i letterali in virgola mobile. Per comodità umana entrambe i letterali di default utilizzano la notazione decimale standard, ma è possibile utilizzare valori interi binari ed esadecimali, prefissando tali valori rispettivamente con 0b e 0x. Per esempio, 0b11001000 (binario per il valore decimale 200), o 0x4e20 (esadecimale per il valore decimale 20.000). Queste notazioni dovrebbero essere familiari agli utenti di molti altri linguaggi di programmazione.

Qupla definisce anche i letterali booleani false e true, che possono essere usati per chiarezza ovunque venga usato un tipo Bool. Si noti che Qupla non impone (ancora) questi tipi booleani, quindi è attualmente possibile aggirare l'uso di false e true e quindi è possibile impostarlo ad un valore che non sia false o true.

Infine, Qupla definisce i literali trinari, che sono serie di trits o trytes che possono essere usati come costanti per qualsiasi vettore trit, prefissando tali valori con 0t. Per esempio, 0t-111-1 (trinario per il valore decimale 200), o 0tKPWCHICGJZXKE9GSUDXZYUQUQPLHU (un valore hash a 27 tryte).

Si noti un'importante differenza tra le costanti trinarie e le altre costanti. Le costanti trinarie sono sempre specificate come big-endian, il che significa che gli zeri iniziali sono significativi e gli zeri finali sono irrilevanti quando si specifica un valore. Le costanti binarie, decimali ed esadecimali sono sempre specificate come little-endian, il che significa che gli zeri anticipatori sono irrilevanti e gli zeri finali sono significativi. Questo rende queste ultime costanti familiari e leggibili dall'uomo.

Letterali interi

Un intero letterale sarà automaticamente convertito in un vettore trit della lunghezza minima necessaria per poter contenere il valore intero che rappresenta il valore trinario bilanciato equivalente al valore intero. Questi letterali possono essere utilizzati in qualsiasi luogo in cui ci si aspetta un vettore trit. C'è anche un set completo di funzioni aritmetiche che è fornito dalla libreria standard di Qupla che accompagna il linguaggio.

Quando la costante viene assegnata ad un parametro o variabile, verrà automaticamente estesa alla dimensione del target di assegnazione. In questo caso, un valore maggiore della dimensione del target causerà un errore di compilazione. Si noti che gli unici limiti ad un valore letterale intero sono quelli imposti dai limiti dell'intervallo che il vettore trit ricevente può rappresentare.

Quando si usano valori costanti per un singolo trit, possiamo accorciare il valore -1 al solo carattere meno (-). Questo significa che un singolo trit può assumere i valori 0, 1 e -. Noterete che questo ci permetterà di rappresentare i dati tabellari in forma molto più compatta e leggibile. La struttura più visibile in Qupla che utilizza tali dati tabulari è la look-up table.

Letterali in virgola mobile

Un valore letterale in virgola mobile può essere utilizzato solo in luoghi in cui è previsto un tipo in virgola mobile. Le funzioni aritmetiche in virgola mobile sono fornite dalla libreria standard di Qupla che accompagna il linguaggio. Ogni volta che si incontra un vettore trit strutturato che consiste in un campo mantissa ed exponent (entrambi possono avere qualsiasi dimensione), Qupla permetterà di assegnare un letterale in virgola mobile.

I letterali in virgola mobile assumeranno sempre la dimensione del target di assegnazione. Come per i letterali intere, un valore che supera uno dei due campi della struttura causerà un errore di compilazione, ma questi sono gli unici limiti imposti al valore in virgola mobile.

Rappresentazione costante in Abra

Così ora sappiamo che Qupla definisce le costanti. Questo è tutto molto bello, ma non c'è nulla nella specifica di Abra che menziona le costanti. Ricordate, Abra è totalmente agnostico sul significato del contenuto dei vettori trit, e sembra non fornire un modo per specificarli. Quindi, che tipo di soluzione possiamo trovare per essere ancora in grado di utilizzare questo concetto probabilmente molto utile in Qupla?

Per rispondere a questo, dobbiamo pensare a ciò che Abra descrive: il flusso di dati. Sappiamo che Abra ha bisogno di ricevere dati di input per poterli trasformare in dati di output. Ciò significa che anche le costanti dovrebbero rimanere fedeli a quella regola e creare il valore costante solo quando si ricevono i dati di input. Questo è il nostro primo indizio. Abra utilizza rami con un vettore di input che attiva i dati di output per il ramo. Quindi abbiamo bisogno di un meccanismo che crei un valore costante dato da un vettore di input, dove il vettore di input potrebbe essere qualsiasi cosa. Questo è il nostro secondo indizio. Perché abbiamo un meccanismo che può fare proprio questo: la LUT.

Cominciamo a costruire prima le costanti più piccole: i singoli valori di trit. Come si ottiene una costante per un trit zero, per esempio? Avremo bisogno di una LUT che restituisce 0 per qualsiasi input. Tutto ciò di cui abbiamo bisogno per attivare il flusso di dati è un singolo trit input, e per ogni singolo trit input per restituire un trit output 0, possiamo usare il seguente blocco di definizione Abra LUT: lut 00000000000000000000000000000000000000000 constZero_. Bene! Le cose vanno avanti.

Prossima sfida: da dove prendiamo l'input trit di attivazione? Beh, dovrebbe essere facile. Ogni ramo è attivato da un vettore input trit. Quindi, perché non prendere il primo trit da quel vettore di input come input per la LUT? Ora il problema diventa come prendere solo un singolo trit da quello che potrebbe essere un vettore trit di qualsiasi lunghezza. Questo è dove una delle peculiarità di Abra torna utile. Possiamo definire un ramo che ha solo un singolo trit come ingresso. Ricordate che in Abra potete passare da un ramo a un qualsiasi vettore trit di qualsiasi dimensione, ma il ramo stesso decide quali parti utilizzare e quali parti ignorare. Il ramo può utilizzare il singolo trit per invocare la LUT attraverso un nodo LUT ed utilizzarlo come sito di output per restituire immediatamente il trit risultante come trit di output dal ramo. In Abra pseudo-codice assomiglia a questo:

lut 000000000000000000000000000 constZero_
lut 111111111111111111111111111 constOne_
lut --------------------------- constMin_
branch const_1_0
[1] in trit
[1] out ret = constZero_[trit]
branch const_1_1
[1] in trit
[1] out ret = constOne_[trit]
branch const_1_T
[1] in trit
[1] out ret = constMin_[trit]

Quindi qui abbiamo 3 diramazioni che possono generare i 3 diversi valori costanti per un singolo trit. Ora che abbiamo l'idea di base possiamo costruirla in diversi modi. Manteniamo la semplicità per ora. Basta avere ogni ramo per ogni costante prima di tutto impostare le costanti per zero, uno e min trits come siti corporei, e poi usare questi come output. Ricordate che gli output multipli saranno concatenati, quindi possiamo usarli a nostro vantaggio. Tutto ciò di cui abbiamo bisogno è un ulteriore trucco. Per usare un sito corporeo come output si può usare una fusione con un solo input. Anche in questo caso, ci sono altri modi per farlo, ma questo è il modo più semplice con le nostre conoscenze attuali. Prendiamo come esempio il valore costante 0t-111-1 (200):

branch const_6_T111T1
[1] in trit
[1] zero = constZero_[trit]
[1] one  = constOne_[trit]
[1] min  = constMin_[trit]
[1] out ret0 = merge{min}
[1] out ret1 = merge{one}
[1] out ret2 = merge{one}
[1] out ret3 = merge{one}
[1] out ret4 = merge{min}
[1] out ret5 = merge{one}

Notate come codifichiamo il nome del ramo costante per contenere la dimensione ed il valore. Questo rende ogni nome di ramo costante unico e si sa immediatamente quale dimensione e costante ritorna.

Notate che in questo caso avremmo potuto lasciare fuori il sito zero perché la costante non contiene uno zero e quindi non è per niente referenziata. Ma in pratica non c'è bisogno di preoccuparsi di questo. Qupla sa esattamente come creare il codice ottimale per un ramo costante per voi e fare la traduzione in Abra. E anche se questo può sembrare un sacco di codice per una semplice costante, in pratica sulle implementazioni su CPU di Qupla l'attuale vettore trit costante sarà memorizzato in cache e referenziato direttamente. Su FPGA questo ridurrà a invocazioni parallele dei siti dei nodi LUT e tutto il resto, input ed ouput, ridurrà a semplice cablaggio.

Conclusione

Il tipo di dati di base in Qupla è il vettore trit. Possiamo creare tipi definiti dall'utente per specifici vettori di trit a dimensione fissa e utilizzare valori costanti che si convertiranno automaticamente in vettori di trit. Un'altra entità di base di Qupla è la look-up table, che è la chiave per il funzionamento interno di molti concetti di Qupla.

Nella prossima parte di questa serie approfondiremo come queste entità di base vengono utilizzate con i costrutti dei linguaggi di programmazione Qupla.

Il testo originale in lingua inglese si trova qui: https://blog.iota.org/explaining-the-qubic-computation-model-part-2-f10f8cd5cbc5


Per ulteriori informazioni in italiano o tedesco trovate i miei contatti a questa pagina.
Se avete trovato utile la mia libera traduzione, accetto volentieri delle donazioni ;)

IOTA:
QOQJDKYIZYKWASNNILZHDCTWDM9RZXZV9DUJFDRFWKRYPRMTYPEXDIVMHVRCNXRSBIJCJYMJ9EZ9USHHWKEVEOSOZB
BTC:
1BFgqtMC2nfRxPRge5Db3gkYK7kDwWRF79

Non garantisco nulla e mi libero da ogni responsabilità.



Posted from my blog with SteemPress : https://antonionardella.it/spiegare-il-modello-computazionale-qubic-parte-2/

Coin Marketplace

STEEM 0.28
TRX 0.12
JST 0.032
BTC 62799.08
ETH 3027.63
USDT 1.00
SBD 3.95