Un wire vs muchos
Hasta ahora cada señal que hemos visto ha sido de un bit de ancho. Los diseños reales casi nunca se ven así - las direcciones son de 16 o 32 bits, los buses de datos son de 8 o 64, los píxeles RGB son de 24. Verilog te da un único mecanismo para hacer cualquier señal multibit: añadir un rango entre corchetes.
wire [7:0] data; // 8-bit wire, bit 7 is MSB, bit 0 is LSB
reg [15:0] address; // 16-bit reg
output reg [31:0] result; // 32-bit module output
Los números entre los corchetes son las posiciones de bit del bit más y menos significativos. [7:0] significa "esta señal tiene bits numerados del 7 al 0", que sale a 8 bits en total. El primer número es el índice alto. El segundo es el bajo.
Ocasionalmente verás [0:7] en su lugar - mismo número de bits, endianness opuesta para fines de slicing. La forma [high:low] es la convención abrumadora de la industria; quédate con ella a menos que tengas una buena razón para no hacerlo.
Un ejemplo trabajado: un sumador de 8 bits
Cada + suma los dos vectores de 8 bits y produce un resultado de 9 bits. Los literales numéricos como 8'd10 dicen "un valor decimal de 10 con ancho de 8 bits" - los cubriremos en Number Literals.
Slicing: extraer bits
Una vez que tienes un vector, puedes sacar bits individuales o rangos contiguos:
No te enredes con la línea force del testbench - solo necesitamos una forma de inyectar un valor para demostrar el slicing. La parte interesante son los slices mismos.
Algunas reglas:
- La dirección del slice tiene que coincidir con la declaración. Si declaraste
[7:0], haces slice con[high:low]. Invertir la dirección es un error de sintaxis. - Hacer slice fuera de rango produce
x(desconocido) en simulación. La herramienta de síntesis puede avisar o dar error. - La selección de bits es base cero en el índice que escribiste -
data[0]es el bit llamado0, que (para una declaración[7:0]) es el LSB.
Slices con base variable: +: y -:
Una necesidad común: "dame 8 bits empezando en el bit N". No puedes escribir data[N+7:N] directamente porque Verilog requiere que ambos extremos del rango sean constantes. La sintaxis que resuelve esto:
data[base +: width] // width bits starting at `base`, going UP
data[base -: width] // width bits starting at `base`, going DOWN
El ancho es constante (estamos tomando 8 bits a la vez), pero la base puede ser una expresión en runtime. Eso es exactamente lo que necesitas para memorias direccionables por byte, taps de shift register, etc.
Arrays: un paso más allá de los vectores
Un vector es una única señal multibit. Un array es una colección de vectores, indexados independientemente:
reg [31:0] mem [0:1023];
Esa declaración tiene dos rangos, y los dos rangos significan cosas distintas:
[31:0]es la dimensión packed - el ancho de cada palabra individual.[0:1023]es la dimensión unpacked - cuántas palabras hay.
Así que mem son 1024 registros separados de 32 bits. Accedes a uno con un único índice:
mem[5] = 32'hCAFE_BABE; // write word 5
data = mem[address]; // read the word at `address`
Esa es una memoria diminuta que contiene cuadrados. Los diseños reales usan el mismo patrón para mantener register files, tablas de lookup, FIFOs y cualquier otro almacenamiento on-chip que sea más grande que un único vector.
Packed vs unpacked: por qué importa
La división entre dimensiones packed y unpacked aparece por todas partes. Saber cuál es cuál ahorra mucha depuración:
- Un vector packed es una señal. Puedes tratar todo como un número:
data + 1funciona,data == 32'h0funciona,data[7:0]funciona. - Un array unpacked son muchas señales. No puedes tratar todo como un número:
mem + 1es un error de sintaxis. Tienes que sacar una palabra específica primero.
Varias dimensiones packed también son legales:
reg [3:0][7:0] regs; // 4 bytes packed together into a 32-bit signal
regs[0] es un byte (el byte bajo). regs como un todo son 32 bits. SystemVerilog usa esto mucho.
Varias dimensiones unpacked crean una memoria 2D:
reg [31:0] frame [0:479][0:639]; // 480x640 of 32-bit pixels
Accedes a un único píxel con frame[y][x]. Así es como se vería un buffer de imagen en HDL.
Qué viene a continuación
Ya puedes declarar y manipular señales de cualquier ancho que necesites. El siguiente doc - Parameters - muestra cómo hacer esos anchos configurables para que el mismo módulo funcione a 8 bits en una instancia y a 32 en otra. Luego pasaremos a las reglas para escribir números literales (8'b1010_1100, 32'hDEAD_BEEF), y los valores x/z que aparecen cuando algo no está excitado.
Preguntas frecuentes
¿Qué es un vector en Verilog?
Un vector es una señal multibit. Declaras uno añadiendo un rango a un wire o reg: wire [7:0] data es un wire de 8 bits. Los números entre los corchetes son las posiciones de bit - en este caso el bit 7 es el más significativo y el bit 0 es el menos. Puedes hacer slice de bits individuales (data[3]) o de rangos contiguos (data[7:4]).
¿Qué significa [7:0] en Verilog?
[7:0] declara un rango del bit 7 hasta el bit 0, inclusive - una señal de 8 bits con el bit 7 como bit más significativo. El primer número es el índice alto, el segundo es el bajo. También puedes escribir [0:7] para indexación little-endian, pero la forma [high:low] es de lejos la convención más común en código de la industria.
¿Cómo se hace slice de bits en Verilog?
Usa indexación con corchetes. data[3] toma un único bit. data[7:4] toma los cuatro bits altos como un vector de 4 bits. El slice tiene que ir en la misma dirección que la declaración - si declaraste [7:0], haz slice con [high:low]. SystemVerilog también añade data[3 +: 4] para slices con base variable de ancho constante.
¿Cuál es la diferencia entre un array packed y unpacked en Verilog?
Un array packed es un único bus contiguo - reg [31:0] word es una única señal de 32 bits. Un array unpacked (o 'memoria') es una colección de palabras independientes - reg [31:0] mem [0:1023] son 1024 registros separados de 32 bits cada uno. Puedes leer o escribir una palabra completa de un array unpacked pero no puedes operar sobre todo como una sola señal.
¿Cómo se declara una memoria en Verilog?
reg [31:0] mem [0:1023]; declara una memoria de 1024 entradas, cada una de 32 bits de ancho. El primer conjunto de corchetes es el ancho de palabra (packed); el segundo es el número de palabras (unpacked). Accede a una entrada con mem[address], y puedes leer o escribir un slice de esa entrada con mem[address][7:0] una vez que la indexación de SystemVerilog-2005 está habilitada.