最终,我动手研究了FPGA。 但是以某种方式证明是错误的:我为Linux的硬件编写驱动程序,为微控制器编程,阅读电路(并做了一些设计),我需要进一步发展。
由于在我看来使LED闪烁并不有趣,所以我决定做一个简单的事情。 即,编写用于UART的接收器和发送器模块,将它们组合到FPGA内(同时了解如何使用IP内核),并在真实硬件上对其进行全部测试。
我马上说,成为任务的通用参数化核心不是一项任务。 这只是一个测试项目,主题是“感受一下FPGA是什么以及如何与之通信”。
因此,让我们从接收器开始。 该算法已被
很好地描述 ,因此在这里我仅重复其要点。
- RX信号采样频率是所需UART传输速率的四倍。
- 如果当前未进行接收,则开始接收的条件被认为是输入信号RX从高电平到低电平的转变。
- 可靠地标识起始位的条件被认为是在采样频率的第二个时钟周期将RX信号保持在低电平状态。 同时,我们实际上到达了位脉冲的中间,这将使我们能够每4个周期进一步采样脉冲。
- 如果起始位或停止位有错误,请设置错误信号错误。 基于此,我们形成快速同步信号,将来将用于快速接收器同步。
- 确定起始位后,我们从最小的位开始依次接收数据位。 接收到的数据向右移一位,写入寄存器。 接收结束的条件是在移位寄存器的第0位检测起始位。
- 接收器的快速同步在于当RX信号变为高电平时检测到错误后将其恢复为原始状态(这可以是逻辑“ 1”传输,停止位传输或传输线的空闲状态)。
- 成功完成接收的条件(起始和停止位的正确值)是完成信号。 从中(当由rdclk信号计时时)会生成就绪脉冲信号,该信号指示rxdata总线上存在有效数据。
我马上要指出,我不想从clk时钟准备好读取信号(出乎意料的,对吗?),以免将后续数据处理的速度与UART交换速率联系在一起。 发送器模块中也有类似的实现(
请参见下文 )。 测试接收器和发射器模块是基于Intel的IP Core FIFO进行的,并能够模拟用户和数据生成器的不同速度。 唯一的限制是数据产生者和使用者的时钟频率不得低于时钟频率clk。
接收器模块(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
由于RX输入信号是异步的(可能是不稳定的),因此
大多数元件都连接在
主模块中接收器模块的前面。 该元素也是用Verilog编写的,但此处的代码没有意义。 相反,它是合成元素的美丽图画。
发送器单元甚至更简单,我希望不需要其他注释。
变送器模块(始终在内部进行Verilog阻止和非阻止分配) // // 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
上面的发射机实现引起评论中的激烈讨论。 结果,尽管每个人似乎都同意这样做是可行的,但要谨慎。 为了您的安心,本模块已考虑所有上述指南进行了重写。 在我看来,从人类对所实现算法的看法来看,它并不比前一个复杂。
发射器模块(Verilog,思想上正确) // // 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
为了测试接收器和发射器,主模块写在膝盖上。 我要求您不要发誓,我知道设计错误(外部异步信号nreset,缺少FIFO复位等)。 但是出于验证功能的目的,它们并不重要。
我的演示板由50Mhz信号源提供时钟。 因此,在主模块中,我使用了PLL,在其输出C0处形成了一个与UART一起工作的频率(1.8432Mhz,实际上是1.843198Mhz),并且为了娱乐起见,我形成了300Mhz的频率(输出c1 PLL)来为信息处理电路的仿真计时。
主模块(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
为了进行测试,使用了Zelax的testcom流量生成器。 不幸的是,我的USB / UART适配器拒绝以230400BPS以上的速度工作,因此所有测试均以此速度进行。
使用多数元素过滤输入信号RX的测试结果
信号分接头信号状态

此处来自入口的多数元素已被删除。但是,当检查快速同步方案时,还能怎样模拟任意错误呢?

信号分接头信号状态

注意事项
抱歉,我没有上Quartus课程,也没有人问问题。 我偶然发现的内容以及对其他启动FPGA的警告:请确保在项目中创建一个SDC文件并描述其中的时钟频率。 是的,没有该项目的项目正在构建,但是如果合成器无法确定时钟的时序特性,可能会出现警告。 起初我一直不理会它们,直到杀死半天才确定执行代码时为什么在接收器模块中出现问题
if(rx == 1'b0) begin busy <= 1'b1; d <= 10'b1111111111; cnt <= 2'd0; idle <= 1'b0; end else begin
忙/闲信号已正确设置,但寄存器d的内容有时未更改。
附录:项目的SDC文件 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, .
非常感谢所有对此文章发表评论的人! 其中,我收集了很多有用的信息,尽管有时会有些冲突。 在我看来,它们的价值远远大于上述算法的实现。 而且,毫无疑问,它们对于那些也敢于进入FPGA世界的人很有用。外部链接列表
- 通用异步收发器(维基百科)
- 多数元素(维基百科)