Schließlich erreichten meine Hände das Studium der FPGAs. Und dann stellt sich irgendwie heraus, dass ich Treiber für Hardware für Linux schreibe, Mikrocontroller programmiere, die Schaltkreise lese (und ein wenig entwerfe), ich muss weiter wachsen.
Da es mir nicht interessant erschien, die LEDs zu blinken, entschied ich mich für eine einfache Sache. Schreiben Sie nämlich die Empfänger- und Sendermodule für UART, kombinieren Sie sie im FPGA (verstehen Sie gleichzeitig, wie IP Core verwendet wird) und testen Sie alles auf realer Hardware.
Ich sage sofort, dass es keine Aufgabe war, einen universellen parametrisierten Kern der Aufgabe zu erstellen. Dies ist nur ein Testprojekt zum Thema "Fühlen, was FPGA ist und wie man damit kommuniziert".
Beginnen wir also mit dem Empfänger. Der Algorithmus ist ziemlich
gut beschrieben , daher werde ich hier nur seine Hauptpunkte wiederholen.
- Die Abtastfrequenz des Empfangssignals ist viermal höher als die erforderliche UART-Übertragungsrate.
- Die Bedingung für den Beginn des Empfangs wird als Übergang des Eingangssignals RX von einem hohen zu einem niedrigen Pegel angesehen, wenn der Empfang derzeit nicht läuft.
- Die Bedingung für eine zuverlässige Identifizierung des Startbits wird als Beibehaltung des Empfangssignals in einem niedrigen Zustand beim zweiten Takt der Abtastfrequenz angesehen. Gleichzeitig fallen wir praktisch in die Mitte des Bitimpulses, wodurch wir die Impulse alle 4 Zyklen weiter abtasten können.
- Stellen Sie im Falle eines Fehlers in den Start- oder Stoppbits den Fehlersignalfehler ein. Darauf aufbauend bilden wir das Fastsync-Signal, das wir zukünftig für eine schnelle Empfängersynchronisation verwenden werden.
- Nachdem wir das Startbit identifiziert haben, beginnen wir mit dem sequentiellen Empfang von Datenbits, beginnend mit dem jüngsten. Die empfangenen Daten werden mit einer Verschiebung nach rechts um ein Bit in das Register geschrieben. Die Bedingung für das Ende des Empfangs ist die Erkennung eines Startbits an der 0. Position des Schieberegisters.
- Die schnelle Synchronisation des Empfängers besteht darin, ihn nach dem Erkennen eines Fehlers in seinen ursprünglichen Zustand zu versetzen, wenn das Empfangssignal auf einen hohen Pegel geht (dies kann entweder eine logische "1" -Übertragung, eine Stoppbitübertragung oder ein Leerlaufzustand der Übertragungsleitung sein).
- Die Bedingung für den erfolgreichen Abschluss des Empfangs (korrekte Werte der Start- und Stoppbits) ist das vollständige Signal. Daraus wird (wenn es durch das rdclk-Signal getaktet wird) ein Bereitschaftsimpulssignal erzeugt, das das Vorhandensein gültiger Daten auf dem rxdata-Bus anzeigt.
Ich werde sofort feststellen, dass ich das Lesesignal nicht von der Clk-Uhr abtakten wollte (unerwartet, oder?), Um die Geschwindigkeit der nachfolgenden Datenverarbeitung nicht an den UART-Wechselkurs zu binden. Eine ähnliche Implementierung findet sich im Sendermodul (
siehe unten ). Auf der Grundlage des IP Core FIFO von Intel und mit der Fähigkeit, unterschiedliche Geschwindigkeiten für den Verbraucher und den Datengenerator zu simulieren, wird eine Testreihe von Empfänger- und Sendemodulen erstellt. Die einzige Einschränkung besteht darin, dass die Taktfrequenz des Produzenten und Verbrauchers der Daten nicht niedriger als die Taktfrequenz clk sein darf.
Empfängermodul (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
Da das Eingangssignal RX asynchron und (möglicherweise) instabil ist, wurde ein
Hauptelement vor dem Empfängermodul im
Hauptmodul angeschlossen . Das Element ist ebenfalls in Verilog geschrieben, aber sein Code macht hier keinen Sinn. Stattdessen ein schönes Bild eines synthetisierten Elements.
Das synthetisierte Schema des Mehrheitselements Die Sendeeinheit ist noch einfacher und benötigt hoffentlich keine zusätzlichen Kommentare.
Sendermodul (Verilog blockierende und nicht blockierende Zuordnungen innen immer) // // 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
Die obige Senderimplementierung verursachte eine hitzige Diskussion in den Kommentaren. Infolgedessen schienen sich alle einig zu sein, dass dies möglich war, aber vorsichtig. Zu Ihrer eigenen Sicherheit wurde das Modul unter Berücksichtigung aller genannten Richtlinien neu geschrieben. Meiner Meinung nach ist es aus Sicht der menschlichen Wahrnehmung des implementierten Algorithmus nicht viel komplizierter als das vorherige.
Sendermodul (Verilog, ideologisch korrekt) // // 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
Um den Empfänger und den Sender zu testen, wurde das Hauptmodul auf das Knie geschrieben. Ich bitte Sie, nicht darauf zu schwören, ich kenne die Entwurfsfehler (externes asynchrones Signal-Reset, fehlendes FIFO-Reset usw.). Für die Überprüfung der Funktionalität sind sie jedoch nicht von Bedeutung.
Meine Demo-Karte wird von einer 50-MHz-Signalquelle getaktet. Daher habe ich im Hauptmodul PLL verwendet, an dessen Ausgang C0 ich eine Frequenz für die Arbeit mit UART (1,8432 MHz, tatsächlich 1,843198 MHz) und zum Spaß eine Frequenz von 300 MHz (Ausgang c1 PLL) gebildet habe, um die Simulation der Informationsverarbeitungsschaltung zu takten.
Hauptmodul (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
Zum Testen wurde der Testcom-Verkehrsgenerator von Zelax verwendet. Leider weigerte sich mein USB / UART-Adapter, mit Geschwindigkeiten über 230400 BPS zu arbeiten, sodass alle Tests mit dieser Geschwindigkeit durchgeführt wurden.
Testergebnis mit Filterung des Eingangssignals RX mit dem Majority-Element
Signal Tippen Sie auf Signalstatus

Und hier wurde das Mehrheitselement vom Eingang entfernt.Aber was, wie könnte ich sonst beliebige Fehler simulieren, wenn ich das schnelle Synchronisationsschema überprüfe?

Signal Tippen Sie auf Signalstatus

Hinweis
Entschuldigung, ich habe keine Quartus-Kurse besucht und es gab niemanden, der Fragen stellte. Was ich über mich selbst gestolpert bin und worüber ich andere Start-FPGAs warne: Stellen Sie sicher, dass Sie im Projekt eine SDC-Datei erstellen und die darin enthaltenen Taktfrequenzen beschreiben. Ja, das Projekt wird ohne es erstellt, obwohl möglicherweise Warnungen angezeigt werden, wenn der Synthesizer die Timing-Eigenschaften der Uhr nicht bestimmen konnte. Zuerst habe ich sie ignoriert, bis ich einen halben Tag getötet habe, um das Problem zu ermitteln, warum in meinem Empfängermodul der Code ausgeführt wurde
if(rx == 1'b0) begin busy <= 1'b1; d <= 10'b1111111111; cnt <= 2'd0; idle <= 1'b0; end else begin
Besetzt- und Leerlaufsignale wurden korrekt eingestellt, aber der Inhalt des Registers d änderte sich manchmal nicht.
Anhang: DEZA-Datei für das Projekt 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, .
Vielen Dank an alle, die Kommentare zu dem Artikel geschrieben haben! Von diesen habe ich viele nützliche, wenn auch manchmal etwas widersprüchliche Informationen gesammelt. Meiner Meinung nach ist ihr Wert viel größer als die Implementierung des oben beschriebenen Algorithmus. Und zweifellos werden sie für diejenigen nützlich sein, die es auch wagen, in die Welt der FPGAs einzusteigen.Liste der externen Links
- Universeller asynchroner Transceiver (Wikipedia)
- Mehrheitselement (Wikipedia)