Le convertisseur Sigma-Delta intervient dans une chaîne de traitement hybride pour transformer un signal analogique placé en entrée en un signal numérique image. Le résultat obtenu est un train de bits au format PDN, c’est-à-dire une succession de bits dont la densité locale est fonction de la valeur analogique en entrée. On parle aussi couramment de convertisseur 1bit; Pour le compléter, il suffit simplement de placer un compteur à sa sortie pour obtenir la valeur numérique.

Plus en détail, notre convertisseur est un système bouclé avec une rétroaction négative. La chaîne directe comporte un intégrateur suivi d’un comparateur et la chaîne de retour un CNA 1bit.

Son principal avantage est d’être une structure simple qui permet de pouvoir échantillonner à une fréquence beaucoup plus élevée que les CAN traditionnels, tout en offrant une bonne quantification.

Mais ce qui est le plus intéressant est sa structure duale pour la conversion numérique-analogique, car il s’agit exactement de la même structure[1]. On retrouve cette fois-ci en entrée un signal numérique au format PCM, et en sortie exactement[2] le même train de bits. Il suffit de remplacer le compteur que l’on avait ajouté par un filtre passe-bas pour obtenir le signal analogique[3].

Certains feront remarquer à raison qu’il n’y a pas d’intérêt visible puisque l’on dispose du signal au format PCM; un simple réseau de résistance suffit à faire la conversion. Sauf que si nous cherchons à convertir en parallèle 16 voies[4] de 24bits de cette manière, outre le fait que cela prenne de la place, il faut avoir recours a des résistances d’une trop grande précision. Le Sigma-Delta n’a pas ce désavantage-la et n’est pas non plus conditionné par la qualité du filtre en sortie.

Maintenant que je sais comment ça marche et que je dispose de documentation, il ne me restait plus qu’à ressortir mon FPGA et refaire un peu de VHDL.

Commençons simplement par définir un package sigma_delta_package dans lequel nous allons placer notre composant sigma_delta_entity, puis le squelette de son architecture.

  • listing: sigma_delta_pkg.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
 
package sigma_delta_package is
	constant resolution : integer := 8;
 
	subtype pcm_input_type is std_logic_vector (resolution - 1 downto 0);
 
	component sigma_delta_entity is
		port
		(
			clock  : in  std_logic;
			reset  : in  std_logic;
 
			pcm_in : in  pcm_input_type;
			bs_out : out std_logic
		);
	end component sigma_delta_entity;
end package sigma_delta_package;
 
package body sigma_delta_package is
end package body sigma_delta_package;
  • listing: sigma_delta.vhd
library ieee;
use ieee.std_logic_1164.all;
use ieee.numeric_std.all;
 
library work;
use work.sigma_delta_package.all;
 
entity sigma_delta
is
	port
	(
		clock  : in  std_logic := 'U';
		reset  : in  std_logic := 'U';
 
		pcm_in : in  pcm_input_type := (others => 'U');
		bs_out : out std_logic := '0'
	);
end entity sigma_delta;
 
architecture simple_sigma_delta_architecture of sigma_delta
is
	--
begin
	--
end architecture simple_sigma_delta_architecture;

Nous définissons ensuite tous les signaux intermédiaires utiles. Le sous-type processing_type est signé pour permettre de faciliter les calculs numériques, sa taille supérieur de deux bits à pcm_input_type évite les débordement en particulier lors de l’intégration.

subtype processing_type is signed (pcm_in'left + 2 downto 0);
 
signal
	input,
	difference,
	sum,
	feedback : processing_type := (others => 'U');
 
signal output : std_logic := 'U';

Dans un premier temps, il faut convertir notre signal PCM d’un représentation binaire non-signée en signée pour la suite des calculs. Pour cela, nous le lisons directement sous la forme d’un entier signé et nous en soustrayons la moitié de son étendue originale. Le calcul de l’écart ε se fait trivialement.

input <= signed("00" & pcm_in) - 2**(pcm_in'left);
difference <= input - feedback;

Pour l’intégration, nous avons recours à un processus hybride avec une remise à zéro asynchrone; idem le calcul est trivial.

sigma_process : process (clock, reset, difference)
begin
	if reset = '1'
	then
		sum <= to_signed (0, sum'length);
	elsif rising_edge (clock)
	then
		sum <= sum + difference;
	end if;
end process sigma_process;

Le comparateur est simplifié de la façon suivante: puisque l’on souhaite comparer par rapport à zéro[5], il suffit de prendre le bit de poids fort du nombre entrée qui représente le signe dans un nombre binaire signé en complément à deux.

output <= sum (sum'left);
bs_out <= output;

Enfin, la boucle de retour est crée à l’aide d’un sélecteur.

feedback <= to_signed (2**(pcm_in'left) - 1, feedback'length) when output = '1'
else to_signed (-2**(pcm_in'left) - 1, feedback'length);

Et voila comment faire un Sigma-Delta ! :)

Au passage, j’ai eu un sacré échec: ma carte de développement dispose bien d’embases Jack 3.5mm, mais celles-ci sont reliées à un CODEC et il n’y a pas moyen de le bypass, ni physiquement sur la carte, ni en l’attaquant avec son interface 3-wire. Du coup je pense que je vais sortir le fer à souder et trouver quelque chose à désosser. ;)

Update à venir si j’arrive à faire marcher un pseudo-synthé.

Notes

[1] hormis le fait que les calculs se fassent numériquement

[2] dans la mesure de la précision de la conversion

[3] la PDN permet cette reconstruction du signal d’origine dans la mesure où la fréquence du signal numérique reste très supérieure à la fréquence de coupure du filtre passe-bas

[4] voix?

[5] la valeur du seuil n’a pas d’importance