J’ai recommencé à jouer avec la communication USB du TinyFPGA, à l’aide de ce programme : https://github.com/davidthings/tinyfpga_bx_usbserial
Je n’ai pas tout saisi le fonctionnement du pipeline, mais ce que j’ai pu observer, c’est qu’en maintenant le signal « uart_valid » de la transmission à 1, le caractère placé dans uart_data est transmis à l’infini, le plus rapidement possible. (J’envoie ici 97, ou « a » en ASCII).
Pour n’envoyer qu’une seule fois le caractère, je compte 2 transitions d’horloge où je maintiens uart_valid à 1. Voici la section du code Verilog (code complet à la fin de l’article) :
reg[26:0] count1hz;
always @(posedge clk_48mhz) begin
if (count1hz == 0)
uart_in_valid <= 1'b1;
if (count1hz >= 10000) //valid doit être à 1 pendant n+1 cycles complets de clock pour transférer n byte
uart_in_valid <= 1'b0;
end
always @(posedge clk_48mhz) begin
if (count1hz >= 47999999) //tick 1Hz
count1hz <= 0;
else
count1hz <= count1hz + 1;
end
assign uart_in_data = 97;
Et voici le programme python . J’utilise python 3.11.3 sur Fedora 38 et pyserial 3.5
import serial
ser = serial.Serial("/dev/ttyACM0")
idx = 0
while(True):
msg_lenght = ser.in_waiting
if msg_lenght > 0:
idx = idx + 1
print(idx)
ser.read(msg_lenght)
Première constatation, le buffer est optimisé pour 64 caractères. Si je maintiens uart_valid pendant plus de 64 transitions d’horloge, seulement 64 caractères se rendent jusqu’à python. Pour arriver à 128 (donc 2 paquets USB), il faut attendre entre 5000 et 10000 transitions. En approximant vers le haut, cela signifie une fréquence de ~5kHz (L’horloge va à 48Mhz). Donc le taux de répétition des paquets est de 10kHz, ce qui fait 640ko/s, ou 6,4 Mbaud.
Honnêtement, je m’attendais à pire. Je lui ai quand même donné une chance en lisant d’un seul coup les 64 octets de son buffer, à l’aide de la commande in_waiting, au lieu d’une boucle for et d’un read sur chaque octet individuel (à proscrire).
Ma mesure concorde avec d’autres sur internet (voir par exemple https://stackoverflow.com/a/56240817, où cela donne 790ko/s).
Comparé au potentiel théorique de l’USB 2.0 de 48Mo/s, on est loin du compte. Toutefois, pour des applications où le débit d’information est bas, pyserial (et python en général) est très simple à utiliser.
Je suis à la recherche d’alternatives pour aller plus vite. Il y a la librairie pyserialtranfer (https://github.com/PowerBroker2/pySerialTransfer), mais je ne suis même pas sûr qu’il y aura un gain de vitesse. Sinon, essayer de voir ce qu’il se fait avec c++. Autre truc intéressant à comprendre, comment être sûr que le protocole à 480Mbit/s est utilisé. Parce que USB1 a la spécification d’aller à 12Mbit/s, ce qui est beaucoup plus dans l’ordre de grandeur que ce que je mesure (6Mbit/s). Donc peut-être que c’est aussi un problème de kernel et d’identification de la part du tinyFPGA. Peut-être (probablement) que l’implémentation de davidthings se base sur le standard USB1. Je ne connais pas les différences fondamentales au niveau du hardware.
EDIT : j’ai vérifié dans le kernel (avec la commande lsusb, puis lsusb -t) et le tinyFPGA est listé comme un device avec 12M de vitesse (USB1) :
Port 3: Dev 102, If 1, Class=CDC Data, Driver=cdc_acm, 12M
Ceci explique cela. La quête vers l’USB2 sera peut-être plus ardue que je le pensais. Cela change un peu ma conclusion : pyserial est une librairie parfaitement adaptée pour l’utilisation avec des devices suivant le protocole USB1.
Le code verilog complet :
/*
Voir le code pour le usb serial ici
https://github.com/davidthings/tinyfpga_bx_usbserial
*/
module uart_top (
input pin_clk,
inout pin_usb_p,
inout pin_usb_n,
output pin_pu,
output pin_led,
);
wire clk_48mhz;
wire clk_locked;
// Use an icepll generated pll
pll pll48( .clock_in(pin_clk), .clock_out(clk_48mhz), .locked( clk_locked ) );
// // LED
// reg [22:0] ledCounter;
// always @(posedge clk_48mhz) begin
// ledCounter <= ledCounter + 1;
// end
// assign pin_led = ledCounter[ 22 ];
// Generate reset signal
reg [5:0] reset_cnt = 0;
wire reset = ~reset_cnt[5];
always @(posedge clk_48mhz)
if ( clk_locked )
reset_cnt <= reset_cnt + reset;
// uart pipeline in
reg [7:0] uart_in_data;
reg uart_in_valid;
reg uart_in_ready;
wire [7:0] uart_out_data;
wire uart_out_valid;
wire uart_out_ready;
// usb uart - this instanciates the entire USB device.
usb_uart uart (
.clk_48mhz (clk_48mhz),
.reset (reset),
// pins
.pin_usb_p( pin_usb_p ),
.pin_usb_n( pin_usb_n ),
// uart pipeline in
.uart_in_data(uart_in_data),
.uart_in_valid(uart_in_valid),
.uart_in_ready(uart_in_ready),
//.uart_out_data(uart_in_data),
//.uart_out_valid(uart_in_valid),
//.uart_out_ready(uart_in_ready)
);
// USB Host Detect Pull Up
assign pin_pu = 1'b1;
reg[26:0] count1khz;
always @(posedge clk_48mhz) begin
if (count1khz == 0)
uart_in_valid <= 1'b1;
if (count1khz >= 10000) //valid doit être à 1 pendant n+1 cycles complets de clock pour transférer n byte
uart_in_valid <= 1'b0;
end
always @(posedge clk_48mhz) begin
if (count1khz >= 47999999) //tick 1Hz
count1khz <= 0;
else
count1khz <= count1khz + 1;
end
assign uart_in_data = 97;
endmodule