串口应用:遵循uart协议发送N位数据(状态优化为3个,适用任意长度的输入数据,取寄存器中的一段(用变量作为边界))
上一节中成功实现了发送多个字节的数据。把需要发送的数据分成多段遵循uart协议的数据依次发送。上一节是使用状态机实现的,每发一次设定为一个状态,所以需要发送的数据越多,状态的个数越多,代码越长,因而冗长且适应范围不广 。
在这里,我通过优化代码,实现了把发送状态固定为3个,并且能适用任意长度的输入数据的功能。只需要修改一个参数即可实现。
学习:
1.error:cannot index into non-array type wire for 'dataN'
出现这个错误是因为dataN没有定义长度,添加定义【】即可。
2.想要修改/取出一个大的寄存器中的某几位,如要取dataN[10:18],我想用变量limits,limitx来作为上下限,这样子能实现取出不同段的值。按照这种想法我写成:data <= dataN [limitx:limits],会报错 : error:range must be bounded by constant expressions要求用常数来限制边界。
解决方法:写成 data <= dataN[(limitx)+:8] 意思是从limitx开始,向上取8位,limit可以是变量。
优化:我把下限写成寄存器变量limitx,在data赋值语句的begin-end内,即:
begin
limitx <= x *8 ;
data <= dataN[(limitx)+:8] ;
x <= x +1 ;
end
综合出来会有个limitx寄存器,data寄存器,一个clk上升沿到来,他们同时变化,limit会取上一个x的输出x0,data会取上一个limit的输出limit0,这样子data就延后了一个limit的输出;而我的目的是limitx变化后,再让data取变化后的limit1计算,这个代码无法实现这个目的。
解决:直接去掉limitx,写成
begin
data <= dataN[(x *8)+:8] ;
x <= x +1 ;
end
这样子就少了一个寄存器,节省资源,而且可以实现data取到前一个x的限定范围,不会出错。
观察波形可以发现这个问题,所以后面写代码时,能用计算式代替的尽量不要多设置一个寄存器。特别是运算都在同一个begin-end里面的情况。
3.底层的输出在顶层不可以被赋值,要被定义成wire。输入可以定义成reg,赋值。
4.always中的复位信号reset中应该复位的变量应包含所有本次always用到的变量,不复位的话仿真时会出现未知状态X。
5.再次确认,非阻塞赋值无论在begin-end中的多个语句顺序如何,综合出来的RTL级电路图是不变的。



6.用parameter定义一个常数,那么在后面他是不能被重写的。不过例化时可以重新定义一次。
7.从串口应用的学习中,要懂得,开始位,结束位的意义。这两个位可以作为指示与标志,为发送系统增添功能,改变适应范围。也要注意使能端的意义,可以让系统分为工作和空闲两种状态。也要懂得状态机的使用,由多状态固定到指定状态的优化。最最重要的是,通过观察仿真波形来修改某个信号的判断条件,从而达到优化/修正的目的。
`timescale 1ns / 1ns module uart_6_tb( ); reg clk ; reg reset ; reg [79:0]dataN ; reg send_pulse ; wire uart_tx ; wire sign ; uart_6_optimization uart_6_sim(//隐式例化 clk, reset, send_pulse, dataN, sign, uart_tx ); initial clk = 1; always #10 clk = ! clk ; initial begin reset = 0 ; dataN = 40'd0 ; send_pulse = 1'b0 ; #201 ; reset = 1 ; dataN = 80'h123456789 ; #200 ; send_pulse = 1'b1 ; #20; send_pulse = 1'b0 ; @(negedge sign) ; dataN = 80'ha98765432 ; #200 ; send_pulse = 1'b1 ; #20;//设置为20ns时识别不出,会出错。设置21可以解决//多给200ns延迟后错误消失,用20ns也可以 send_pulse = 1'b0 ; @(negedge sign) ; #200 ; $stop ; end endmodule
module uart_6_optimization(//实现优化的目标,输入N位的数据,利用固定的三状态实现。 clk ,//想要改变输入的位数,只需要修改dataN的位数定义和parameter N 即可 reset , send_pulse , dataN , sign , uart_tx ); input clk ; input reset ; input send_pulse ; input [79:0]dataN ; output sign ; output uart_tx ; reg [7:0]data ; reg send_en ; wire baud_rate ; wire tx_done ; reg sign ; reg tx_done_ctrl ; uart_1_2 uart_6_opti( //设计输入 clk,//时钟 reset,//复位 data,//数据 send_en,//使能 tx_done_ctrl, baud_rate,//波特率 uart_tx,//串口输出 tx_done//结束信号 ); assign baud_rate = 3'd5 ; reg [1:0]state ; reg [3:0]x ; parameter N = 37 ;//输入的位数 always@(posedge clk or negedge reset) if (!reset ) begin data <= 8'd0 ; send_en <= 1'b0 ; state <= 2'd0 ; sign <= 1'b0 ; tx_done_ctrl <= 1'b0 ; x <= 4'd0 ; end else if( send_pulse == 1'd1 ) begin state <= 2'd1 ; x <= 4'd0 ; tx_done_ctrl <= 1'b1 ; sign <= 1'b1 ; end else if(tx_done_ctrl == 1'b1 ) tx_done_ctrl <= 1'b0 ; else if (( state == 2'd1 ) && ( (x) * 8 < N ) && (tx_done == 1'b1 )) begin send_en <= 1'b1 ; x <= x + 1'b1 ;//说明在begin end里面,非阻塞赋值综合出来的还是对变量的综合设计寄存器是有先后顺序的,综合后对一个时钟沿到来,data先变,x再变。emm,也不是说谁先变,是时钟对寄存器同时作用,然后寄存器同时变化,只不过data根据x上一次的输出而变化。 data <= dataN[( x * 8 )+: 8 ] ;//确实是先取了data,然后x再自加1.只不过一开始我多设置了一个寄存器limit,使得data要延后一个 周期才赋值。逻辑出错。波形不对 end else if (( (x) * 8 >= N )&& (tx_done == 1'b1 )) begin sign <= 1'b0 ; state <= 1'b0 ; data <= 8'd0 ; end endmodule
module uart_1_2( //设计输入 clk,//时钟 reset,//复位 data,//数据 send_en,//使能 tx_done_ctrl, baud_rate,//波特率 uart_tx,//串口输出 tx_done//结束信号 ); input clk; input reset; input [7:0]data; input send_en; input [2:0]baud_rate; input tx_done_ctrl ; output reg uart_tx; output reg tx_done; reg [17:0]bit_tim; //设计逻辑 //把波特率转化为一位的持续时间 //单位时间内通过信道传输的码元数称为码元传输速率,即波特率,码元/s,一个码元可能由多个位组成。而比特率即 ‘位/s’ always@(baud_rate) //在这里一个 码元由一位组成,所以波特率=比特率 begin case(baud_rate) //常见的串口传输波特率 3'd0 : bit_tim = 1000000000/300/20 ; //波特率为300 3'd1 : bit_tim = 1000000000/1200/20 ; //波特率为1200 3'd2 : bit_tim = 1000000000/2400/20 ; //波特率为2400 3'd3 : bit_tim = 1000000000/9600/20 ; //波特率为9600 3'd4 : bit_tim = 1000000000/19200/20 ; //波特率为19200 3'd5 : bit_tim = 1000000000/115200/20 ; //波特率为115200 default bit_tim = 1000000000/9600/20 ; //多余的寄存器位置放什么:默认速率 endcase end reg [17:0]counter1 ;//用来计数每一位的持续时间 always@(posedge clk or negedge reset) begin if(!reset)//复位清零 counter1 <=17'b0 ; else if (send_en )//使能端有效,计数 begin if( counter1 == bit_tim - 1'b1 )//位持续时间到达时归零 counter1 <= 17'b0 ; else counter1 <= counter1 + 1'b1 ;//位持续时间没达到时继续进行 end else counter1 <= 17'b0 ; //使能端无效时,清零 end reg [3:0]counter2 ; //输出第几位。如果忘了考虑归零,那么计数器会出现溢出归零,在这里是加到15然后归零 always@(posedge clk or negedge reset) begin if(!reset)//复位 counter2 <= 4'b0 ; else if ( send_en )//使能端有效 begin if(counter2 == 0)//消耗20ns,进入起始位。这个挺重要的,没有这个的话得消耗一位的时间进入起始位 counter2 <= counter2 +1'b1 ; else if( counter1 == bit_tim - 1'b1 )//开始进行位移 counter2 <= counter2 + 4'b1 ; else if(counter2 == 4'd11) counter2 <= 4'd0 ; else counter2 <= counter2 ; end else//使能端无效,归零,进入空闲位 counter2 <= 4'b0 ; end always@(posedge clk or negedge reset) begin if(!reset)//复位 begin uart_tx <= 4'b1 ; end else if ( send_en )//使能端有效,输出每一位 case(counter2) 0:begin uart_tx <= 1'b1 ; end//设定第一位为空闲位。没有空闲位的话,使能端无效时,counter停留在0,不能保持输出高电平(取决于要输出的数据),不符合要求。 1:uart_tx <= 1'b0 ;//起始位 2:uart_tx <= data[0] ; 3:uart_tx <= data[1] ; 4:uart_tx <= data[2] ; 5:uart_tx <= data[3] ; 6:uart_tx <= data[4] ; 7:uart_tx <= data[5] ; 8:uart_tx <= data[6] ; 9:uart_tx <= data[7] ; 10:uart_tx <= 1'b1 ;//结束位 11:begin uart_tx <= 1'b1 ; end//为了让结束位跑满,设置11,作为第11个点,定第十位长度。 default uart_tx <= 1'b1 ; endcase else uart_tx <= 1'b1 ; end always@(posedge clk or negedge reset) begin if(!reset)//复位清零 tx_done <= 1'b0 ; else if (send_en )//使能端有效 begin if( counter2 == 0 )// tx_done <= 1'b0 ; else if (( counter2 == 10 ) && ( counter1 == bit_tim - 1'b1 )) tx_done <= 1'b1 ; else if (tx_done == 1'b1) tx_done <= 1'b0 ; else if (tx_done_ctrl == 1'b1) tx_done <= 1'b1 ; end else if (tx_done_ctrl == 1'b1) tx_done <= 1'b1 ; else if (tx_done == 1'b1) tx_done <= 1'b0 ; end endmodule

浙公网安备 33010602011771号