Finalmente, minhas mãos chegaram ao estudo dos FPGAs. Mas, de alguma maneira, isso acaba errado: escrevo drivers para hardware para Linux, programa microcontroladores, leio os circuitos (e desenvolvo um pouco), preciso crescer ainda mais.
Como não me pareceu interessante piscar os LEDs, decidi fazer uma coisa simples. Ou seja, escreva os módulos receptor e transmissor para UART, combine-os dentro do FPGA (ao mesmo tempo, entenda como usar o IP Core) e teste tudo em hardware real.
Eu digo imediatamente que não era uma tarefa criar um núcleo parametrizado universal da tarefa. Este é apenas um projeto de teste, sobre o tema "sentir o que é o FPGA e como se comunicar com ele".
Então, vamos começar com o receptor. O algoritmo é bastante
bem descrito , então repetirei aqui apenas seus pontos principais.
- A frequência de amostragem do sinal RX é quatro vezes maior que a taxa de transmissão UART necessária.
- A condição para o início da recepção é considerada a transição do sinal de entrada RX de um nível alto para um baixo se a recepção não estiver em andamento no momento.
- A condição para identificação confiável do bit inicial é considerada a retenção do sinal RX em um estado baixo no segundo ciclo de clock da frequência de amostragem. Ao mesmo tempo, praticamente chegamos ao meio do pulso de bit, o que nos permitirá amostrar ainda mais os pulsos a cada 4 ciclos.
- Em caso de erro nos bits de partida ou parada, defina o erro do sinal de erro. Com base nisso, formamos o sinal fastsync, que usaremos no futuro para uma sincronização rápida do receptor.
- Depois de identificar o bit inicial, iniciamos a recepção sequencial dos bits de dados, começando pelo mais novo. Os dados recebidos são gravados no registro com um deslocamento para a direita em um bit. A condição para o final da recepção será a detecção de um bit inicial na 0ª posição do registrador de mudança.
- A sincronização rápida do receptor consiste em levá-lo ao seu estado original após a detecção de um erro quando o sinal RX atinge um nível alto (pode ser uma transmissão lógica "1", uma transmissão de bit de parada ou um estado ocioso da linha de transmissão).
- A condição para a conclusão bem-sucedida da recepção (valores corretos dos bits de início e parada) é o sinal completo. A partir dele (quando sincronizado pelo sinal rdclk), um sinal de pulso pronto é gerado, o que indica a presença de dados válidos no barramento rxdata.
Observarei imediatamente que não queria cronometrar o sinal de leitura pronto do relógio clk (inesperadamente, certo?), Para não vincular a velocidade do processamento de dados subsequente à taxa de câmbio do UART. Uma implementação semelhante está no módulo transmissor (
veja abaixo ). Um conjunto de testes de módulos receptores e transmissores é realizado com base no IP Core FIFO da Intel e com a capacidade de simular velocidades diferentes para o consumidor e o gerador de dados. A única limitação é que a frequência do relógio do produtor e consumidor dos dados não deve ser menor que a freqüência do relógio clk.
Módulo receptor (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
Como o sinal de entrada RX é assíncrono e (possivelmente) instável, um
elemento majoritário foi conectado na frente do módulo receptor no
módulo principal . O elemento também está escrito em Verilog, mas seu código não faz sentido aqui. Em vez disso, uma bela imagem de um elemento sintetizado.
O esquema sintetizado do elemento majoritário A unidade transmissora é ainda mais simples e, espero, não precisa de comentários adicionais.
Módulo transmissor (atribuições de bloqueio e não bloqueio da Verilog sempre dentro) // // 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
A implementação do transmissor acima causou uma discussão acalorada nos comentários. Embora, como resultado, todos parecessem concordar que era possível fazer isso, mas com cuidado. Para sua própria tranqüilidade, o módulo foi reescrito levando em consideração todas as diretrizes mencionadas. Na minha opinião, não é muito mais complicado do que o anterior do ponto de vista da percepção humana do algoritmo implementado.
Módulo transmissor (Verilog, ideologicamente correto) // // 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
Para testar o receptor e o transmissor, o módulo principal foi escrito no joelho. Peço que não jure, conheço os erros de design (sinal externo assíncrono nreset, falta de redefinição FIFO, etc.). Mas, com o objetivo de verificar a funcionalidade, elas não são significativas.
Minha placa de demonstração tem clock de uma fonte de sinal de 50Mhz. Portanto, no módulo principal, usei PLL, na saída C0, da qual formava uma frequência para trabalhar com UART (1,8432Mhz, na verdade 1,843198Mhz) e, por diversão, formei uma frequência de 300Mhz (saída c1 PLL) para registrar a simulação do circuito de processamento de informações.
Módulo 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
Para o teste, foi utilizado o gerador de tráfego testcom da Zelax. Infelizmente, meu adaptador USB / UART se recusou a trabalhar com velocidades acima de 230400BPS; portanto, todos os testes foram realizados nessa velocidade.
Resultado do teste com a filtragem do sinal de entrada RX usando o elemento majoritário
Toque em Status do sinal

E aqui o elemento majoritário da entrada foi removido.Mas o que, de que outra forma eu poderia simular erros arbitrários ao verificar o esquema de sincronização rápida?

Toque em Status do sinal

Nota
Desculpe, não participei dos cursos do Quartus e não havia ninguém para fazer perguntas. Sobre o que me deparei e sobre o que aviso outros FPGAs de inicialização: Crie um arquivo SDC no projeto e descreva as frequências de clock nele. Sim, o projeto está sendo construído sem ele, embora possam aparecer avisos se o sintetizador não puder determinar as características de tempo do relógio. No começo, eu os ignorei até matar meio dia para determinar o problema do porquê no meu módulo receptor ao executar o código
if(rx == 1'b0) begin busy <= 1'b1; d <= 10'b1111111111; cnt <= 2'd0; idle <= 1'b0; end else begin
os sinais de ocupado e ocioso foram definidos corretamente, mas o conteúdo do registro d às vezes não era alterado.
Apêndice: arquivo SDC para o projeto 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, .
Muito obrigado a todos que escreveram comentários sobre o artigo! Dessas, reuni muitas informações úteis, embora às vezes um tanto conflitantes. Na minha opinião, seu valor é muito maior que a implementação do algoritmo descrito acima. E, sem dúvida, serão úteis para aqueles que também ousam entrar no mundo dos FPGAs.Lista de links externos
- Transceptor assíncrono universal (Wikipedia)
- Elemento majoritário (Wikipedia)