Conocimientos básicos de FPGA----Capítulo 3 Sección 5 Descripción funcional-Lógica de combinación

Sección 5 Descripción funcional - Lógica de combinación

5.1 Declaraciones de programa

5.1.1 sentencia de asignación

La declaración de asignación es una declaración de asignación continua. Generalmente, el valor de una variable se asigna a otra variable sin interrupción . Las dos variables son similares a estar conectadas por un cable, que se usa como conexión. El formato básico de una declaración de asignación es:

asignar a = b (operador lógico) c ...;

La función de la declaración de asignación pertenece a la categoría de lógica combinacional , y su ámbito de aplicación se puede resumir de la siguiente manera:

(1) asignación continua ;

(2) Conexión ;

(3) Asigne un valor a la variable de tipo de cable . El cable es una red de cables, que es equivalente a la línea de conexión real. Si desea usar la asignación para conectarse directamente, use la variable de tipo de cable. El valor de la variable de tipo de cable cambia en cualquier momento.

Cabe señalar que múltiples sentencias de asignación continua se ejecutan de forma independiente y en paralelo.

5.1.2 La declaración siempre

La sentencia always es una sentencia de bucle condicional, y el mecanismo de ejecución se realiza mediante la activación de un evento llamado tabla de variables sensibles , que se describirá en detalle a continuación. El formato básico de la declaración siempre es:

siempre @ (evento sensible) comienza

declaración del programa

fin

siempre significa "siempre, siempre", @ va seguido de eventos. El todo siempre significa: cuando se cumpla la condición del evento sensible, ejecute la "instrucción del programa" una vez. Cada vez que se satisface un evento sensible, la "sentencia del programa" se ejecuta una vez. (Cuando cambia la condición sensible en el evento sensible, ejecute el contenido en la declaración de bucle siempre condicional

imagen-20211029113455362

El significado de este programa es: Cuando cambie la señal a, la señal bo la señal d, ejecute la siguiente instrucción una vez. Al ejecutar esta declaración, primero juzgue si la señal sel es 0, y si es 0, ejecute la tercera línea de código. Si sel no es 0, ejecute la quinta línea de código. Debe enfatizarse que si cualquiera de a, b y c cambia una vez, las líneas 2 a 5 solo se ejecutarán una vez y no se ejecutarán por segunda vez.

Cabe señalar aquí que solo el cambio de la señal sel no ejecutará los códigos de la línea 2 a la línea 5, lo que generalmente no está en línea con la idea del diseñador. Por ejemplo, la idea del diseñador general es: cuando sel es 0, el resultado de c es a+b; cuando sel no es 0, el resultado de c es a+d. Pero si la condición de disparo no cambia, aunque sel cambie de 0 a 1, el resultado de c sigue siendo a+b. Entonces, este no es un pensamiento de diseño canónico.

Por lo tanto, rediseñe el código de acuerdo con la idea del diseñador: cuando cambie la señal a o la señal b o la señal d o la señal sel, ejecute las líneas 2 a 5. De esta forma, se puede asegurar que cuando el valor de la señal de sel es 0, el resultado de c debe ser a+b, y cuando sel no es 0, el resultado de c debe ser a+d. Por lo tanto, para agregar sel a la lista confidencial, el código es el siguiente.

imagen-20211029113540684

Cuando hay muchas señales sensibles, es fácil pasar por alto las señales sensibles. Para evitar esta situación, puede usar "*" en su lugar. Este "*" se refiere a todas las señales condicionales en la "instrucción del programa", es decir, a, b, d, sel (sin incluir c), y también se recomienda este método de escritura, y el código específico es el siguiente.

imagen-20211029113600878

Este tipo de declaración siempre en la que el resultado de un cambio de señal condicional cambia inmediatamente se llama "lógica combinatoria".

imagen-20211029113618567

La lista de sensibilidad de código anterior es **"posedge clk", donde posedge significa flanco ascendente**. Es decir, cuando clk cambia de 0 a 1, el código del programa se ejecuta una vez, es decir, las líneas 2 a 5, y el valor de c permanece invariable en otros momentos. Debe enfatizarse que si clk no cambia de 0 a 1, incluso si a, b, d, sel cambian, el valor de c permanece sin cambios.

imagen-20211029113640663

Se puede ver que la lista sensible del código anterior es "negedge clk", donde negedg representa el flanco descendente . Es decir, cuando clk cambia de 1 a 0, el código del programa se ejecuta una vez, es decir, las líneas 2 a 5, y el valor de c permanece invariable en otros momentos. Debe enfatizarse que si clk no cambia de 1 a 0, incluso si a, b, d, sel cambian, el valor de c permanece sin cambios.

imagen-20211029113709007

La lista sensible del código anterior es "posge clk o negedge rst_n", es decir, cuando clk cambia de 0 a 1, o cuando rst_n cambia de 1 a 0, el código del programa se ejecuta una vez, es decir, las líneas 2 a 8, otros El valor de c permanece invariable en el instante de tiempo. Este tipo de disparo de flanco de señal, es decir, la señal siempre cambia solo en el flanco ascendente o descendente, se denomina "lógica secuencial" , y la señal clk es el reloj en este momento. Nota: para identificar si una señal es un reloj no es mirar el nombre, sino mirar dónde se coloca la señal. Solo aquellos que se colocan en la lista sensible y se activan por flanco son relojes. La señal rst_n es una señal de reinicio, y no se juzga por el nombre, sino que se coloca en la lista sensible y se activa por el mismo borde. Lo más importante es que la "instrucción del programa" primero juzga el valor de rst_n, lo que significa que rst_n tiene la prioridad más alta. Ambos se usan para reiniciar.

Se debe prestar atención a los siguientes puntos al diseñar :

1. * Las variables sensibles en la declaración siempre de lógica combinatoria deben escribirse en su totalidad o reemplazarse con " " .

2. La asignación de dispositivos lógicos combinacionales adopta la asignación de bloqueo "=", la declaración de asignación de dispositivos lógicos secuenciales adopta la asignación sin bloqueo "<=" ,

Consulte la sección "Asignaciones bloqueantes y no bloqueantes" por razones específicas.

5.2 Sistema numérico

5.2.1 Representación digital

El formato más utilizado para la representación digital en Verilog es: <bit width>'<radix><value> , como 4'b1011. Ancho de bits: un entero decimal que describe el número de bits contenidos en la constante, que es opcional. Por ejemplo, el 4 en 4'b1011 es el ancho de bit, que es 4 hilos en la comprensión popular. Si no existe tal elemento, se puede inferir del valor de la constante. Por ejemplo, 'b1011 infiere un ancho de bit de 4, mientras que 'b10010 infiere un ancho de bit de 5.

Base : Indica de cuantas bases es el valor. Puede ser b, B, d, D, o, O, h o H para binario, decimal, octal y hexadecimal, respectivamente . Sin esta entrada, el valor predeterminado es un número decimal. Por ejemplo, 4'b1011 en binario se puede escribir como 4'd11 en decimal, 4'hb en hexadecimal o 4'o13 en octal, o 11 sin la base. En resumen, mientras el número binario sea el mismo, es el mismo número independientemente de si está escrito en decimal, octal o hexadecimal.

Valor : Una cadena de códigos ASCII que representa el valor real de una constante determinada por la base. Si la base se define como b o B, el valor puede ser 0, 1, x, X, zo Z. Si la base se define como o o O, los valores pueden ser 2, 3, 4, 5, 6, 7. Si la base se define como h o H, los valores pueden ser 8, 9, a, b, c, d, e, f, A, B, C, D, E, F. Para radix d o D, el signo de valor puede ser cualquier número decimal: 0 a 9, pero no x o z. Por ejemplo, 4'b12 es incorrecto porque b significa binario y el valor solo puede ser 0, 1, x o z, sin incluir el 2. 32'h12 es igual a 32'h00000012, es decir, cuando el valor no se escribe completo, el bit alto se rellena con 0.

5.2.2 Binario es la base

En un circuito digital, si el chip A transmite datos al chip B, como transmitir información de 0 o 1, el chip A y el chip B se pueden conectar a través de un pin, y luego el chip A controla la salida del pin para que sea alta o baja. Nivel, 0 y 1 están representados por niveles alto y bajo. Cuando el chip B detecta que el pin está en nivel bajo, significa que ha recibido 0, y cuando el chip B detecta que este pin está en nivel alto, significa que ha recibido 1.

imagen-20211029114040717

Por el contrario, si se usa un nivel bajo para indicar que se recibe 1 y un nivel alto para indicar que se recibe 0, ¿es posible? Por supuesto, siempre que el chip A y el chip B estén de acuerdo de antemano, cuando el chip A quiera enviar un 1 digital, establecerá el pin en un nivel bajo. El chip B detecta que el pin está en un nivel bajo, lo que indica que se recibe el 1 digital y se completa la comunicación.

imagen-20211029114120140

Un pin tiene dos estados de alto y bajo, que pueden representar dos situaciones de 0 y 1 digital respectivamente. ¿Qué pasa si el chip A quiere enviar los números 0, 1, 2, 3 al chip B?

Puede conectar el chip A y el chip B con dos pines, es decir, dos líneas: a y b. Cuando ambas líneas son de nivel bajo, significa enviar digital 0; cuando a es de nivel alto y b es de nivel bajo, significa enviar digital 1; cuando a es de nivel bajo y b es de nivel alto, significa enviar digital 2; cuando Cuando ambos las líneas son altas, significa enviar el número 3.

imagen-20211029114157781

De acuerdo con el mismo principio, cuando el chip A quiere enviar datos 4, 5, 6, 7 al chip B, simplemente agregue otra línea. Los tres cables tienen un total de 8 estados, que pueden representar 8 números. En resumen, diferentes estados de nivel de la línea pueden representar diferentes significados, y tantos estados diferentes como haya pueden representar tantos números.

Pensemos si el chip A quiere enviar +1, -1, 0, +2 y otros números al chip B, ¿cómo expresar aquí el positivo y el negativo? Haciendo referencia a la idea anterior, el significado de los niveles alto y bajo de la línea se acuerda de antemano por los dos lados del chip. En este caso, se puede usar una sola línea para representar el símbolo, por ejemplo, los medios de nivel bajo número positivo, y nivel alto significa número negativo.

imagen-20211029114327334

Entre las tres líneas que se muestran en la figura anterior, la línea c se usa para representar positivo y negativo, donde 0 representa un número positivo y 1 representa un número negativo. Use la línea a y la línea b para representar el valor, tomando como ejemplo 3'b111, puede interpretarse como el número decimal 7, también puede interpretarse como el código original del número con signo "-3", y también puede ser interpretado como el complemento del número con signo "-1, cómo se interpreta depende de la definición del ingeniero de un número binario. Mientras esta definición no afecte la comunicación entre circuitos, no ocurrirá ningún problema. Por lo tanto, "0" y "1" en números no solo pueden representar significados numéricos literales, sino también representar otros significados, como símbolos positivos y negativos, etc. De la misma manera, en los circuitos digitales, los números binarios son la base de otros sistemas numéricos como octal, decimal, hexadecimal, números con signo, números sin signo y decimales. En el diseño de FPGA, la razón más fundamental para no conocer los métodos de cálculo de los números decimales y con signo es no conocer los valores binarios correspondientes a estos datos. Mientras comprenda los valores binarios correspondientes, se pueden resolver muchos problemas.

Permita que los estudiantes entiendan mejor este concepto a través de los ejemplos a continuación. Muchos principiantes a menudo preguntan, ¿cómo realizar el cálculo decimal en FPGA? Toma "0.5+0.25" como ejemplo. Es bien sabido que el resultado de 0.5+0.25 es 0.75. ¿Consideras cómo expresar 0.5, 0.25 y 0.75 en binario? El método de representación específico depende de la práctica del ingeniero, ya que existen muchos métodos de representación de este tipo, como decimales de punto fijo, decimales de punto flotante e incluso, como se mencionó anteriormente, usar unas pocas líneas para definirlo usted mismo. puede ser normal, no hay problema. Suponga que un ingeniero usa tres cables para definir el valor decimal representado por el valor binario, como se muestra en la siguiente tabla.

valor binario definición valor binario definición
3'b000 0.1 3'b100 0.25
3'b001 0.5 3'b101 0.3
3'b010 0.75 3'b110 0.8
3'b011 0.2 3'b111 0

Para ilustrar que el significado de los valores binarios se puede definir libremente, el orden de los números está desordenado. Entonces, ¿por qué solo este tipo de decimales? Esto se debe a que el sistema asumido solo tiene este tipo de números, y si desea representar más números, puede aumentar la cantidad de líneas. Después de completar la definición anterior, es muy fácil realizar "0.5+0.25", que en realidad es la "suma" de 3'b001 y 3'b100, esperando obtener 3'b010. Pero usando directamente 3'b001 + 3'b100 en la tabla, el resultado es "101", que no es el resultado deseado, y el código se puede escribir como:

imagen-20211029114740441

Por supuesto, esta es solo una forma de escribir, siempre que se pueda realizar la función correspondiente y el resultado sea correcto, cualquier forma de escribir está bien.

Puede haber dudas aquí, 0.1+0.8 debería ser 0.9, pero no hay representación de 0.9 en la tabla anterior. Esto es realmente un defecto en la tabla definida por el diseñador, o el diseñador piensa que esta situación no ocurrirá. Lo que quiero expresar aquí es: siempre que se definan los números binarios correspondientes, muchas funciones son fáciles de diseñar.

Por supuesto, en la ingeniería real, por lo general se siguen las prácticas acordadas y habituales, y no hay necesidad de encontrar otra forma. Por ejemplo, la siguiente tabla es la definición de decimales de punto fijo de uso común:

valor binario definición valor binario definición
3'b000 0.0 3'b100 0.5
3'b001 0.125 3'b101 0.625
3'b010 0.25 3'b110 0.75
3'b011 0.3725 3'b111 0.8725

En este momento, si desea obtener 0+0,5=0,5, es decir, sume 3'b000 y 3'b100, espere obtener 3'b100. Se puede encontrar que 3'b100 se puede obtener usando directamente el binario 3'b000+3'b100. De manera similar, para lograr 0.125+0.75=0.8725, es decir, agregue 3'b001 y 3'b110, espere obtener 3'b111. Se puede encontrar que 3'b111 se puede obtener usando directamente el binario 3'b001+3'b110.

Si se va a realizar el cálculo de 0,5+0,75=1,25, se puede ver que 1,25 ha excedido el rango de representación en este momento, y este problema se puede resolver aumentando el ancho de bit de señal o solo representando lugares decimales. Si solo se representan decimales, el resultado es 0,25, es decir, sumando 3'b100 y 3'b110, esperando obtener 3'b010. No es difícil encontrar que 3'b100 + 3'b110 = 4'b1010, expresado en 3 bits es 3'b010, que es 0,25. De lo anterior, se puede ver que el cálculo de los decimales de punto fijo no es complicado, y el cálculo se puede realizar directamente después de definir la relación entre los decimales de punto fijo y los valores binarios.

5.2.3 Estados indeterminados

Como se mencionó anteriormente, los circuitos digitales solo tienen nivel alto y nivel bajo, que representan 1 y 0 respectivamente. Pero x y z a menudo se pueden ver en el código, como 1'bx, 1'bz. Entonces, ¿cuáles son los niveles de x y z? La respuesta es que no hay un nivel real que corresponda a los dos. x y z son más la intención de un diseñador o con fines de simulación, para decirles a los simuladores y sintetizadores cómo interpretar este código.

El estado X se denomina estado indeterminado, que a menudo se usa para juzgar la condición, para decirle al diseñador de la herramienta de síntesis que no le importa su nivel, si es 0 o 1 está bien.

imagen-20211029114938488

Del ejemplo anterior, se puede ver que la condición de juicio es din== 4'b10x0, que es equivalente a din== 4'b1000||din==4'b1010, donde "||" es un "o" símbolo.

imagen-20211029115013142

Sin embargo, es mejor escribir directamente din== 4'b1000||din == 4'b1010 que "din == 4'b10x0" en el diseño, porque esta forma de escribir es más directa y sencilla.

En el proceso de simulación, algunas señales tienen un estado indeterminado, por lo que el diseñador debe analizar cuidadosamente si el estado indefinido es razonable. Si realmente no te importa si es 0 o 1, entonces puedes dejarlo en paz. Pero se recomienda que todas las señales no estén en un estado indeterminado, escriba claramente si es 0 o 1 y no agregue problemas de "pensamiento" al diseño.

5.2.4 Estado de alta impedancia

El estado Z, generalmente llamado estado de alta impedancia, significa que el diseñador no maneja esta señal (ni 0 ni 1), y generalmente se usa en la interfaz de puerta de tres estados.

imagen-20211030112458042

La figura anterior es un caso de aplicación del bus de tres estados. El bus de conexión en la figura es tanto de entrada como de salida para CPU y FPGA, y es una interfaz bidireccional. En los circuitos de hardware generales, se conectará a esta línea una resistencia pull-up (pull-up débil) o una resistencia pull-down (pull-down débil).

El punto A permanece alto cuando ni la CPU ni la FPGA están manejando el bus. Cuando la FPGA no controla el bus y la CPU controla el bus, el valor del punto A lo determina la CPU. Cuando la CPU no controla el bus y la FPGA controla el bus, el valor del punto A lo determina la FPGA. Pero FPGA y CPU no pueden controlar el bus al mismo tiempo, de lo contrario, el nivel de A será incierto. Por lo general, cuando FPGA y CPU controlan el bus, funcionan de acuerdo con el acuerdo negociado de antemano.

imagen-20211030112556798

La figura de arriba es una sincronización I2C típica. El bus SDA de I2C es una señal de tres estados. El protocolo I2C ha estipulado qué hora es conducida por el dispositivo maestro y qué hora es conducida por el dispositivo esclavo en el tiempo anterior. Ambas partes deben cumplir con el acuerdo y no pueden ser conducidas al mismo tiempo. Entonces, ¿cómo logra el FPGA el comportamiento de "no conducir" en el diseño? Esto se debe a que hay puertas de tres estados dentro de la FPGA.

imagen-20211030112615735

La puerta de tres estados es una pieza de hardware, y la figura de arriba es su estructura típica. La puerta de tres estados tiene cuatro interfaces, como la habilitación de escritura wr_en, escribir datos wr_data, leer datos rd_data y los datos de señal de tres estados conectados a dispositivos externos como se muestra en la figura anterior.

Cabe señalar que la señal de habilitación de escritura, cuando la señal es válida, la puerta de tres estados asignará el valor de wr_data a los datos de la línea de tres estados, en este momento el valor de los datos está determinado por wr_data, cuando wr_data es 0, el valor de los datos es 0; cuando wr_data Cuando es 1, el valor de los datos es 1. Cuando la señal de habilitación de escritura no es válida, no importa cuál sea el valor de wr_data, no afectará el valor de los datos externos, es decir, no se controlará.

En Verilog, las funciones anteriores se realizan mediante el siguiente código:

imagen-20211030112644309

Cuando el sintetizador ve estas dos líneas de código, sabe que se sintetizará en una puerta de tres estados, y este es el papel de la alta impedancia z. Además, se puede notar que el uso de líneas de tres estados en el hardware es para reducir los pines, y no hay necesidad de reducir el cableado en el FPGA, por lo que no tiene sentido usar señales de tres estados. Por lo tanto, se recomienda que no use el estado de alta impedancia "z" dentro de la FPGA cuando diseñe, porque no hay necesidad de agregarse problemas de "pensamiento". Por supuesto, si se usa el estado de alta impedancia en el diseño, no se informará ningún error y la función también se puede realizar.

En términos generales, el estado de alta impedancia "z" significa el comportamiento de "no conducir el autobús". De hecho, los circuitos digitales son altos o bajos, y no hay otros niveles.

5.3 Operadores aritméticos

imagen-20211030112732563

imagen-20211030145125938

Los operadores aritméticos incluyen suma "+", resta "-", multiplicación "*", división "/" y resto "%". Los operadores aritméticos comúnmente utilizados incluyen principalmente: suma "+", resta "-" y multiplicación "*". .

Tenga en cuenta que las operaciones de uso común no incluyen los operadores de división y resto, ya que los operadores de división y resto no están construidos con una lógica de puerta simple y los circuitos de hardware correspondientes son relativamente grandes. La suma y la resta son las operaciones más simples, y la multiplicación se puede desarmar en múltiples operaciones de suma, por lo que los circuitos correspondientes a la suma, resta y multiplicación son relativamente pequeños. La división es diferente. Los estudiantes pueden recordar los pasos de la división, que implica múltiples multiplicaciones, desplazamientos, sumas y restas, por lo que el circuito correspondiente a la división es complicado, lo que también requiere que el diseñador tenga cuidado al diseñar Verilog. Use la división con precaución.

5.3.1 Operador de suma

Primero aprenda el operador de suma, el símbolo "+" se puede usar directamente en el código Verilog:

imagen-20211030112934610

Su diagrama de circuito es el siguiente:

imagen-20211030112947325

Un sintetizador puede reconocer el operador de suma y convertirlo en un circuito como el que se muestra arriba. La operación de suma binaria es similar a la operación de suma decimal, el sistema decimal es cada diez y el binario es cada dos. La operación básica de la suma binaria es la siguiente:

imagen-20211030113004606

5.3.2 Operador de resta

Operador de resta, el símbolo "-" se puede usar directamente en el código Verilog:

imagen-20211030113035316

Su diagrama de circuito es el siguiente:

imagen-20211030113049315

Un sintetizador puede reconocer el operador de resta y convertirlo directamente en el circuito que se muestra arriba.

La operación de resta binaria es similar a la operación de resta decimal, y también existe el concepto de préstamo. En el sistema decimal, uno se toma prestado como diez, y en binario, uno se toma prestado como dos. La operación básica de la resta de 1 bit es la siguiente:

imagen-20211030113108106

5.3.3 Operador de multiplicación

Operador de multiplicación, el símbolo "*" se puede usar directamente en el código Verilog:

imagen-20211030113141837

Su diagrama de circuito es el siguiente:

imagen-20211030113153342

El sintetizador reconoce el operador de multiplicación y lo convierte directamente en el circuito que se muestra arriba. La operación de multiplicación binaria es similar a la operación de multiplicación decimal y el proceso de cálculo es el mismo. La operación básica de la multiplicación de 1 bit es la siguiente:

imagen-20211030113213925

La multiplicación entre varios dígitos es igual que el proceso de cálculo decimal. Por ejemplo, el proceso de cálculo de 2'b11 * 3'b101 es el siguiente:

imagen-20211030113232235

5.3.4 Operadores de división y resto

El operador de división puede usar el símbolo "/" directamente en el código Verilog, mientras que el operador de resto es "%":

imagen-20211030142720756

El diagrama esquemático del circuito de división es el siguiente:

imagen-20211030142733602

El diagrama esquemático del circuito restante es el siguiente:

imagen-20211030142749890

El sintetizador puede reconocer el operador de división y el operador de resto, pero estos dos operadores incluyen una gran cantidad de operaciones de multiplicación, suma y resta, por lo que el circuito del divisor en la FPGA es muy grande y es posible que el sintetizador no se convierta directamente en circuito que se muestra en la Fig.

Puede haber dudas aquí: ¿Por qué la división y el resto consumen muchos recursos? Analicemos el proceso de división decimal y resto, tomando como ejemplo 122 dividido por 11.

imagen-20211030142812957

En el proceso de realizar las operaciones anteriores, se involucran múltiples turnos, multiplicaciones, restas y otras operaciones. Es decir, se utilizan múltiples multiplicadores y restadores para realizar una operación de división, lo que requiere recursos de hardware relativamente grandes. Lo mismo ocurre con las operaciones binarias.

Por lo tanto, en el código de diseño, la división y el resto generalmente no se usan. Hay varias formas de evitar las operaciones de división y resto en el algoritmo. Por lo tanto, en el procesamiento de señales digitales, la comunicación y el procesamiento de imágenes, encontrará muchas multiplicaciones, sumas y restas, etc., pero rara vez verá operaciones de división y resto. Pero en la prueba de simulación, se puede usar la división y el resto, porque solo se usa para la prueba de simulación y no necesita sintetizarse en un circuito, por lo que, naturalmente, no hay necesidad de preocuparse por cuántos recursos están ocupados.

5.3.5 Resumen de experiencia

problema de ancho de bit

Al escribir código, debe prestar atención al ancho de bits de la señal. El resultado final depende del ancho de bits de la señal a la izquierda del signo "=", guarde el bit bajo y descarte el bit alto. Por ejemplo:

imagen-20211030142922330

El ancho de bits de la señal c es 1 bit, por lo que el resultado de la operación finalmente reserva el bit más bajo, por lo que el valor de c es 1'b0. Dado que el ancho de bits de d es de 2 bits, los 2 bits inferiores del resultado de la operación se pueden reservar, por lo que el valor de d es 2'b10. Dado que el ancho de bits de e es de 3 bits, los 3 bits inferiores del resultado de la operación se pueden reservar, por lo que el valor de e es 3'b010. "1" tiene 32 bits de forma predeterminada, y el resultado de 1+1 también es de 32 bits, pero dado que el ancho de bits de f es de solo 3 bits, los 3 bits inferiores del resultado de la operación se pueden reservar, por lo que el valor de f es 3'b010.

Lo mismo es cierto para las operaciones de resta.Tome el siguiente código como ejemplo:

imagen-20211030142952611

El valor binario obtenido de "0-1" es "1111111111….", pero el resultado guardado depende del ancho de bit de la señal a la izquierda del signo "=". El ancho de bit de c es 1, y el bit más bajo está reservado, por lo que el valor de c es 1'b1. Dado que el ancho de bits de d es de 2 bits, los 2 bits inferiores se reservan en el resultado, por lo que el valor de d es 2'b11. Dado que el ancho de bits de e es de 3 bits, los 3 bits inferiores se reservan en el resultado, por lo que el valor de e es 3'b111. El ancho de bits de f es de 4 bits, por lo que los 4 bits inferiores del resultado de la operación se pueden reservar, por lo que el valor de f es 4'b1111.

Al escribir el código de multiplicación, también debe prestar atención al ancho de bits de la señal. El resultado final depende del ancho de bits de la señal a la izquierda del signo "*". Guarde el bit bajo y descarte el bit alto. :

imagen-20211030143016965

El valor binario obtenido por "2'b11 * 3'b101" es "4'b1111", pero el resultado guardado depende del ancho de bit de la señal a la izquierda del "*". El ancho de bit de c es 1, y el bit más bajo está reservado, por lo que el valor de c es 1'b1. Dado que el ancho de bits de d es de 2 bits, los 2 bits inferiores se reservan en el resultado, por lo que el valor de d es 2'b11. Dado que el ancho de bits de e es de 3 bits, los 3 bits inferiores se reservan en el resultado, por lo que el valor de e es 3'b111. El ancho de bits de f es de 4 bits, por lo que los 4 bits inferiores del resultado de la operación se pueden reservar, por lo que el valor de f es 4'b1111. Cabe señalar que h, la señal tiene 5 bits, se asigna 4'b1111 a la señal de 5 bits, y el resultado es que los bits altos se llenan con 0, por lo que el resultado es 5'b01111.

El origen del complemento.

Cuando FPGA implementa varios algoritmos, lo más importante es garantizar la exactitud de los resultados del cálculo, de lo contrario, todo carece de sentido. Al analizar los operadores de suma y resta, se puede encontrar que el hecho de que el ancho de bits de la señal para guardar el resultado sea razonable tiene una gran influencia en la corrección.

Por ejemplo, la siguiente operación de suma:

imagen-20211030143139276

Puede verse en la tabla anterior que si no se conserva el acarreo, el resultado del cálculo es incorrecto cuando se produce la suma, y ​​el resultado del cálculo es correcto solo si se conserva el acarreo. De esto, podemos sacar una conclusión: cuando se usa la suma, para garantizar la exactitud del resultado, se debe guardar el acarreo, es decir, el resultado debe expandir el ancho de bit.

Por ejemplo, al sumar dos números de 8 bits, el resultado debe extenderse un bit y el ancho de bit se establece en 9 bits.

imagen-20211030143209482

A continuación, analicemos la operación de resta, como se muestra en la siguiente tabla:

imagen-20211030143329162

Tenga en cuenta que en la tabla y 2'b00-2'b01, el resultado es 2'b11, el valor decimal correspondiente es 3, pero el resultado esperado es "-1". De la misma manera, 2'b01 - 2'b11, el resultado es 2'b10, el valor decimal correspondiente es 2 y el resultado esperado es "-2", por lo que el resultado anterior es incorrecto.

Cuando el resultado esperado es positivo o negativo, se puede agregar un bit de signo para distinguir el resultado positivo o negativo. El método de representación acordado en la industria es que cuando el bit más alto es 0, significa un número positivo, y cuando el bit más alto es 1, significa un número negativo. El valor después del bit de signo está representado por los 2 bits inferiores y el resultado es el siguiente:

imagen-20211030143414060

En la tabla anterior se puede ver que después de agregar el bit de signo, todavía habrá algunos problemas de que los resultados de la operación no cumplen con las expectativas. Por ejemplo, 2'b00-2'b01 en la tabla, el resultado es 3'b111, el valor decimal correspondiente es -3, pero el resultado esperado es "-1". Así que el resultado anterior sigue siendo incorrecto.

Ahora, vuelva a convertir el número binario "000~111" de la siguiente manera :

a. Número positivo: permanece sin cambios

B. Número negativo: el bit de signo permanece sin cambios, y el valor se invierte y se suma 1 .

Es decir, si es un número positivo "+1", antes se representaba por "001", pero ahora se sigue representando por "001". Si es un número negativo "-1", antes se representaba por "101", pero ahora se representa por "111". El número negativo "-3" antes se representaba con "111", pero ahora se representa con "101". Esta representación es la representación del complemento .

Luego de expresarlo en código complemento, analicemos el resultado:

imagen-20211030145353945

Se puede ver que los resultados en la tabla anterior son todos correctos y consistentes con las expectativas. Este proceso no hizo ningún cambio en el código, pero el resultado correcto se logró cambiando la definición de los datos.

En la discusión anterior, las operaciones de sumando, sumando, sustraendo y minuendo no usaban números con signo. Ahora vuelve a representarlo usando el complemento a dos del número con signo. Suponiendo que el sumando, el sumando, el sustraendo y el minuendo son todos de 2 bits (el rango es -2~1), considerando la razón de acarreo y préstamo, el resultado está representado por 3 bits (el rango es -4~3 ). Debido a que el ancho de bit del resultado se convierte en 3 bits, tanto el sustraendo como el minuendo se expanden para ser representados por 3 bits, como se muestra en la siguiente tabla:

imagen-20211030145455123

imagen-20211030145507551

Los pasos de la operación resumida son los siguientes:

  1. De acuerdo con el "sentido común humano", se espera que los valores máximo y mínimo del resultado determinen el ancho de bits de la señal del resultado .
  2. Extienda el ancho de bits del sumando, sustraendo y otros datos para que el ancho de bits del resultado sea consistente.
  3. Realizar cálculos de suma y resta binaria.

A través del método anterior, lo que se obtiene es el resultado del código de complemento. De hecho, en las FPGA e incluso en los sistemas informáticos, todos los datos se almacenan en forma de complemento . Si desea obtener más información sobre el código de complemento, puede consultar los materiales relacionados.

5.4 Operadores lógicos

imagen-20211030145547059

imagen-20211030145556624

Hay 3 operadores lógicos en el lenguaje Verilog HDL, son:

(1) &&: lógica y ;

(2) | | : lógico o ;

(3) !: Lógico NO .

5.4.1 Y lógico

"&&" es un operador binario, que requiere dos operandos, como a && b.

(1) AND lógico de 1 bit

imagen-20211105212729170

Cuando tanto A como B son 1, C es 1; de lo contrario, C es 0.

El diagrama de circuito de hardware correspondiente es el siguiente:

imagen-20211105212800939

(2) AND lógico multibit

imagen-20211105212816535

C es 1 cuando ni A ni B son 0, de lo contrario es 0.

imagen-20211105212839123

5.4.2 O lógico

"||" es un operador binario, que requiere dos operandos , como a||b.
(1) OR lógico de 1 bit

imagen-20211105213147969

Uno de A y B es 1, C es 1, de lo contrario C es 0.

El diagrama de circuito de hardware correspondiente se muestra en la siguiente figura:

imagen-20211105213306687

(2) OR lógico multibit

imagen-20211105213322593

Si uno de A y B es distinto de cero, C es 1, de lo contrario, C es 0.

El diagrama de circuito de hardware correspondiente se muestra en la siguiente figura:

imagen-20211105213347308

5.4.3 Lógico NO

"!" es un operador unario, que requiere solo un operando, como! (a>b).

imagen-20211105213442864

Para el operando a, es necesario juzgar si no es verdadero, si es verdadero, ejecutar la operación dentro de {}, y si es falso, finalizar la operación.
La siguiente tabla es la tabla de verdad de las operaciones lógicas, que indica los valores obtenidos por varias operaciones lógicas cuando los valores de a y b están en diferentes combinaciones.

imagen-20211105213506720

El resultado final de los operadores lógicos es solo lógicamente verdadero o lógicamente falso, es decir, 1 o 0. En general, cuando se utilizan operadores lógicos como condiciones de juicio, la operación lógica AND solo puede ser de dos números de 1 bit de ancho. Solo cuando dos expresiones son verdaderas al mismo tiempo puede ser verdadera, y si una de ellas es falsa, puede ser falso

Si el operando es de varios bits, el operando se puede considerar como un todo. Si cada bit en el operando es 0, es un valor lógico 0; si hay un
1 en el operando, es un valor lógico 1.

imagen-20211105213540379

Dado que ni 4'b0111 ni 4'b1000 son 0, se considera lógicamente cierto si no es 0, por lo que el código anterior es equivalente al código siguiente.

imagen-20211105213557611

Es decir, el resultado es que a es lógicamente verdadero, b es lógicamente verdadero y c es lógicamente falso.

5.4.4 Resumen de experiencia

(1) Prioridad de los operadores lógicos

Entre los operadores lógicos - "&&" y - "||" tienen menor prioridad que los operadores aritméticos; "!" tiene mayor prioridad que los operadores lógicos binoculares

Los ejemplos son los siguientes:

imagen-20211105213736040

(2) Ambos lados del operador lógico corresponden a señales de 1 bit

Experiencia :Los operadores lógicos en ambos lados corresponden a señales de 1 bit

imagen-20211105213859092

Preste atención al código anterior, donde a y b son señales de varios bits, lo que indica que dos señales de varios bits tienen un AND lógico. La comprensión correcta de este código es: cuando a no es igual a 0 y b no es igual a 0, el valor de d es 1. Sin embargo, incluso los ingenieros con muchos años de experiencia laboral difícilmente pueden comprender intuitivamente el significado implícito en el código anterior. El concepto de que distinto de 0 significa lógicamente verdadero e igual a 0 significa lógicamente falso se pasa por alto fácilmente.

Por lo tanto, aunque no hay ningún error en el código anterior, el diseñador no debe escribir el código con el fin de mostrar la tecnología en el diseño, y esta forma de escribir es propensa a errores de diseño, por ejemplo, originalmente puede expresar asignar d = a & b, pero al final, el código anterior se escribió porque el diseño no era lo suficientemente intuitivo, lo que generó problemas de diseño. Por lo tanto, es muy importante escribir de forma intuitiva y comprensible en el diseño, para que usted y los demás puedan comprender inmediatamente el significado del código cuando lo vean, por lo que se recomienda escribir el código anterior de la siguiente forma.

imagen-20211105213942868

(3) Paréntesis de usos múltiples para distinguir prioridades

Experiencia 2 :No intente recordar la precedencia, use paréntesis más

De hecho, los ingenieros no recuerdan todas las prioridades en su trabajo, y recordar todas las prioridades no mejorará mucho la eficiencia del trabajo de los ingenieros. En el diseño, puede encontrar el siguiente código:

(1) a < b && c > d ;

(2) un = = segundo | | c = = re;

(3)! un | | a > b

Si no recuerdas la precedencia de los operadores, cuando te encuentres con situaciones como las de estos tres ejemplos, definitivamente pasarás una cierta cantidad de tiempo para ordenar tus pensamientos y pensar qué parte juzgar primero. Si el ingeniero puede recordar la prioridad, también necesita comunicar y verificar el trabajo de estos códigos, y los seres humanos son propensos a cometer errores, y estos errores a menudo se pasan por alto y son difíciles de verificar.

Por lo tanto, para mejorar la legibilidad del programa y expresar claramente la relación de prioridad entre los operadores, se recomienda utilizar más corchetes en el diseño. Los tres ejemplos anteriores se pueden escribir como:

1) (a < b) && (c > d);

2)( un = = b) | |( c = = d);

3)(!a) | |(a > b)。

(4) Usar NOT menos lógico

Experiencia 3 :Use "lógica y" y "lógica o" más, y use menos lógica no

"Lógico y" traducido al chino es "y", y "lógico o" traducido al chino es "o". Supongamos que hay un indicador de señal, 0 significa inactivo, 1 significa ocupado. "(!(flag== 0) && a== 4' b1000)", que se lee como "tomar el estado opuesto cuando está inactivo, y la condición es verdadera cuando a es igual a 4' b1000". Esto es muy difícil de leer, y al leer este código, debe girar un poco más la cabeza y pensar un poco más. Para que el código sea más intuitivo, se sugiere que el ejemplo anterior se escriba como "flag== 1 && a==4' b1000", que se lee como "cuando está ocupado y a es igual a 4' b1000", la condición es verdad.

5.5 Operadores lógicos bit a bit

imagen-20211105215609897

imagen-20211105215735525

Nota: ~ ^, ^ ~ (XOR binario, NOR): (equivalente a la operación NOR).

Existen los siguientes operadores bit a bit en el lenguaje Verilog HDL:

~ (NO unario): (equivalente a la operación NOT)

& (Y binario): (equivalente a la operación AND)

| (or binario): (equivalente a la operación OR)

^ (XOR binario): (equivalente a la operación XOR)

Estos operadores operan bit a bit en los bits correspondientes de los operandos de entrada y producen un resultado vectorial. Cada tabla de verdad de la siguiente figura muestra el resultado de la operación bit a bit para diferentes operadores lógicos bit a bit:

imagen-20211106195003983

5.5.1 Monocular bit a bit Y

El operador AND bit a bit unario &, después del operador, es la señal que debe operarse lógicamente, lo que significa la operación AND entre cada bit de la señal. Por ejemplo

Reg[3:0] A, C;

asignar C=&A;

El código anterior es equivalente a C = A[3] & A[2] & A[1] & A[0]; si A=4'b0110, el resultado de C es 0.

5.5.2 Monocular bit a bit O

Operador OR bit a bit monocular |, después del operador es la señal que debe operarse lógicamente, lo que indica que la señal es OR entre bits. Por ejemplo

registro[3:0] A,C;

asignar C=|A;

El código anterior es equivalente a C = A[3] | A[2] | A[1] | A[0]; si A=4'b0110, el resultado de C es 1.

5.5.4 AND bit a bit binocular

Operador AND bit a bit binocular &, la señal se encuentra en los lados izquierdo y derecho del operador, lo que significa que la operación AND de fase correspondiente se realiza en las dos señales. Por ejemplo

registro[3:0] A,B,C;

asigne C = A y B;

El código anterior es equivalente a: C[0] = A[0] & B[0], C[1] = A[1] & B[1], C[2] = A[2] & B[2 ], C[3] = A[3] y B[3]. Si A=4'b0110, B=4'b1010, el resultado de C es 4'b0010.

Si las longitudes de los operandos no son iguales, el operando con la longitud más pequeña se completa con 0 en el extremo izquierdo. Por ejemplo,

registro[1:0] A;

registro[2:0] B;

registro[3:0] C;

asigne C = A y B;

El código anterior es equivalente a: C[0] = A[0] & B[0], C[1] = A[1] & B[1], C[2] = 0& B[2], C[ 3] = 0 &0.

5.5.5 Binocular bit a bit O

Operador OR bit a bit binocular |, la señal se encuentra en los lados izquierdo y derecho del operador, lo que indica que la operación OR de fase correspondiente se realiza en las dos señales. Por ejemplo

registro[3:0] A, B, C;

asignar C = A | B;

El código anterior es equivalente a: C[0] = A[0] | B[0], C[1] = A[1] | B[1], C[2] = A[2] | B[2 ], C[3] = A[3] | B[3]. Si A=4'b0110, B=4'b1010, el resultado de C es 4'b1110.

Si las longitudes de los operandos no son iguales, el operando con la longitud más pequeña se completa con 0 en el extremo izquierdo. Por ejemplo,

registro[1:0] A;

registro[2:0] B;

registro[3:0] C;

asignar C = A | B;

El código anterior es equivalente a: C[0] = A[0] | B[0], C[1] = A[1] | B[1], C[2] = 0 | B[2], C [3] = 0|0.

5.5.6 XOR bit a bit binocular

El operador XOR bit a bit binocular ^, las señales están ubicadas en los lados izquierdo y derecho del operador, lo que significa realizar la operación XOR de fase correspondiente en las dos señales. XOR se refiere a 0 0=0,1 1=0,0^1=1, es decir, lo mismo es 0, y la diferencia es 1. Por ejemplo

registro[3:0] A, B, C;

asignar C = A ^ B;

El código anterior es equivalente a: C[0] = A[0] ^ B[0], C[1] = A[1] ^ B[1], C[2] = A[2] ^ B[2 ], C[3] = A[3]
^ B[3]. Si A=4'b0110, B=4'b1010, el resultado de C es 4'b1100.

Si las longitudes de los operandos no son iguales, el operando con la longitud más pequeña se completa con 0 en el extremo izquierdo. Por ejemplo,

registro[1:0] A;

registro[2:0] B;

registro[3:0] C;

asignar C = A | B;

El código anterior es equivalente a: C[0] = A[0] ^ B[0], C[1] = A[1] ^ B[1], C[2] = 0 ^ B[2], C [3] = 0^0.

5.5.7 Resumen de experiencia

Diferencia entre operadores lógicos y operadores bit a bit

Los operadores lógicos incluyen &&, ||, !, y los operadores bit a bit incluyen &, |, ~ . Entonces, ¿cuál es la diferencia entre operadores lógicos y operadores bit a bit? Comparando el AND lógico "&&" y el AND bit a bit "&", se puede ver que la operación del operador AND lógicoSolo hay dos resultados: lógicamente verdadero o lógicamente falso, es decir, 1 o 0; y "&" es un operador bit a bit,para dos operaciones de datos de varios bits de ancho. Para operadores bit a bit, dos números son AND, OR o NOT bit a bit.

imagen-20211107101136767

El resultado de la operación anterior es: a=1'b1, b=1'b1, c=1'b0, d=4'b0000, e=4'b1111, f=4'b1000.

5.6 Operadores relacionales

imagen-20211107101351496

imagen-20211107101503199

Los operadores relacionales son: > (mayor que), < (menor que), >= (no menor que), <= (no mayor que), == (lógicamente igual) y ! = (lógico no igual).

Los operadores relacionales se evalúan como verdadero (1) o falso (0). Si uno de los operandos es x o z, el resultado es x . Ejemplo: 23 > 45 : El resultado es falso ( 0 ). 52 < 8'hxFF: El resultado es x.

Si los operandos tienen diferentes longitudes, el operando más corto se rellena con ceros en la dirección de bit más significativa (izquierda) . Por ejemplo: 'b1000 >= 'b01110 es equivalente a: 'b01000 >= 'b01110, que es falso (0).

En la comparación de igualdad y desigualdad lógica, siempre que un operando contenga x o z, el resultado de la comparación es desconocido (x). Por ejemplo, si Data = 'b11x0; Addr = 'b11x0; entonces Data == Addr, la comparación resultado es incierto, es decir El resultado es x.

5.7 Operadores de turno

Hay dos operadores de desplazamiento en Verilog HDL, a saber, "<<" (operador de desplazamiento a la izquierda) y ">>" (operador de desplazamiento a la derecha).

A continuación se describe el uso de los dos respectivamente:

imagen-20211107102901190

imagen-20211107102911492

5.7.1 Operador de desplazamiento a la izquierda

En Verilog HDL, "-"<< representa el operador de desplazamiento a la izquierda. Su expresión general es:

A << n;

Entre ellos, A representa el operando a desplazar y n representa el número de bits a desplazar a la izquierda. El significado de esta expresión es desplazar el operando A a la izquierda en n bits. La operación de desplazamiento a la izquierda es un desplazamiento lógico, y se debe usar 0 para llenar la vacante desplazada, es decir, 0 se llena en los bits inferiores. Para desplazar a la izquierda n bits, es necesario completar n 0.

imagen-20211107103016873

Dado que el código anterior se desplaza a la izquierda 2 bits, se completan 2 ceros en los bits inferiores, por lo que el resultado de ejecución del código anterior es: a = 4'b1100.
Hay tres puntos que vale la pena señalar en la operación de desplazamiento a la izquierda:
(1) La operación de desplazamiento a la izquierda no consume recursos lógicos, incluso la puerta AND y la puerta NOT no son necesarias, es solo la conexión de líneas .

imagen-20211107103040064

El código anterior desplaza la señal b a la izquierda dos bits y la asigna a c. El circuito de hardware correspondiente es el siguiente:

imagen-20211107103103978

(2) La operación de desplazamiento a la izquierda necesita almacenar el resultado de acuerdo con el ancho de bit

Es posible que haya visto los siguientes códigos durante el proceso de aprendizaje: 4'b1001<<1=4'b0010 y 4'b1001<<1=5'b10010

¿Por qué el operando también es 4'b1001, que se desplaza un bit a la izquierda, pero el resultado es 4'b0010 y 5'b10010? Esto se debe a que después de la operación de desplazamiento a la izquierda, depende de cuántos bits se utilicen para almacenar el resultado.

imagen-20211107103702482

En el código anterior, dado que a tiene 4 bits, solo se pueden guardar 4 bits, por lo que b se desplaza 1 bit a la izquierda y se asigna a 4 bits a, y el resultado después de llenar los bits desplazados con 0 es a = 4'b0010;

imagen-20211107103721433

En el código anterior, dado que a tiene 5 bits, puede almacenar 5 bits de resultados, por lo que b se desplaza 1 bit a la izquierda y se asigna a 5 bits a, y el resultado es a = 5'b10010 después de llenar los bits desplazados con 0 ; ( 3**)
izquierda El operando de la operación de desplazamiento puede ser una constante o una señal**. De manera similar, el número de cambio, constante para la operación de cambio a la izquierda, también puede ser una señal.

imagen-20211107103747278

En el código anterior, cnt se incrementa en 1 cada reloj, y dado que son 3 bits, el valor es 0~2. a es 4'b1 desplazado a la izquierda por cnt bits. Cuando cnt es igual a 0, se desplaza 0 bits a la izquierda, a es igual a 4'b1; cuando cnt es igual a 1, se desplaza 1 bit a la izquierda, a es igual a 4'b10. Por analogía, cada cambio de reloj de a es como sigue:

imagen-20211107103805477

Cabe señalar que cuando el número de turno es una señal, el circuito integrado no es una conexión simple, y el selector que se muestra en la figura a continuación puede estar integrado . Sin embargo, aún así, los recursos consumidos por este circuito de hardware son aún relativamente pequeños.

imagen-20211107103822414

5.7.2 Operador de desplazamiento a la derecha

En Verilog HDL, use ">>" para representar el operador de desplazamiento a la derecha. Su expresión general es:

Un >>n;

Entre ellos, A representa el operando que se desplazará y n representa el número de bits que se desplazarán a la derecha. El significado de este código es desplazar el operando A a la derecha en n bits.

Hay tres puntos que vale la pena señalar en la operación de cambio a la derecha:
(1) La operación de desplazamiento a la derecha es un desplazamiento lógico, y se necesita 0 para llenar la vacante desplazada, es decir, para llenar los bits altos con 0, y cuántos 0 para llenar
depende del guarda el resultado .

imagen-20211107103914795

4'b0111 El resultado de desplazar dos bits a la derecha es 2'b01 Dado que a tiene 6 bits, asignar 2 bits a 6 bits necesita agregar 0 al bit alto, por lo que se deben agregar 4 0. Así que el resultado de ejecutar el código anterior es:

a = 6'b0001
(2) Similar a la operación de desplazamiento a la izquierda, la operación de desplazamiento a la derecha no consume recursos lógicos, incluso las puertas AND y NOT no son necesarias, es solo la conexión de
líneas .

imagen-20211107103936593

El código anterior desplaza la señal b hacia la izquierda dos bits y la asigna a A. El circuito de hardware correspondiente se muestra en la siguiente figura.

imagen-20211107103951964

(3) El operando de la operación de desplazamiento a la izquierda puede ser una constante o una señal. Asimismo, el número de cambio para una operación de cambio a la derecha puede ser una constante o una señal .

imagen-20211107104010745

En el código anterior, cnt se incrementa en 1 cada reloj, y dado que son 3 bits, el valor es 0~2. a es 4'b1000 desplazado a la derecha por cnt bits. Cuando cnt es igual a 0, se desplaza 0 bits a la derecha, a es igual a 4'b1000; cuando cnt es igual a 1, se desplaza 1 bit a la derecha, a es igual a 4'b0100. Por analogía, el cambio de cada reloj de a se muestra en la siguiente figura.

imagen-20211107104037650

Similar a la operación de cambio a la izquierda, en la operación de cambio a la derecha, si el número de cambio es una señal, el circuito integrado no es una conexión simple, pero puede sintetizar un selector como se muestra en la figura a continuación. Sin embargo, también en este caso, los recursos consumidos por dichos circuitos de hardware son todavía relativamente pequeños.

imagen-20211107104055622

5.7.3 Resumen de la experiencia

Multiplicar por desplazamiento a la izquierda

En FPGA, la operación de multiplicación debe evitarse tanto como sea posible, ya que este tipo de cálculo necesita ocupar grandes recursos de hardware y la velocidad de operación es relativamente lenta. Cuando tenga que usar la multiplicación, intente multiplicar por 2 a la N-ésima potencia , de modo que la operación de multiplicación se pueda realizar usando la operación de desplazamiento a la izquierda en el diseño, lo que reduce en gran medida los recursos de hardware.

Cuando el multiplicador es una constante de 2 elevado a la N-ésima potencia, la multiplicación se puede realizar mediante la operación de desplazamiento. Por ejemplo: un 2 es equivalente a a<<1; un 4 es equivalente a a<<2; a*8 es equivalente a a<<3, y así sucesivamente. Incluso si el multiplicador no es una constante de 2 elevado a la N-ésima potencia, la implementación se puede simplificar mediante una operación de desplazamiento. Por ejemplo:

imagen-20211107104134562

Tanto b como c en el código anterior pueden realizar a*127, pero la primera línea consume una multiplicación, mientras que la segunda línea solo usa un restador
.

imagen-20211107104147477

En el código anterior, tanto b como c pueden realizar a*67, pero la primera línea consume una multiplicación, mientras que la segunda línea solo usa dos sumadores, lo que ahorra recursos.

Se puede notar que los multiplicadores en los dos ejemplos anteriores son todos constantes, entonces, ¿este tipo de multiplicación también requiere tiempo y esfuerzo para considerar la optimización durante el diseño? De hecho, es innecesario, porque las herramientas integrales son muy poderosas ahora. Cuando la herramienta encuentra que el multiplicador es una constante, optimizará automáticamente de acuerdo con el proceso anterior. Es decir, multiplicar por una constante no consume multiplicador. recursos en esencia, para que pueda usarlo con confianza.

Pero cuando el multiplicador no es una constante, es necesario prestar atención al uso de la multiplicación. Trate de convertir la señal en una forma relacionada con 2 a la N-ésima potencia. Por ejemplo, cuando los datos deban expandirse y calcularse más adelante, no los expanda 100 veces de acuerdo con el pensamiento habitual, sino que los expanda directamente 128 veces (No es posible expandir decimales por 100 o 1000 veces de acuerdo con el pensamiento convencional, y ampliar de acuerdo con la n-ésima potencia de 2, lo que reduce la ocupación del espacio de recursos)。

Use el desplazamiento a la derecha para implementar la operación de división

En el diseño de FPGA, es necesario evitar la división tanto como sea posible, e incluso está estrictamente prohibido usar "/" para el cálculo de la división. Esto se debe a que el divisor ocupará una gran cantidad de recursos, que es más que el multiplicador, y en muchos casos el resultado no se puede obtener dentro de un ciclo de reloj. Y cuando tengas que usar la división, debes tratar de convertir la división en la forma de dividir por 2 elevado a la N-ésima potencia, para que puedasUsar la operación de cambio a la derechaPara realizar la operación de división, lo que reduce en gran medida los recursos de hardware .

Cuando el divisor es una constante de 2 elevado a la N-ésima potencia, la división se puede realizar mediante la operación de desplazamiento. Por ejemplo: a/2 equivale a a>>1, a/4 equivale a a>>2, a/8 equivale a a>>3, y así sucesivamente.

A diferencia del desplazamiento a la izquierda, cuando el divisor no es una constante de 2 elevado a la N-ésima potencia, la implementación no se puede simplificar simplemente mediante una operación de desplazamiento. En resumen, la división debe evitarse tanto como sea posible en el diseño de FPGA.

Codificación one-hot con desplazamiento a la izquierda

El código one-hot, también llamado one-hot code, es un sistema de código en el que solo un bit es 1 y los demás son todos 0 . Por ejemplo, 8'b00010000, 8'b1000000, etc.

El código one-hot es muy útil en el diseño, se puede usar para representar el estado de la máquina de estado para hacer que la máquina de estado sea más robusta, y también se puede usar en un circuito de opción múltiple para representar la elección de uno de ellos.

Usando la operación de desplazamiento a la izquierda, se pueden generar fácilmente códigos one-hot, por ejemplo, se puede generar 4'b0010, que puede ser 4'b1 << 1. De manera similar, también se puede generar un sistema de código en el que un bit es 0 y los demás son 1. Por ejemplo, para generar 4'b1011, puede ser ~(4'b1 <<2). También se pueden producir otros resultados numéricos deseados usando desplazamientos a la izquierda:

Por ejemplo, para generar 5'b00111, podría ser (5'b1<<3)-1.

Por ejemplo, para producir 5'b11100, podría ser ~((5'b1<<2)-1).

5.8 Operadores condicionales

imagen-20211107192312809

imagen-20211107192334644

5.8.1 Operador ternario

**Operador condicional (?: )** en la sintaxis Verilog HDL tiene tres operandos (es decir, operador ternario), y su formato generalmente se expresa como:

imagen-20211107192358180

Su significado es: cuando la "expresión condicional" es verdadera (es decir, 1 lógico), ejecuta la "expresión verdadera"; cuando la "expresión condicional" es falsa (es decir, 0
lógico), ejecuta la "expresión falsa". Es decir, cuando condition_expr es verdadero (es decir, el valor es 1), seleccione true_expr; si condition_expr
es falso (el valor es 0), seleccione false_expr. Si condition_expr es x o z, el resultado será el valor de la operación bit a bit de true_expr y false_expr de acuerdo con la siguiente lógica: 0 y 0 dan como resultado 0, 1 y 1 dan como resultado 1 yx de lo contrario.

Los ejemplos de aplicación son los siguientes:

imagen-20211107192422886

En la expresión anterior, si s es verdadera, asigne t a r; si s es falsa, asigne u a r.

El diagrama de circuito de hardware correspondiente se muestra a continuación.

imagen-20211107192454340

El uso de operadores condicionales tiene los siguientes puntos a tener en cuenta :

(1) La función de la expresión condicional es similar a la de un multiplexor , como se muestra en la figura 1.3-8. Además, se puede reemplazar con una declaración if-else.

imagen-20211107192527485

(2) Los operadores condicionales se pueden usar para la asignación condicional en el modelado de flujo de datos y, en este caso, las expresiones condicionales actúan como interruptores de control . Por ejemplo:

imagen-20211107192550223

Entre ellos, si la expresión Notas > 18 es verdadera, entonces se le asigna el valor de estudiante al Grado_A; si la expresión Notas > 18 es falsa, entonces se le
asigna el valor de estudiante al Grado_C.

El diagrama de circuito de hardware correspondiente se muestra a continuación.

imagen-20211107192607991

(3) Los operadores condicionales también se pueden anidar, y cada "expresión verdadera" y "expresión falsa" pueden ser en sí mismas una
expresión . Por ejemplo:

imagen-20211107192719206

El significado del código anterior es: si la expresión M == 1 es verdadera, entonces juzgue si CTL es verdadera, si CTL es verdadera, asigne A a OUT, si es falsa, asigne B a OUT; si M = = 1 es falso, luego juzgue si CLT es verdadero, si CLT es verdadero, asigne C a OUT, si es falso, asigne D a OUT.

El diagrama de circuito de hardware correspondiente es el siguiente:

imagen-20211107192740274

5.8.2 sentencia if

La sintaxis de la instrucción "si" es la siguiente:
si (condición_1)

procedural_statement_1;
{else if (condición_2)

procedural_statement_2};
{otro

declaración_de_procedimiento_3};

Su significado es: si se cumple condición_1, independientemente de que se cumplan el resto de condiciones, se ejecutará procedural_statement_1, y no se ejecutará procedural_statement_2 ni procedural_statement_3.

Si condition_1 no se cumple y condition_2 se cumple, entonces se ejecuta procedural_statement_2, y no se ejecuta procedural_statement_1 ni procedural_statement_3.

Si condition_1 no se cumple y condition_2 no se cumple, se ejecuta procedural_statement_3, y no se ejecuta procedural_statement_1 ni procedural_statement_2.

Ilustremos con un ejemplo:

si (Suma < 60) comienza

Grado = C;

Total_C = Total_C + 1;
terminar
si no (Suma < 75) comenzar

Grado = B;

Total_B = Total_B + 1;
terminar
si no empezar

Grado = A;

Total_A = Total_A + 1;
fin

Tenga en cuenta que las expresiones condicionales siempre deben estar entre paréntesis y pueden ser ambiguas si se usa el formato if - if - else,
como se muestra en el siguiente ejemplo:
if(Clk)
if(Reset)

Q = 0;
demás

Q = D;

Aquí hay una pregunta: ¿A qué declaración if pertenece el último else? ¿Pertenece a la condición del primer if (Clk) o a la condición del segundo if (Reset)? Esto se hace en Verilog HDL combinando el else con la instrucción if de none Else más cercana asociada a
resolver. En este ejemplo, el else está asociado con la instrucción if interna.

Aquí hay otro ejemplo de una sentencia if:
if(Suma < 100)

Suma = Suma + 10;
si (Nickel_In)

Depósito = 5;
Elseif (Dime_In)

Depósito = 10;
otra cosa si (cuarto de entrada)

Depósito = 25;
demás

Depósito = ERROR;
sugerencia

1. Las expresiones condicionales deben estar entre paréntesis .

2. Si es una declaración if - if, utilice la declaración de bloque begin — end , como se muestra a continuación.
si (Clk) comienza

si (restablecer)

Q = 0;

demás

Q = D;
fin

Las dos sugerencias anteriores son para aclarar el código y evitar errores.

5.8.3 declaración de caso

La instrucción case es una forma de bifurcación condicional multidireccional y su sintaxis es la siguiente:
case(case_expr)
case_item_expr{case_item_expr} :procedural_statement
. . . . . . . . . .
[predeterminado:procedural_statement]
endcase

Bajo la declaración de caso, primero se evalúa la expresión condicional case_expr, y luego cada elemento de rama se evalúa y compara a su vez, y se ejecuta la declaración en la primera rama que coincide con el valor de la expresión condicional . Se pueden definir varios elementos de sucursal en 1 sucursal, y estos valores no necesitan ser mutuamente excluyentes. La rama predeterminada anula todas las demás ramas no cubiertas por una expresión de rama.

imagen-20211107193238224

Sugerencias de escritura: se debe escribir el elemento predeterminado de la declaración del caso para evitar la generación de pestillos.

5.8.4 Instrucción de selección

Hay una declaración de selección de uso común en la sintaxis de Verilog, y su sintaxis es:

vect[a +: b] 或 vect [a -: b]

vect es el nombre de la variable, a es la posición inicial,Los signos más o menos representan un orden ascendente o descendente, b representa el ancho en orden ascendente o descendente .

vect[a +: b] es equivalente a vect[a : a+b-1] , el intervalo de vect comienza desde a y cuenta b veces en la dirección mayor que a; vect[a -: b] es equivalente a vect [a : a -b+1] , el intervalo de vect se cuenta b veces desde a hasta la dirección donde a es menor. a puede ser un número constante o variable, pero b debe ser una constante.

Ejemplo 1 : vect[7 +: 3]; donde, la posición inicial es 7, + representa el orden ascendente y el ancho es 3. Es decir, cuenta 3 números desde el 7 hacia la dirección mayor que 7. Su forma equivalente es: vect[7 +: 3]== vect[7 : 9].

Ejemplo 2 : vect[9 -: 4]; entre ellos, la posición inicial es 9, - representa el orden descendente y el ancho es 4. Es decir, cuenta 4 números desde el 9 hasta la dirección menor que 9. Su forma equivalente es: vect[9 -: 4]== vect[9 : 6].

La forma más común de esta sintaxis en el uso real es usar a como un número variable. Por ejemplo, necesita diseñar
código con las siguientes funciones:

Cuando cnt==0, asigne din[7:0] a data[15:8]; cuando cnt==1, asigne din[7:0] a data[7:0].

Al diseñar, se puede escribir como: data[15-8 cnt -: 8] <= din[7:0] (En este momento, 15-8 cnt debe considerarse
como un todo, lo que sucederá con el cambio de cambios de cnt), de modo que se complete la simplificación del código.

La estructura del circuito de hardware de la declaración de selección se muestra en la siguiente figura, que es esencialmente un selector. Cuando cnt==0, seleccione el latch de data[15:8], asigne din[7:0] a data[15:8], y el latch de data[7:0] mantiene la salida sin cambios; Cuando cnt= =1, seleccione el pestillo de datos[7:0], asigne din[7:0] a datos[7:0], y el pestillo de datos[15:8] mantiene el cambio de salida.

imagen-20211107193442661

Conclusión de la experiencia: en proyectos reales, la forma de declaración de selección vect[a +: b] o vect [a -: b] se puede usar para escribir código, lo que ayudará a simplificar el código de diseño.

5.8.5 Resumen de experiencia

La sentencia if y la sentencia case son dos sentencias muy importantes en Verilog Las sentencias if y case tienen ciertas correlaciones y
diferencias. Lo mismo es que los dos pueden lograr casi la misma función. A continuación se presentan principalmente las diferencias entre los dos.
Hay una prioridad entre cada rama de la instrucción if y el circuito sintetizado es similar a una estructura en cascada. Cada rama de la declaración del caso es igual, y el circuito sintetizado es un multiplexor . Por lo tanto, el retardo del circuito lógico obtenido al combinar varias sentencias if else-if puede ser ligeramente mayor que el de la sentencia case . Para los principiantes, a menudo les gusta usar la declaración if else-if en el proceso de aprendizaje de Veriolg al principio, porque esta sintaxis es más sencilla de expresar. Pero en proyectos donde la velocidad de ejecución es más crítica, el efecto de usar la declaración de caso será mejor. Comparémoslo a través de un caso específico, usando la declaración if y la declaración case para describir el resultado integral del mismo circuito funcional.

Primero está el código escrito con la sentencia if:

imagen-20211107193601401

imagen-20211107193618227

Su vista RTL sintetizada se muestra a continuación.

imagen-20211107193643645

Como se puede ver en el diagrama RTL que se muestra en la figura anterior, este circuito contiene dos multiplexores de dos a uno, y la prioridad del lado derecho es mayor que la del lado izquierdo (porque el valor de q está directamente relacionado con el selección de dos a uno en la conexión del dispositivo derecho), cuando en[0] no es 1, continuará juzgando en[1]. es decir, enEl circuito sintetizado bajo la instrucción if tiene prioridad

A continuación, analice el código descrito utilizando la instrucción case.

imagen-20211107193718179

Su vista RTL sintetizada es la siguiente:

imagen-20211107193742651

Como puede verse,El circuito sintetizado por el código lógico escrito por la declaración del caso es paralelo, no tiene prioridad y no afecta la velocidad de funcionamiento del circuito.

Aunque en la vista RTL, hay una gran diferencia entre los circuitos sintetizados por las dos declaraciones, pero debido a que las herramientas de desarrollo actuales son lo suficientemente inteligentes, el circuito se optimizará automáticamente durante el diseño y el enrutamiento, y finalmente se asignará al circuito. dentro del FPGA Básicamente no hay diferencia entre ellos .

imagen-20211107193823156

imagen-20211107193833956

Finalmente, resuma la diferencia y la conexión entre la declaración if y la declaración case:

La declaración If tiene prioridad, y la parte que sigue al else se ejecuta solo cuando no se cumple la condición del if. La declaración del caso es paralela y no tiene prioridad, lo que se puede observar claramente en la vista RTL sintetizada por los dos. Sin embargo, dado que las herramientas actuales de simulación y síntesis son lo suficientemente poderosas, los resultados de la síntesis final de las sentencias if...else... y case... en realidad no son diferentes, son solo dos métodos de implementación diferentes, por lo que básicamente no hay necesidad de considerar la diferencia entre las dos diferencias. Bajo la premisa de no afectar la función, el diseñador no necesita realizar un trabajo de optimización local, por ejemplo, no es necesario considerar la diferencia en el consumo de recursos de las declaraciones if/case, y no es necesario considerar la optimización de circuitos. Solo bajo la premisa de afectar la función (es decir, informar un error debido a restricciones de secuencia), optimizar el circuito de acuerdo con las indicaciones.

5.9 Operadores de concatenación

imagen-20211107193940242

imagen-20211107193949282

La operación de concatenación es una operación que combina expresiones pequeñas para formar una expresión grande, y su forma es la siguiente:

{expr1, expr2, . . .,exprN};

El carácter de empalme no consume ningún recurso de hardware, solo cambia la combinación de líneas , puede consultar los siguientes ejemplos:

imagen-20211107194014357

No se permite la concatenación de constantes de longitud no fija porque se desconoce la longitud de las constantes de longitud no fija. Por lo tanto, el código que se muestra a continuación no es gramatical. {Dbus,5}; //No se permiten operaciones de concatenación en constantes de longitud no fija.

Supongo que te gusta

Origin blog.csdn.net/Royalic/article/details/121196365
Recomendado
Clasificación