Funzioni logiche
Operazioni booleane
Le variabili o i segnali booleani possono assumere solo 2 stati, che a seconda del contesto possono essere descritti con mnemonici diversi
| 0 | 1 |
|---|---|
| false | true |
| no | yes |
| off | on |
| low | high |
Di seguito si assume inoltre il simbolo X per "don't care", cioè il valore è irrilevante per il risultato. I fronti sono indicati con la relativa notazione 0->1 o 1->0.
A scopo di confronto, in molti punti viene mostrato il collegamento con i classici circuiti integrati di porte logiche della serie 74 (ad es. 74HC147). Nei componenti complessi, la funzione implementata nel modulo Math offre per lo più ancora più opzioni del modello indicato.
La funzione logica più semplice ed elementare è l'elemento invertente "NOT":
| NOT A | |
|---|---|
| A | (74HC04) |
| 0 | 1 |
| 1 | 0 |
Quando due variabili o segnali booleani vengono combinati tra loro, si applicano 2 funzioni logiche elementari, AND e OR:
| A AND B | A OR B | A XOR B | ||
|---|---|---|---|---|
| A | B | (74HC21) | (74HC32) | (74HC86) |
| 0 | 0 | 0 | 0 | 0 |
| 0 | 1 | 0 | 1 | 1 |
| 1 | 0 | 0 | 1 | 1 |
| 1 | 1 | 1 | 1 | 0 |
L'elemento XOR (OR esclusivo) può essere realizzato con una combinazione complessa di AND, OR e NOT e viene qui elencato come elemento a sé stante per rendere più semplice la descrizione delle funzioni seguenti.
Combinando con uno stadio NOT si ottiene rispettivamente
| A NAND B | A NOR B | A NXOR B | ||
|---|---|---|---|---|
| A | B | (74HC00) | (74HC02) | (74HC266) |
| 0 | 0 | 1 | 1 | 1 |
| 0 | 1 | 1 | 0 | 0 |
| 1 | 0 | 1 | 0 | 0 |
| 1 | 1 | 0 | 0 | 1 |
A seconda dei tipi di dati si applicano i seguenti operatori e funzioni logiche, dove i 64 bit del tipo numerico <uint> (unsigned integer) vengono forniti singolarmente alle funzioni logiche:
<bool> A, B | <uint> A, B | |
|---|---|---|
| NOT | !A | bNot(A) |
| AND | A && B | bAnd(A,B) |
| OR | A || B | bOr(A,B) |
| XOR | A ^^ B | bXOr(A,B) |
| NAND | !(A && B) | bNAnd(A,B) |
| NOR | !(A || B) | bNOr(A,B) |
| NXOR | !(A ^^ B) | bNXOr(A,B) |
Funzioni orientate ai bit
Queste funzioni sui bit non sono operatori logici! Operano sempre sulla rappresentazione interna come <uint> e per questo convertono dapprima tutti i parametri nel tipo <uint>. Lo si vede, ad esempio, nella funzione bNot(), che inverte tutti i bit del valore di ingresso.
bNot(false) => bNot(0x00) => 0xFFFFFFFFFFFFFFFF => logic: truebNot(true) => bNot(0x01) => 0xFFFFFFFFFFFFFFFE => logic: true
Anche se nel risultato l'operatore XOR ^^ somiglia a != (diverso), il primo impone ai propri operandi il tipo di dati <bool>, mentre != o == cercano la migliore rappresentazione possibile per entrambi i tipi di dati e quindi, mescolando tipi di dati diversi, di norma convertono l'operando booleano in un valore numerico 0 o 1 (<uint>, <int>, <dbl>) per il confronto.
| Funzione | Descrizione |
|---|---|
| bNot(x) | inversione orientata ai bit di x (vedere anche l'operatore ~)Per un NOT logico su un'espressione <bool> utilizzare l'operatore !. |
| bAnd(x1, ..., xN) | congiunzione bit a bit (AND) di tutte le espressioni x1, ..., xN |
| bNAnd(x1, ..., xN) | congiunzione bit a bit (AND) di tutte le espressioni x1, ..., xN con successiva inversione |
| bOr(x1, ..., xN) | disgiunzione bit a bit (OR) di tutte le espressioni x1, ..., xN |
| bNOr(x1, ..., xN) | disgiunzione bit a bit (OR) di tutte le espressioni x1, ..., xN con successiva inversione |
| bXor(x1, ..., xN) | disgiunzione esclusiva bit a bit (OR) di tutte le espressioni x1, ..., xN |
| bNXor(x1, ..., xN) | disgiunzione esclusiva bit a bit (OR) di tutte le espressioni x1, ..., xN con successiva inversione |
| bShift(x,n) | spostamento bit a bit (SHIFT) di x di n posizioni verso sinistra, con n negativo verso destra |
| bCount(x) | numero di bit impostati in x |
| bTest(x,n) | verifica se in x è impostato l'n-esimo bit, n corrisponde al peso del bit |
| bSet(x,n,v) | impostazione (v=true) o cancellazione (v=false) del bit in x |
Elementi logici
Rilevamento di fronti "posedge", "negedge", "onchange"
Per gli elementi di rilevamento dei fronti il segnale di ingresso può essere un qualsiasi valore numerico scalare di tipo <bool>, <uint>, <int> o <dbl>.
| Funzione | Descrizione |
|---|---|
| posedge(x) | restituisce true per un nanosecondo se il valore attuale del segnale y è maggiore del valore precedente |
| negedge(x) | restituisce true per un nanosecondo se il valore attuale del segnale y è minore del valore precedente |
| onchange(x) | restituisce true per un nanosecondo se il valore attuale del segnale y è cambiato rispetto al valore precedente |
A causa della durata estremamente breve del segnale, di soli 1 ns, un segnale di questo tipo viene di regola ignorato dalla trasmissione MQTT. Per il controllo o la visualizzazione sono quindi adatte solo le registrazioni nei file OSF.
Flip-flop RS "rsFF"
Con true sull'ingresso reset l'uscita passa a false e con true sull'ingresso set l'uscita viene impostata a true. reset ha la priorità.
Gli ingressi reset e set vengono interpretati come <bool>.
Q = rsFF(reset, set);
// Optionale Konfiguration
Qx = rsFF(..., { storage: <str>
});
| Proprietà | Valore | Descrizione |
|---|---|---|
| storage | <str> | Nome di una memoria persistente per lo stato interno. In questo modo, dopo un riavvio del sistema l'uscita assume l'ultimo valore salvato. Il contenuto di questa memoria non è utilizzabile in nessun altro punto di smartCORE. |
| reset | set | Uscita |
|---|---|---|
| 0 | 0 | L'ultimo stato viene mantenuto |
| 0 | 1 | 1 |
| 1 | X | 0 |
Flip-flop JK "jkFF"
Con un fronte di salita sull'ingresso clock, gli ingressi di preparazione j e k decidono come viene modificata l'uscita (74HC73).
Con true sull'ingresso reset l'uscita passa a false e con true sull'ingresso set l'uscita viene impostata a true. reset ha la priorità.
Tutti gli ingressi vengono interpretati come <bool>.
Q1 = jkFF(clock, j, k);
Q2 = jkFF(clock, j, k, reset);
Q3 = jkFF(clock, j, k, reset, set);
// Optionale Konfiguration für alle Varianten
Qx = jkFF(..., { storage: <str>
});
| Proprietà | Valore | Descrizione |
|---|---|---|
| storage | <str> | Nome di una memoria persistente per lo stato interno. In questo modo, dopo un riavvio del sistema l'uscita assume l'ultimo valore salvato. Il contenuto di questa memoria non è utilizzabile in nessun altro punto di smartCORE. |
| clock | j | k | reset | set | Uscita |
|---|---|---|---|---|---|
| 0 | X | X | 0 | 0 | L'ultimo stato viene mantenuto |
| 1 | X | X | 0 | 0 | L'ultimo stato viene mantenuto |
| 0->1 | 0 | 0 | 0 | 0 | L'ultimo stato viene mantenuto |
| 0->1 | 0 | 1 | 0 | 0 | 0 |
| 0->1 | 1 | 0 | 0 | 0 | 1 |
| 0->1 | 1 | 1 | 0 | 0 | L'uscita cambia stato |
| X | X | X | 1 | X | 0 |
| X | X | X | 0 | 1 | 1 |
Flip-flop D "dFF"
Con un fronte di salita sull'ingresso clock il valore sull'ingresso data viene memorizzato (74HC74).
Con true sull'ingresso reset l'uscita passa a false e con true sull'ingresso set l'uscita viene impostata a true. reset ha la priorità.
Questo flip-flop può memorizzare tutti i tipi di dati. Ad eccezione di data, tutti gli ingressi vengono interpretati come <bool>.
Q1 = dFF(clock, data);
Q2 = dFF(clock, data, reset);
Q3 = dFF(clock, data, reset, set);
// Optionale Konfiguration für alle Varianten
Qx = dFF(..., { storage: <str>
});
| Proprietà | Valore | Descrizione |
|---|---|---|
| storage | <str> | Nome di una memoria persistente per lo stato interno. In questo modo, dopo un riavvio del sistema l'uscita assume l'ultimo valore salvato. Il contenuto di questa memoria non è utilizzabile in nessun altro punto di smartCORE. |
| clock | data | reset | set | Uscita |
|---|---|---|---|---|
| 0 | X | 0 | 0 | L'ultimo valore di uscita viene mantenuto |
| 1 | X | 0 | 0 | L'ultimo valore di uscita viene mantenuto |
| 0->1 | <var> | 0 | 0 | Emissione e memorizzazione del valore su data |
| X | X | 1 | X | L'uscita viene impostata a <false> |
| X | X | 0 | 1 | L'uscita viene impostata a <true> |
Latch D "dLatch"
Con true sull'ingresso hold viene memorizzato l'ultimo valore sull'ingresso data, altrimenti l'uscita segue direttamente l'ingresso data (74HC75). Questo flip-flop può memorizzare tutti i tipi di dati. L'ingresso hold viene interpretato come <bool>.
Q = dLatch(hold, data);
// Optionale Konfiguration
Qx = dLatch(..., { storage: <str>
});
| Proprietà | Valore | Descrizione |
|---|---|---|
| storage | <str> | Nome di una memoria persistente per lo stato interno. In questo modo, dopo un riavvio del sistema l'uscita assume l'ultimo valore salvato. Il contenuto di questa memoria non è utilizzabile in nessun altro punto di smartCORE. |
| hold | data | Uscita |
|---|---|---|
| 0 | <var> | Segue il valore su data |
| 1 | X | L'ultimo valore di uscita viene mantenuto |
Se hold e data cambiano contemporaneamente, il nuovo valore di data non viene più utilizzato.
Memoria indicizzata "memory" 1
La funzione memory rappresenta un dLatch() o dFF() indirizzabile con funzionalità estese. Il valore restituito è il vettore di dati memorizzato. Il tipo di dati viene stabilito tramite il parametro preset.
MEM1 = memory(control, index, data, {...});
MEM2 = memory(control, index, data, reset, {...});
// mandatory configuration
MEMx = memory(..., { mode: <enum> // mandatory
, size: <uint> // mandatory
, preset: <var> // recommended
, weight: <dbl>
, storage: <str>
});
| Proprietà | Valore | Descrizione |
|---|---|---|
| mode | <enum> | latch: con true sull'ingresso control viene memorizzato l'ultimo valore sull'ingresso data nell'elemento index, altrimenti l'elemento segue direttamente l'ingresso data (come dLatch())set: mem[index] = data, con un fronte di salita sull'ingresso control il valore sull'ingresso data viene memorizzato nell'elemento index (come dFF())add: mem[index] += data, con un fronte di salita sull'ingresso control il valore sull'ingresso data viene sommato all'elemento index: mult: mem[index] *= data, con un fronte di salita sull'ingresso control il valore sull'ingresso data viene moltiplicato per l'elemento indexweight: mem[index] = (1-weight)*mem[index] + weight*data, con un fronte di salita sull'ingresso control il valore sull'ingresso data viene moltiplicato per questo fattore di ponderazione e sommato all'elemento index ponderato in modo complementare. |
| size | Numero di posizioni di memoria riservate (indicizzate da 0 a size-1) e dimensione del vettore di uscita. | |
| preset | Inizializzazione della memoria (con reset = true) e definizione del tipo di dati, ad es. 0.0 per <dbl> o 0 per <int>. | |
| weight | Fattore di ponderazione per la modalità weight, intervallo di valori valido | |
| storage | <str> | Nome di una memoria persistente per il vettore di dati. In questo modo, dopo un riavvio del sistema l'uscita assume l'ultimo valore salvato. Il contenuto di questa memoria non è utilizzabile in nessun altro punto di smartCORE. |
Un elemento del vettore "memoria" può essere letto tramite la sintassi di indicizzazione [...] o la funzione GetCol(), anche in punti diversi del testo della formula:
e1 = MEMx[rdIndex];
e2 = GetCol(MEMx, rdIndex);
Un caso d'uso frequente della funzione memory() è, ad esempio, la memorizzazione delle letture dei contatori di energia. In questo caso, nella modalità latch si ricaverebbe l'indice dalla funzione decodeTime(), ad es. come giorno della settimana, e si fornirebbe come data la lettura cumulata del contatore di energia. L'ingresso control è costantemente su false, per seguire i valori del contatore di energia.
Una seconda memoria mantiene in modo analogo i valori del giorno precedente, in modo che il consumo giornaliero risulti dalla differenza dei vettori.
Un'implementazione si trova nell'Esempio 1c).
Multiplexer "select"
Multiplexer (74HC151). Tramite l'indice i viene selezionato e inoltrato un segnale s0, s1, ... Se i è negativo oppure maggiore o uguale al numero di ingressi (n+1), viene restituito false. I segnali possono rappresentare tipi di dati qualsiasi; l'uscita cambia di conseguenza anche il tipo di dati.
y = select(i, s0, s1, ...sn);
| i | s0 | s1 | ... | sn | Uscita |
|---|---|---|---|---|---|
| < 0 | X | X | ... | X | false |
| 0 | <var> | X | ... | X | segue s0 |
| 1 | X | <var> | ... | X | segue s1 |
| n | X | X | ... | <var> | segue sn |
| > n | X | X | ... | X | false |
Nel linguaggio di programmazione C ciò corrisponderebbe alla seguente istruzione:
switch(i)
{
case 0: return s0;
case 1: return s1;
// ...
case n: return sn;
default: return false;
}
Se i singoli segnali sono già raggruppati come vettore, si può utilizzare più semplicemente la sintassi con indici per i vettori:
VSig = [s0, s1, ...sn]; // eventually result of calculation or buffering
yi = VSig[i];
In tal caso occorre tuttavia fare attenzione che i rimanga in un intervallo di valori valido [0 .. n].
Encoder con priorità "prioEnc"
Encoder con priorità (simile a 74HC147 o 74HC148): contando in ordine crescente, viene restituito l'indice del primo ingresso attivo. Tramite l'oggetto di configurazione opzionale si possono impostare varianti della funzionalità. Tutti gli ingressi vengono interpretati come <bool>.
enc1 = prioEnc(e0, e1, ...);
// Optionale Konfiguration
encX = prioEnc(..., { none: <int>
, active: <bool>
, edge: <bool>
, map: [<int>]
, latch: <bool>
, startup: inf|<bool>
});
| Proprietà | Valore | Descrizione |
|---|---|---|
| none | <int> | Se nessun ingresso è attivo, viene restituito questo valore (def.: -1) |
| active | <bool> | Con questa proprietà si può impostare il livello da riconoscere come attivo (con edge: false) (def.: true). |
| edge | <bool> | Con true solo i fronti positivi (active: true) o negativi (active: false) vengono utilizzati per l'attivazione. |
| map | [<int>] | Tabella per l'assegnazione dei valori di uscita agli ingressi di controllo e0, e1, ... ad es.[1, 10, 100, 1000] |
| latch | <bool> | Con true viene mantenuto l'indice dell'ultimo ingresso attivo (radio button). |
| startup | inf/<bool> | Definizione di un livello di riferimento per gli ingressi all'avvio del software |
Nella tabella seguente un ingresso è attivo se lo stato definito dalle proprietà active e edge è soddisfatto. Altrimenti è inattivo.
| e0 | e1 | ... | en | Uscita |
|---|---|---|---|---|
| inattivo | inattivo | ... | inattivo | latch = false: -1 o valore della proprietà none latch = true: l'ultimo valore attivato (radio button) |
| attivo | X | ... | X | 0 o il valore definito con map[0] |
| inattivo | attivo | ... | X | 1 o il valore definito con map[1] |
| inattivo | inattivo | ... | attivo | n o il valore definito con map[n] |
Per l'avvio del sistema occorre stabilire come trattare lo storico degli ingressi e se all'avvio debbano quindi essere rilevati subito dei fronti. A tale scopo serve la proprietà startup:
-
inf: il primo campione del segnale determina il valore nel passato prima dell'avvio del sistema, quindi con il primo campione non viene generato alcun fronte.
-
<bool>: gli ingressi vengono assunti con questo valore di segnale (true, false) nel passato prima dell'avvio del sistema. In questo modo con il primo campione di un ingresso può eventualmente essere generato un fronte.
Nel linguaggio di programmazione C ciò corrisponderebbe (senza edge, latch o startup) alla seguente istruzione:
if (e0 == active)
return map.size() > 0 ? map[0] : 0;
else if (e1 == active)
return map.size() > 0 ? map[1] : 1;
// ...
else if (en == active)
return map.size() > 0 ? map[n] : n;
else
return none; // by default -1;