Verilog Práctico

Capitulo 6. Matrices de Ledes y Memorias ROM

6 de febrero de 2021

Siguiendo con los tutoriales sobre displays, a continuación veremos cómo controlar una pequeña matriz de ledes. Además, el dato a mostrar por el display lo almacenaremos en una memoria ROM. 

Se trata de una matriz de 5x7 ledes modelo HDSP-703G. Cada fila tiene los cátodos comunes, como un display de 7 segmentos. 

La forma de controlar este tipo de matrices es similar a la que vimos en el capítulo 5 dedicado a los display de 4x7 segmentos.


Lo que haremos será multiplexar las filas, es decir, enviaremos datos de 5 bits por las columnas y solo iluminaremos una fila cada vez, pero muy rápidamente, de manera que parezca que todos los ledes se encienden a la vez.

Por ejemplo, si queremos mostrar la letra R programaremos la siguiente secuencia, sincronizando el dato de 5 bits con la columna correspondiente. 

Esquema


Como en ocasiones anteriores, me he ayudado del CI 74HC244 para mejorar la calidad de las señales del bus de datos.

1. Conmutación manual con sw1


Decíamos que el dato a mostrar lo vamos a almacenar en una ROM. Sin saberlo ya hemos utilizado memorias ROM en tutoriales anteriores; una estructura case (x) es una ROM, donde el valor de x es la dirección y la palabra que almacena es la variable dato.


Por ejemplo, la siguiente ROM tiene 7 palabras de 5 bits cada una que se direccionan con los números 0 a 6.


Dirección: Dato:

0 11110

1 10001

2 10001

3 11110

4 10100

5 10010

6 10001


La forma de implementar una ROM con una estructura case (x) es la siguiente:


      3'd0: dato <= 5'b11110;

      3'd1: dato <= 5'b10001;

      3'd2: dato <= 5'b10001;

      3'd3: dato <= 5'b11110;

      3'd4: dato <= 5'b10100;

      3'd5: dato <= 5'b10010;

      3'd6: dato <= 5'b10001;


Por supuesto, el tamaño de una ROM lo podemos codificar como queramos, tanto en el tamaño de palabra, como en número de direcciones.


En este primer ejemplo vamos a implementar la secuencia que vimos al principio para mostrar la letra R.


Con cada pulsación de sw1 iremos introduciendo datos por las columnas y a la vez conectamos  a GND mediante el ULN2803 la fila correspondiente, para cerrar el circuito e iluminar los ledes.


A continuación el código en Verilog 

module HDSP703G(

input sw1,

output reg [6:0] filas,

output reg [4:0] columnas

);


// modulo multiplexor para las filas

reg [2:0] mpx;

always @ (posedge sw1)

begin

    mpx <= mpx + 1;

    case (mpx)

      3'd0: begin

        columnas <= 5'b11110;

        filas <= 7'b0000001;

        end

      3'd1: begin

        columnas <= 5'b10001;

        filas <= 7'b0000010;

        end

      3'd2: begin

        columnas <= 5'b10001;

        filas <= 7'b0000100;

        end

      3'd3: begin

        columnas <= 5'b11110;

        filas <= 7'b0001000;

        end

      3'd4: begin

        columnas <= 5'b10100;

        filas <= 7'b0010000;

        end

      3'd5: begin

        columnas <= 5'b10010;

        filas <= 7'b0100000;

        end

      3'd6: begin

        columnas <= 5'b10001;

        filas <= 7'b1000000;

        end

    endcase

end

endmodule

Archivo .pcf


set_io sw1 10

set_io filas[0] 113

set_io filas[1] 114

set_io filas[2] 115

set_io filas[3] 116

set_io filas[4] 117

set_io filas[5] 118

set_io filas[6] 119

set_io columnas[4] 78

set_io columnas[3] 79

set_io columnas[2] 80

set_io columnas[1] 81

set_io columnas[0] 88



Subimos el código a la Alhambra y con cada pulsación de sw1 veremos la siguiente secuencia.

2. Conmutación automática con prescaler

Este ejemplo es igual que el anterior, pero utilizando un prescaler para conmutar las filas y que parezca que todos los ledes están iluminados a la vez.

module HDSP703G(

input clk,

output reg [6:0] filas,

output reg [4:0] columnas

);

wire clk_digitos;

// modulo prescaler

reg [23:0] prescaler;

assign clk_digitos = prescaler[13]; //velocidad conmutacion filas


always @(posedge clk)

begin

  prescaler <= prescaler + 1;

end


// modulo multiplexor para las filas

reg [2:0] mpx;

always @ (posedge clk_digitos)

begin

    mpx <= mpx + 1;

    case (mpx)

      3'd0: begin

        columnas <= 5'b11110;

        filas <= 7'b0000001;

        end

      3'd1: begin

        columnas <= 5'b10001;

        filas <= 7'b0000010;

        end

      3'd2: begin

        columnas <= 5'b10001;

        filas <= 7'b0000100;

        end

      3'd3: begin

        columnas <= 5'b11110;

        filas <= 7'b0001000;

        end

      3'd4: begin

        columnas <= 5'b10100;

        filas <= 7'b0010000;

        end

      3'd5: begin

        columnas <= 5'b10010;

        filas <= 7'b0100000;

        end

      3'd6: begin

        columnas <= 5'b10001;

        filas <= 7'b1000000;

        end

    endcase

end

endmodule

Archivo .pcf 

set_io clk 21

set_io filas[0] 113

set_io filas[1] 114

set_io filas[2] 115

set_io filas[3] 116

set_io filas[4] 117

set_io filas[5] 118

set_io filas[6] 119

set_io columnas[4] 78

set_io columnas[3] 79

set_io columnas[2] 80

set_io columnas[1] 81

set_io columnas[0] 88

Subimos el código a la Alhambra y vemos la letra R en el display. 


Podemos jugar con la frecuencia de conmutación, por ejemplo, si ponemos un valor elevado en el prescaler assign clk_digitos = prescaler[22] veremos cómo se iluminan las filas una por una. 

3. Lectura de una ROM desde archivo.

En este ejemplo vamos a codificar la ROM en un archivo de texto y con el sw1 iremos direccionando las palabras de datos una por una para comprobar que funciona correctamente.


Las palabras se pueden separar por filas o con un espacio y el archivo debe tener extensión .list

Para direccionar las palabras de memoria utilizaremos un registro de 3 bits → máximo posibles 8 direcciones: reg [2:0] addres = 0;


Con cada pulsación de sw1 incrementamos el registro addres 


always @ (posedge sw1 ) begin

  addres <= addres + 1;


Para codificar una ROM en un archivo debemos indicar el tamaño de la palabra (ancho) y el número de palabras de datos que contiene (alto): reg [4:0] ROM [0:7];


ROM de 8 palabras de 5 bits cada una:


11111

11110

10001

10001

11110

10100

10010

10001


Para leer la memoria ROM se utiliza el comando $readmemb("rom_x.list", ROM);


Con el comando assign data = ROM [addres]; asignamos al registro data el valor de la memoria ROM.


Con la estructura case (addres) sincronizamos el dato con la fila correspondiente.


Ponemos todo junto y el código para la Alhambra queda asi:

// memoria ROM desde archivo

module HDSP703G(input sw1, output [4:0] data, output reg [6:0] filas);

reg [2:0] addres = 0;


always @ (posedge sw1 ) begin

  addres <= addres + 1;

  case (addres)

  3'd0: begin

    filas <= 7'b0000001;

    end

  3'd1: begin

    filas <= 7'b0000010;

    end

  3'd2: begin

    filas <= 7'b0000100;

    end

  3'd3: begin

    filas <= 7'b0001000;

    end

  3'd4: begin

    filas <= 7'b0010000;

    end

  3'd5: begin

    filas <= 7'b0100000;

    end

  3'd6: begin

    filas <= 7'b1000000;

    end

  3'd7: begin

    filas <= 7'b0000000;

    end

endcase

end


reg [4:0] ROM [0:7];

assign data = ROM [addres];


initial begin


        $readmemb("rom_x.list", ROM);


end

endmodule

Archivo .pcf


set_io sw1 10

set_io filas[0] 113

set_io filas[1] 114

set_io filas[2] 115

set_io filas[3] 116

set_io filas[4] 117

set_io filas[5] 118

set_io filas[6] 119

set_io data[4] 78

set_io data[3] 79

set_io data[2] 80

set_io data[1] 81

set_io data[0] 88 

Lo subimos a la FPGA y el resultado es igual que en el ejemplo 1, solo que ahora estamos leyendo los datos desde un archivo de texto. 

4. Lectura de una ROM desde archivo con direccionamiento automático.

Este código es similar al anterior, pero ahora el direccionamiento de la ROM se hace por flanco de subida de clk_out

// lectura de una ROM desde archivo

// direccionamiento automático con prescaler

module HDSP703G(input clk, output [4:0] data, output reg [6:0] filas);

reg [2:0] addres = 0;

wire clk;

wire clk_out;


reg [23:0] prescaler = 0;

assign clk_out = prescaler[13];


always @ ( posedge clk ) begin

  prescaler <= prescaler + 1;

end

reg [2:0] mpx = 0;

always @ ( posedge clk_out ) begin

  addres <= addres + 1;

  case (addres)

  3'd0: begin

    filas <= 7'b0000001;

    end

  3'd1: begin

    filas <= 7'b0000010;

    end

  3'd2: begin

    filas <= 7'b0000100;

    end

  3'd3: begin

    filas <= 7'b0001000;

    end

  3'd4: begin

    filas <= 7'b0010000;

    end

  3'd5: begin

    filas <= 7'b0100000;

    end

  3'd6: begin

    filas <= 7'b1000000;

    end

  default: filas <= 7'b0000000;

endcase

end


reg [4:0] ROM [0:7];

assign data = ROM [addres];


initial begin


        $readmemb("rom_R.list", ROM);


end

endmodule

Archivo .pcf


set_io clk 21

set_io filas[0] 113

set_io filas[1] 114

set_io filas[2] 115

set_io filas[3] 116

set_io filas[4] 117

set_io filas[5] 118

set_io filas[6] 119

set_io data[4] 78

set_io data[3] 79

set_io data[2] 80

set_io data[1] 81

set_io data[0] 88 

Lo subimos a la Alhambra y vemos en el display la letra R. Igual que en el ejemplo 2, podemos variar la velocidad de refresco de las filas con el comando: assign clk_out = prescaler[13]; 

5. Lectura de múltiples ROM’s desde archivo.

En este último ejemplo, mostraremos en el display el mensaje HOLA MUNDO. Cada letra es una memoria ROM codificada en un archivo de texto. 


Utilizaremos dos prescaler:


assign clk_out = prescaler[13]; para multiplexar las filas como en el ejemplo 2

assign clk_rom = prescaler[22]; para seleccionar la velocidad de lectura de las ROM, es decir la velocidad a la que se muestran las letras.


A continuación, el código.

// lectura de multiples rom desde archivo

module HDSP703G(input clk, output [4:0] data, output reg [6:0] filas);

reg [2:0] addres = 0;

wire clk;

wire clk_out;

wire clk_rom;


reg [23:0] prescaler = 0;

assign clk_out = prescaler[13];

assign clk_rom = prescaler[22];


always @ ( posedge clk ) begin

  prescaler <= prescaler + 1;

end

reg [2:0] mpx = 0;

always @ ( posedge clk_out ) begin

  addres <= addres + 1;

  case (addres)

  3'd0: begin

    filas <= 7'b0000001;

    end

  3'd1: begin

    filas <= 7'b0000010;

    end

  3'd2: begin

    filas <= 7'b0000100;

    end

  3'd3: begin

    filas <= 7'b0001000;

    end

  3'd4: begin

    filas <= 7'b0010000;

    end

  3'd5: begin

    filas <= 7'b0100000;

    end

  3'd6: begin

    filas <= 7'b1000000;

    end

  default: filas <= 7'b0000000;

endcase

end


reg [4:0] ROM [0:7];

assign data = ROM [addres];

reg [3:0] cont = 0;

always @ ( posedge clk_rom ) begin

  if (cont>=4'd11)

    cont = 4'd0;


  cont <= cont + 1;

  case (cont)

    4'd0: begin

      $readmemb("4_.list", ROM);

      end

    4'd1: begin

      $readmemb("0_H.list", ROM);

      end

    4'd2: begin

      $readmemb("1_O.list", ROM);

      end

    4'd3: begin

      $readmemb("2_L.list", ROM);

      end

    4'd4: begin

      $readmemb("3_A.list", ROM);

      end

    4'd5: begin

      $readmemb("4_.list", ROM);

      end

    4'd6: begin

      $readmemb("5_M.list", ROM);

      end

    4'd7: begin

      $readmemb("6_U.list", ROM);

      end

    4'd8: begin

      $readmemb("7_N.list", ROM);

      end

    4'd9: begin

      $readmemb("8_D.list", ROM);

      end

    4'd10: begin

      $readmemb("1_O.list", ROM);

      end

  endcase

end

endmodule

Archivo .pcf


set_io clk 21

set_io filas[0] 113

set_io filas[1] 114

set_io filas[2] 115

set_io filas[3] 116

set_io filas[4] 117

set_io filas[5] 118

set_io filas[6] 119

set_io data[4] 78

set_io data[3] 79

set_io data[2] 80

set_io data[1] 81

set_io data[0] 88 

Subimos el código a la Alhambra y vemos como el display muestra HOLA MUNDO. 

Eso es todo, espero que os haya gustado


¡Hasta pronto!

Links


Verilog wiki


ASIC WORLD modelado de memorias


Descarga los ejemplos en Verilog aquí abajo