Enfin, mes mains ont atteint l'étude des FPGA. Mais d'une manière ou d'une autre, cela se passe mal: j'écris des pilotes pour le matériel pour Linux, je programme des microcontrôleurs, je lis les circuits (et je conçois un peu), j'ai besoin de grandir davantage.
Puisqu'il ne m'a pas semblé intéressant de faire clignoter les LED, j'ai décidé de faire une chose simple. À savoir, écrivez les modules de récepteur et d'émetteur pour UART, combinez-les à l'intérieur du FPGA (en même temps, comprenez comment utiliser IP Core) et testez le tout sur du matériel réel.
Je dis tout de suite qu'il ne s'agissait pas de faire un noyau paramétré universel de la tâche. Ceci est juste un projet de test, sur le thème "sentir ce qu'est le FPGA et comment communiquer avec lui".
Commençons donc par le récepteur. L'algorithme est assez
bien décrit , donc je ne répéterai ici que ses principaux points.
- La fréquence d'échantillonnage du signal RX est quatre fois supérieure au taux de transmission UART requis.
- La condition de début de réception est considérée comme la transition du signal d'entrée RX d'un niveau haut à un niveau bas si la réception n'est pas en cours.
- La condition pour une identification fiable du bit de départ est considérée comme étant la rétention du signal RX dans un état bas au deuxième cycle d'horloge de la fréquence d'échantillonnage. Dans le même temps, nous tombons pratiquement au milieu de l'impulsion de bit, ce qui nous permettra d'échantillonner davantage les impulsions tous les 4 cycles.
- En cas d'erreur dans les bits de démarrage ou d'arrêt, définissez l'erreur du signal d'erreur. Sur cette base, nous formons le signal Fastsync, que nous utiliserons à l'avenir pour une synchronisation rapide du récepteur.
- Après avoir identifié le bit de départ, nous commençons la réception séquentielle des bits de données, en commençant par le plus jeune. Les données reçues sont écrites dans le registre avec un décalage vers la droite d'un bit. La condition de fin de réception sera la détection d'un bit de départ à la 0ème position du registre à décalage.
- La synchronisation rapide du récepteur consiste à le ramener à son état d'origine après avoir détecté une erreur lorsque le signal RX passe à un niveau élevé (il peut s'agir soit d'une transmission logique "1", d'une transmission à bit d'arrêt ou d'un état de repos de la ligne de transmission).
- La condition pour la réussite de la réception (valeurs correctes des bits de démarrage et d'arrêt) est le signal complet. À partir de celui-ci (lorsqu'il est cadencé par le signal rdclk), un signal d'impulsion prêt est généré, qui indique la présence de données valides sur le bus rxdata.
Je noterai tout de suite que je ne voulais pas synchroniser le signal de lecture à partir de l'horloge clk (de manière inattendue, non?), Afin de ne pas lier la vitesse du traitement des données ultérieur au taux de change UART. Une implémentation similaire se trouve dans le module émetteur (
voir ci-dessous ). Un groupe de test de modules de récepteur et d'émetteur est réalisé sur la base du processeur IP Core FIFO d'Intel et avec la capacité de simuler différentes vitesses pour le consommateur et le générateur de données. La seule limitation est que la fréquence d'horloge du producteur et du consommateur des données ne doit pas être inférieure à la fréquence d'horloge clk.
Module récepteur (Verilog)// // UART // // rxdata , ready==1 error==0. // ready 1 rdclk. // // : // rx // 2- . , . // 8 - ( 9 ). // 2 - , // . // // . // . '0' , . '1' // idle (. '1') // start- (. '0') // stop- (. '1') module uart_rx( nreset, // (, 0) clk, // UART, .. UART rx, // UART rdclk, // (rxdata, ready) rxdata, // , ready==1 ready, // rxdata ( 1) error, // ( 1) busy, // ( , 1) idle); // ( 1) input wire nreset; // (, 0) input wire clk; // , .. UART input wire rx; // UART input wire rdclk; // output wire[7:0] rxdata; output wire ready; output error; output busy; output idle; // , rdclk reg[2:0] done = 3'b000; // , rdclk //assign ready = (done == 2'b10) ? 1'b1 : 1'b0; assign ready = (done[1] && !done[0]) ? 1'b1 : 1'b0; // reg error = 1'b0; // // error // rx, . wire fastsync = (error && rx); // reg idle = 1'b1; // : // d[9] - , .. == 1 // d[8:1] - // d[0] - , .. == 0 reg[9:0] d = 10'b1xxxxxxxx1; // . 2'b10 wire[1:0] status = { d[9], d[0] }; // . wire complete = (status == 2'b10) ? 1'b1 : 1'b0; // assign rxdata = d[8:1]; // reg busy = 0; // rx reg[1:0] cnt; always @(posedge clk, negedge nreset) begin if(!nreset) begin rxreset(); end else begin if(fastsync) begin rxreset(); end else begin if(busy == 1'b1) begin // -, rx if(cnt == 2'd0) begin // // // (.. ) d <= { rx, d[9:1] }; if(d[1] == 1'b0) begin // , busy <= 1'b0; // error <= (rx == 1'b1) ? 1'b0 : 1'b1; end else begin // if(rx && (d == 10'b1111111111)) begin // busy <= 1'b0; // error <= 1'b1; end else begin // // - - cnt <= 2'd3; end end end else begin // - cnt <= cnt - 2'd1; end end else begin // if(!error) begin // , if(rx == 1'b0) begin // - busy <= 1'b1; // . 1, .. // d[0]==0 d <= 10'b1111111111; // rx 1/2 // 1- - // 2- - (cnt 0) cnt <= 2'd0; // .. , idle <= 1'b0; end else begin // idle <= 1'b1; end end end end end end task rxreset; begin // error <= 1'b0; // (!?) idle <= 1'b1; // busy <= 0; // -, complete d <= 10'b1xxxxxxxx1; end endtask always @(negedge rdclk, negedge nreset) begin if(!nreset) begin done <= 3'b000; end else begin // complete. // ready // complete 0 1 rdclk. done <= { complete, done[2:1] }; end end endmodule
Le signal d'entrée RX étant asynchrone et (éventuellement) instable, un
élément majoritaire a été connecté devant le module récepteur dans le
module principal . L'élément est également écrit en Verilog, mais son code n'a pas de sens ici. Au lieu de cela, une belle image d'un élément synthétisé.
Le schéma synthétisé de l'élément majoritaire L'émetteur est encore plus simple et, je l'espère, n'a pas besoin de commentaires supplémentaires.
Module émetteur (affectations bloquantes et non bloquantes Verilog à l'intérieur toujours) // // UART // // : // clk - 4 , // rdclk - txdata, write, fetch. .. clk // txdata - , write/fetch // write - (1=) // fetch - (1=) // tx - UART // idle - (1=, ) // // FIFO dcfifo_component.lpm_showahead = "ON" module uart_tx( nreset, // (, 0) clk, // UART, .. UART rdclk, // txdata, // write, // ( 1) idle, // ( 1) fetch, // , rdclk tx); // UART input wire nreset; // (, 0) input wire clk; // UART input wire rdclk; input wire[7:0] txdata; input wire write; output wire idle; output fetch; output tx; // reg tx = 1'b1; reg fetch = 1'b0; // 4 reg[1:0] div4 = 2'd0; // : reg[3:0] s = 4'd10; // assign idle = (s == 4'd10); // reg[7:0] d; // reg sendstart; // reg canfetch; // , clk reg gotdata = 1'b0; // clock domains reg[2:0] sync = 3'b000; // rdclk write reg wr = 1'b0; // getdata==1 // nextdata // gotdata==1. , // . // gotdata getdata. always @(posedge rdclk, negedge nreset) begin if(!nreset) begin wr <= 1'b0; sync <= 3'b000; // fetch <= 1'b0; end else begin // write wr <= write; // sync <= { gotdata, sync[2:1] }; // gotdata // . // . fetch <= (sync[1] && !sync[0]) ? 1'b1 : 1'b0; end end always @(posedge clk, negedge nreset) begin if(!nreset) begin // div4 <= 2'd0; s <= 4'd10; gotdata <= 1'b0; end else begin // sendstart = 1'b0; // canfetch = wr; if(div4 == 2'd0) begin case(s) 4'd0: begin // sendstart = 1'b1; // , canfetch = 1'b0; end 4'd9: begin // tx <= 1'b1; end 4'd10: begin // idle, end default: begin // , tx <= d[0]; // d <= { 1'b0, d[7:1] }; // , canfetch = 1'b0; end endcase end else begin // div4 <= div4 - 2'd1; if(s < 4'd9) begin // 9 ! canfetch = 1'b0; end end if(canfetch) begin // , d <= txdata; // gotdata <= 1'b1; if(idle ) begin // idle - sendstart = 1'b1; end else begin // s <= 4'd0; end end if(gotdata) begin // , gotdata <= 1'b0; end if(sendstart) begin // tx <= 1'b0; // s <= 4'd1; // div4 <= 2'd3; end else begin if(div4 == 2'd0) begin if(s < 4'd10) begin // s <= s + 4'd1; // div4 <= 2'd3; end end end end end endmodule
La mise en œuvre de l'émetteur ci-dessus a provoqué une discussion animée dans les commentaires. Bien que, par conséquent, tout le monde semblait convenir qu'il était possible de le faire, mais avec prudence. Pour votre tranquillité d'esprit, le module a été réécrit en tenant compte de toutes les directives mentionnées. À mon avis, il n'est pas beaucoup plus compliqué que le précédent du point de vue de la perception humaine de l'algorithme implémenté.
Module émetteur (Verilog, idéologiquement correct) // // UART // // : // clk - 4 , // rdclk - txdata, write, fetch. .. clk // txdata - , write/fetch // write - (1=) // fetch - (1=) // tx - UART // idle - (1=, ) // // FIFO dcfifo_component.lpm_showahead = "ON" module uart_tx( nreset, // (, 0) clk, // UART, .. UART rdclk, // txdata, // write, // ( 1) idle, // ( 1) fetch, // , rdclk tx); // UART input wire nreset; // (, 0) input wire clk; // UART input wire rdclk; input wire[7:0] txdata; input wire write; output wire idle; output fetch; output tx; // reg tx = 1'b1; reg fetch = 1'b0; // 4 reg[1:0] div4 = 2'd0; // : reg[3:0] s = 4'd10; // assign idle = (s == 4'd10); // reg[7:0] d; // reg sendstart; // reg canfetch; // , clk reg gotdata = 1'b0; // clock domains reg[2:0] sync = 3'b000; // rdclk write reg wr = 1'b0; // getdata==1 // nextdata // gotdata==1. , // . // gotdata getdata. always @(posedge rdclk, negedge nreset) begin if(!nreset) begin wr <= 1'b0; sync <= 3'b000; // fetch <= 1'b0; end else begin // write wr <= write; // sync <= { gotdata, sync[2:1] }; // gotdata // . // . fetch <= (sync[1] && !sync[0]) ? 1'b1 : 1'b0; end end // (?) always // sendstart canfetch always @(*) begin // sendstart = 1'b0; if(nreset) begin // canfetch = wr; if(div4 == 2'd0) begin case(s) 4'd0: begin // sendstart = 1'b1; // , canfetch = 1'b0; end 4'd9: begin // end 4'd10: begin // idle, end default: begin // // , canfetch = 1'b0; end endcase end else begin if(s < 4'd9) begin // 9 ! canfetch = 1'b0; end end if(canfetch && idle) begin // idle - sendstart = 1'b1; end end else begin // reset canfetch = 1'b0; end end always @(posedge clk, negedge nreset) begin if(!nreset) begin // div4 <= 2'd0; s <= 4'd10; gotdata <= 1'b0; end else begin if(div4 == 2'd0) begin case(s) 4'd0: begin // sendstart end 4'd9: begin // tx <= 1'b1; end 4'd10: begin // idle, end default: begin // , tx <= d[0]; // d <= { 1'b0, d[7:1] }; end endcase end else begin // div4 <= div4 - 2'd1; end if(canfetch) begin // , d <= txdata; // gotdata <= 1'b1; if(!idle /*s == 4'd10*/) begin // s <= 4'd0; end end else begin // , gotdata <= 1'b0; end if(sendstart) begin // tx <= 1'b0; // s <= 4'd1; // div4 <= 2'd3; end else begin if((div4 == 2'd0) && (s < 4'd10)) begin // s <= s + 4'd1; // div4 <= 2'd3; end end end end endmodule
Pour tester le récepteur et l'émetteur, le module principal a été écrit sur le genou. Je vous demande de ne pas le jurer, je connais les erreurs de conception (nreset de signal asynchrone externe, absence de réinitialisation FIFO, etc.). Mais aux fins de vérification des fonctionnalités, elles ne sont pas significatives.
Ma carte de démonstration est cadencée à partir d'une source de signal de 50 MHz. Par conséquent, dans le module principal, j'ai utilisé PLL, à la sortie C0 dont j'ai formé une fréquence pour travailler avec UART (1,8432Mhz, en fait 1,843198Mhz) et, pour le plaisir, j'ai formé une fréquence de 300Mhz (sortie c1 PLL) pour cadencer la simulation du circuit de traitement de l'information.
Module principal (Verilog) // // .. UART UART, // FPGA, // FIFO IP CORE DCFIFO. // //NB! // SDC- ! // ( if , // ). module uart( input wire clk50mhz, // 50Mhz input wire nreset, // input wire rx, // UART output wire tx, // UART output wire overflow ); // 1.8432Mhz ( 1.843198Mhz) wire clk_1843200; // 1.2288Mhz ( 1.228799Mhz) //wire clk_1228800; // 300Mhz, PLL wire clk300mhz; // UART uart_pll pll50mhz(.inclk0(clk50mhz), .c0(clk_1843200) /*, .c1(clk_1228800)*/, .c1(clk300mhz)); // UART 38400 // (1843200/38400)/4 = 12 ('b1100). // UART 57600 // (1843200/57600)/4 = 8 // UART 115200 // (1843200/115200)/4 = 4 // UART 230400 // (1843200/230400)/4 = 2 // UART 460800 // (1843200/460800)/4 = 1 (.. !) // UART wire uart_baud4; // // .data 1 . // uart_baud4 .clock/ // uart_baud4 .clock uart_osc uart_osc_1(.clock(clk_1843200), .data(5'd2/*5'd4*//*5'd12*/-5'd1), .sload(uart_baud4), .cout(uart_baud4)); //wire uart_baud4 = clk_1843200; // wire rxf; // mfilter mfilter_rx(.clk(clk50mhz /*clk_1843200*/), .in(rx), .out(rxf)); //wire rxf = rx; // wire[7:0] rxdata; wire rxready; wire error; uart_rx uart_rx_1(.nreset(nreset), .clk(uart_baud4), .rx(rxf), .rdclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/), .rxdata(rxdata), .ready(rxready), .error(error)); wire[7:0] txdata; // , , wire txnone; // , wire fetch; wire full; // // uart_baud4 // clk50mhz uart_fifo_rx uart_fifo_rx_1(.data(rxdata), .rdclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/ /*uart_baud4*/), .rdreq(fetch), .wrclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/ /*uart_baud4*/), .wrreq(rxready), .rdempty(txnone), .q(txdata), .wrfull(full)); assign overflow = ~error; uart_tx uart_tx_1(.nreset(nreset), .clk(uart_baud4), .rdclk(clk300mhz /*clk50mhz*/ /*clk_1843200*/), .txdata(txdata), .write(~txnone), .fetch(fetch), .tx(tx)); endmodule
Pour les tests, le générateur de trafic testcom de Zelax a été utilisé. Malheureusement, mon adaptateur USB / UART a refusé de fonctionner avec des vitesses supérieures à 230400BPS, donc tous les tests ont été effectués à cette vitesse.
Résultat du test avec filtrage du signal d'entrée RX à l'aide de l'élément majoritaire
Signal Tap État du signal

Et ici, l'élément majoritaire de l'entrée a été supprimé.Mais quoi, comment pourrais-je autrement simuler des erreurs arbitraires lors de la vérification du schéma de synchronisation rapide?

Signal Tap État du signal

Remarque
Désolé, je n'ai pas suivi de cours Quartus et il n'y avait personne pour poser des questions. Ce que je suis tombé sur moi-même et ce que j'avertis des autres FPGA en démarrage: assurez-vous de créer un fichier SDC dans le projet et de décrire les fréquences d'horloge. Oui, le projet est en cours de construction sans lui, bien que des avertissements puissent apparaître si le synthétiseur n'a pas pu déterminer les caractéristiques de synchronisation de l'horloge. Au début, je les ai ignorés jusqu'à ce que je tue une demi-journée pour déterminer le problème de pourquoi dans mon module récepteur lors de l'exécution du code
if(rx == 1'b0) begin busy <= 1'b1; d <= 10'b1111111111; cnt <= 2'd0; idle <= 1'b0; end else begin
les signaux occupé et inactif étaient correctement définis, mais le contenu du registre d parfois ne changeait pas.
Annexe: fichier SDC pour le projet set_time_format -unit ns -decimal_places 3 # 50Mhz, (50/50 duty cycle) create_clock -name {clk50mhz} -period 20.000 -waveform { 0.000 10.000 } ############################################################################## Now that we have created the custom clocks which will be base clocks,# derive_pll_clock is used to calculate all remaining clocks for PLLs derive_pll_clocks -create_base_clocks derive_clock_uncertainty # PLL ? # altpll_component.clk0_divide_by = 15625, # altpll_component.clk0_duty_cycle = 50, # altpll_component.clk0_multiply_by = 576, # altpll_component.clk0_phase_shift = "0", #create_generated_clock -name clk_1843200 -source [get_ports {clk50mhz}] -divide_by 15625 -multiply_by 576 -duty_cycle 50 -phase 0 -offset 0 # baudrate=38400 # 1/4 , .. duty=(1/4)*100=25% #create_generated_clock -name uart_baud4 -source [get_nets {pll50mhz|altpll_component|auto_generated|wire_pll1_clk[0]}] -divide_by 12 -duty_cycle 25 [get_nets {uart_osc_1|LPM_COUNTER_component|auto_generated|counter_reg_bit[0]}] # baudrate=230400 # 1/4 , .. duty=(1/4)*100=50% create_generated_clock -name uart_baud4 -source [get_nets {pll50mhz|altpll_component|auto_generated|wire_pll1_clk[0]}] -divide_by 2 -duty_cycle 25 [get_nets {uart_osc_1|LPM_COUNTER_component|auto_generated|counter_reg_bit[0]}] # baudrate=460800 # 1, PLL, .
Un grand merci à tous ceux qui ont écrit des commentaires sur l'article! Parmi celles-ci, j'ai rassemblé beaucoup d'informations utiles, bien que parfois quelque peu contradictoires. À mon avis, leur valeur est bien supérieure à la mise en œuvre de l'algorithme décrit ci-dessus. Et, sans aucun doute, ils seront utiles à ceux qui osent également grimper dans le monde des FPGA.Liste des liens externes
- Émetteur-récepteur asynchrone universel (Wikipedia)
- Élément majoritaire (Wikipedia)