Implémenter un BuildCounter en VHDL

Posté le 16 septembre 2021 dans Hardware

Lorsque l'on utilise un FPGA dans un système, il peut être nécessaire de connaitre la version du bitstream chargé dans le FPGA.

Cela peut-être nécessaire lorsque le FPGA est associé à une mémoire de configuration, par exemple.
Une nouvelle version logicielle devra alors déterminer si le bitstream stocké dans la mémoire doit être mis à jour.

Pour cela, un BuildCounter peut être utilisé. Il devra pouvoir être lu d'une façon ou d'une autre bien sûr, mais ce n'est pas le but de cet article.
Pour être utile, encore faut-il qu'il soit à jour.
Qui n'a pas attendu 1 journée (ou plus) de compilation pour se rendre compte que ce fameux identifiant n'a pas été modifié ?

Je vous propose deux codes pour en gérer un automatiquement.

Le premier, que j'utilise depuis des années, est en VHDL. Le deuxième, que j'ai écrit récemment, est en VHDL-2008.



Voici le premier code, en VHDL.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;
use std.textio.all;
use IEEE.STD_LOGIC_TEXTIO.all;

entity BuildCounter is
Generic
(
    BUILD_COUNTER_SIZE : positive := 16
);
Port
(
    BuildCounter : out std_logic_vector(BUILD_COUNTER_SIZE-1 downto 0)
);
end BuildCounter;


architecture Behavioral of BuildCounter is

constant FILE_NAME : string := "../../Sources/BuildCounter.txt"; -- Path relative to working directory

impure function BuildCounterFromFile (BCFileName : in string) return std_logic_vector is
FILE BCFile : text is in BCFileName;
variable BCFileLine : line;
variable BCounter : STD_LOGIC_VECTOR(BUILD_COUNTER_SIZE-1 downto 0);

begin
    readline (BCFile, BCFileLine);
    read (BCFileLine, BCounter);
    report "File name : " & BCFileName;
    report "Build counter value : " & integer'Image(to_integer(unsigned(BCounter)));
    return BCounter;
end function;


constant BC : STD_LOGIC_VECTOR (BUILD_COUNTER_SIZE-1 downto 0) := BuildCounterFromFile(FILE_NAME);

file  BCFile : text;

begin

    process
    variable file_status : file_open_status;
    variable BCFileLine  : line;
    variable run : natural := 0;

    begin
        file_open(file_status, BCFile, FILE_NAME, write_mode);
        write(BCFileLine, std_logic_vector(unsigned(BC)+1));
        writeline(BCFile, BCFileLine);
        file_close(BCFile);
        wait until run=1;
    end process;

    BuildCounter <= BC;

end Behavioral;
L'entité comporte un seul port, la sortie BuildCounter, qui contient le numéro de build.
Le paramètre générique BUILD_COUNTER_SIZE permet de fixer la taille de BuildCounter.
La constante FILE_NAME contient le chemin vers le fichier contenant la valeur du BuildCounter.
La fonction BuildCounterFromFile lit le fichier dont le nom est passé en paramètre et renvoie la valeur lue. Les report contenus dans cette fonction permettent d'insérer les informations utiles dans le logfile du synthétiseur.
La constante BC est initialisée avec le contenu courant du fichier en utilisant la fonction BuildCounterFromFile.
Enfin, le process contenu dans le corps de l'entité (ligne 43) ouvre le fichier en écriture, écrit le nouveau BuildCounter dans le fichier, ferme le fichier et attend que la variable run passe à 1 ; c'est dire, attend indéfiniment.

Ce code est sensible. J'ai dû le retoucher au fur et à mesure de la sortie des versions de ISE/Vivado. La variable run, par exemple ne devrait pas être nécessaire, et ne l'est peut-être plus (je n'ai pas essayé). Elle a été introduite pour contourner une erreur de synthèse.
Le contenu du fichier est également sensible. La première ligne du fichier doit contenir un nombre binaire dont le nombre de bits est exactement égal à BUILD_COUNTER_SIZE.

Le nombre contenu dans le fichier au moment de la synthèse est celui qui sera utilisé dans le design.
Après synthèse, le nombre contenu dans le fichier est incrémenté de 1... Si tout s'est bien passé ;)

Attention : Cette entité ne doit être instanciée qu'une seule fois dans le design. Sinon, le comportement ne sera pas forcément consistent d'une synthèse à l'autre. Surtout lorsque la synthèse multi-tâches est activée.
En passant le nom du fichier dans un paramètre générique, il devient possible d'instancier cette entité plusieurs fois, à condition de passer un nom de fichier différent à chaque instance.

Avec cette méthode, Il faut être conscient que le numéro de build est incrémenté à chaque fois que l'entité est synthétisée. Cela veut dire que lors d'une phase de mise au point, même si la synthèse du design se termine en erreur, le BuildCounter sera quand même incrémenté si l'entité BuildCounter a été synthétisée. Le BuildCounter ne s'incrémente pas seulement lors d'une synthèse du design sans erreur.

Voici maintenant la version VHDL-2008.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
library IEEE;
use IEEE.STD_LOGIC_1164.ALL;
use ieee.numeric_std.all;
use std.textio.all;
use IEEE.STD_LOGIC_TEXTIO.all;

entity BuildCounter is
Port
(
    Build_Counter : out std_logic_vector
);
end BuildCounter;


architecture Behavioral of BuildCounter is

constant FILE_NAME : string := "../../Sources/BuildCounter.txt"; -- Path relative to working directory

pure function str_to_natural (Str : in String) return Natural is
variable Val : Natural := 0;
variable FirstCharFound : Boolean := False;
begin
    for i in Str'Range loop
        case Str(i) is
            when '0' => Val := Val * 10; Val := Val + 0; FirstCharFound := True;
            when '1' => Val := Val * 10; Val := Val + 1; FirstCharFound := True;
            when '2' => Val := Val * 10; Val := Val + 2; FirstCharFound := True;
            when '3' => Val := Val * 10; Val := Val + 3; FirstCharFound := True;
            when '4' => Val := Val * 10; Val := Val + 4; FirstCharFound := True;
            when '5' => Val := Val * 10; Val := Val + 5; FirstCharFound := True;
            when '6' => Val := Val * 10; Val := Val + 6; FirstCharFound := True;
            when '7' => Val := Val * 10; Val := Val + 7; FirstCharFound := True;
            when '8' => Val := Val * 10; Val := Val + 8; FirstCharFound := True;
            when '9' => Val := Val * 10; Val := Val + 9; FirstCharFound := True;
            when ' ' => if FirstCharFound then return Val; end if;
            when others => return Val;
        end case;
    end loop;
    return Val;
end function;

impure function BuildCounterFromFile (BC_FileName : in string) return Natural is
File BC_File : text open read_mode is BC_FileName;
variable FileLine : line;
variable Counter     : Natural;
variable CounterStr  : String (1 to 32);
variable LineLength  : Natural;
begin
    report "Build counter file name : " & BC_FileName;
    readline (BC_File, FileLine);
    --file_close(BC_File);
    LineLength := FileLine'Length;
    CounterStr := (others => ' ');
    read (FileLine, CounterStr(1 to LineLength));
    --Counter := Integer'Value(CounterStr(1 to LineLength));
    Counter := str_to_natural(CounterStr(1 to LineLength));
    report "Build counter value : " & Natural'Image(Counter);
    return Counter;
end function;


constant Counter    : Natural := BuildCounterFromFile(FILE_NAME);
constant CounterStr : String := Natural'Image(Counter + 1);

File BC_File : text;

begin

    process
    variable file_status : file_open_status;  -- @suppress "variable file_status is never read"
    variable FileLine : line;

    begin
        file_open(file_status, BC_File, FILE_NAME, write_mode);
        write(FileLine, CounterStr);
        writeline(BC_File, FileLine);
        file_close(BC_File);
        wait;
    end process;

    Build_Counter <= std_logic_vector(to_unsigned(Counter, Build_Counter'Length));

end Behavioral;
Globalement, le fonctionnement est le même que le code précédent mais en bénéficiant des avantages de VHDL-2008.

Entre autres, il n'y a plus de paramètre générique et le port Build_Counter n'est plus contraint. C'est donc lors de l'instanciation que sa taille est déterminée.
On notera que la déclaration du fichier BC_File dans la fonction BuildCounterFromFile (ligne 43) a changé pour être adaptée à VHDL-2008.
La variable run a disparu.
La ligne 51 est en commentaire mais pourrait être active. Le fichier étant automatiquement fermé à la sortie de la fonction, cette ligne n'est pas indispensable. La rendre active permettrait de se protéger contre une utilisation imprévue du fichier plus loin dans le code.

J'ai profité cette version pour rendre le contenu du fichier contenant le BuildCounter plus lisible. Le contenu est maintenant en décimal.
A ce sujet, la ligne 55 devrait fonctionner mais ce n'est pas le cas. J'ai donc créé la fonction str_to_natural pour contourner le problème. Cette fonction élimine les caractères autres que des chiffres se trouvant avant le nombre lu et s'arrête dès qu'un caractère autre qu'un chiffre est détecté.

Les restrictions et recommandations d'usages de la version VHDL s'appliquent également à cette version en VHDL-2008.

Le support de VHDL-2008 par le synthétiseur de Vivado est imparfait mais s'améliore un peu plus à chaque version. Le code proposé ici a été testé avec Vivado 2021.1.



Note

Ces codes source n'ont été testés qu'avec les outils Xilinx. Il est fort probable qu'ils ne soient pas utilisables avec d'autres chaines de développement. La raison principale est qu'en VHDL, la lecture/écriture de fichiers est présente, à la base, pour être utilisée lors de la simulation. Lecture de vecteurs de test, écriture de fichiers de compte rendu de simulation... Le synthétiseur de Xilinx permet de lire et écrire des fichiers lors de la synthèse, avec des restrictions.