一、实验要求
要求使用串口工具发送出去的数据存储到单端RAM中再读取数据,完成回环测试。
二、信号流向分析
但连线图其实更复杂一些
三、程序设计
rx模块:
`timescale 1ns / 1ps
/串口接收端 串行转并行
module uart_rx(
input sys_clk ,
input rst_n ,
input rx_data , //输入串行数据
output reg[7:0] uart_data , // 输出并行数据
output reg rx_done //数据传输完成结束信号
);
parameter SYSCLK = 50_000_000 ;
parameter Baud = 115200 ;
parameter COUNT = SYSCLK/Baud ;//434 传输1比特所需要的时钟周期
parameter MID = COUNT/2 ;
parameter MODE_CHECK = 0 ;
///产生开始信号(检测下降沿-----二级寄存)
reg rx_reg1 ;
reg rx_reg2 ;
wire start_flag ; 开始信号
reg rx_flag ;
reg [4:0] cnt_bit ;///0~10
reg [9:0] cnt ;///434
reg [8:0] data_reg ;
reg check ;
always@(posedge sys_clk)
if(!rst_n)begin
rx_reg1 <= 1 ; //处于空闲位 数据线上无数据
rx_reg2 <= 1 ; //处于空闲位 数据线上无数据
end
else
begin
rx_reg1 <= rx_data ;
rx_reg2 <= rx_reg1 ;
end
assign start_flag = ~rx_reg1 & rx_reg2 ;
//rx_flag
always@(posedge sys_clk )
if(!rst_n)
rx_flag <= 0 ;
else if (start_flag)
rx_flag <= 1 ;
else if ( cnt_bit == 10 && cnt == MID -1 )
rx_flag <= 0 ;
else
rx_flag <= rx_flag ;
cnt 434
always@(posedge sys_clk )
if(!rst_n)
cnt <= 0;
else if ( rx_flag == 1 )begin
if ( cnt == COUNT -1) ///一定要减一,如果不减一,实际会计到435次,反算回去波特率就不是115200了
cnt <= 0;
else
cnt <= cnt +1 ;
end
else
cnt <= 0 ;
/计数器
always@(posedge sys_clk )
if(!rst_n)
cnt_bit <= 0 ;
else if ( rx_flag )begin
if ( cnt == COUNT -1)begin
if(cnt_bit == 10)
cnt_bit <= 0 ;
else
cnt_bit <= cnt_bit +1 ;
end
else
cnt_bit <= cnt_bit ;
end
else
cnt_bit <= 0 ;
[7:0]uart_data
/*
cnt_bit = 0 起始位
cnt_bit = 1-8 数据位
cnt_bit = 9 停止位
但是 [7:0]uart_data 没有uart[8]
所以
if ( cnt == MID -1 && cnt_bit > 0 && cnt_bit < 9 )
uart_data[cnt_bit -1] <= rx_data ;
*/
// always@(posedge sys_clk )
// if(!rst_n)
// uart_data <= 0 ;
// else if ( rx_flag )begin
// if ( cnt == MID -1 && cnt_bit > 0 && cnt_bit < 9 )
// uart_data[cnt_bit -1] <= rx_data ; //这里uart_data是不断随着cnt_bit变化的,只有在第九位的时候才有正确的最终值
// else
// uart_data <= uart_data ;
// end
// else
// uart_data <= 0 ;
因为这里要让uart_data只在rx_done的时候有值,所以定义一个中间寄存器data_reg
always@(posedge sys_clk )
if(!rst_n)
data_reg <= 0 ;
else if ( rx_flag )begin
if ( cnt == MID -1 && cnt_bit > 0 && cnt_bit < 10 )
data_reg[cnt_bit -1] <= rx_data ; //这里uart_data是不断随着cnt_bit变化的,只有在第九位的时候才有正确的最终值
else
data_reg <= data_reg ;
end
else
data_reg <= 0 ;
// 其他赋值方法1
// always@(posedge sys_clk )
// if(!rst_n)
// uart_data <= 0;
// else if (cnt == MID -1)begin
// case(cnt_bit)
// 0: uart_data <= 0 ;
// 1: uart_data [0] <= rx_data ;
// 2: uart_data [1] <= rx_data ;
// 3: uart_data [2] <= rx_data ;
// 4: uart_data [3] <= rx_data ;
// 5: uart_data [4] <= rx_data ;
// 6: uart_data [5] <= rx_data ;
// 7: uart_data [6] <= rx_data ;
// 8: uart_data [7] <= rx_data ;
// 9: uart_data <= uart_data ; ///停止位的时候数据传完,保持
// endcase
// end
// else
// uart_data <= uart_data ;
// 其他赋值方法2
// always@(posedge sys_clk )
// if(!rst_n)
// uart_data <= 0 ;
// else if (cnt == MID -1 && cnt_bit > 0 && cnt_bit < 9)
// uart_data <= {rx_data,uart_data[7:1]} ;
// else
// uart_data <= uart_data ;
check
always@(posedge sys_clk)
if(!rst_n)
check <= 0 ;
else if (rx_flag )begin
if ( cnt_bit == 10 )
check <= ^data_reg ;
else
check <= check ;
end
else
check <= 0 ;
给uart_data赋值
always@(posedge sys_clk )
if(!rst_n)
uart_data <= 0 ;
else if (rx_flag)begin
// if (cnt_bit == 10 && check == MODE_CHECK)
if (cnt_bit == 10 && cnt == MID/2 -1 && check == MODE_CHECK)
uart_data <= data_reg[7:0] ;
else
uart_data <= uart_data ;
end
else
uart_data <= uart_data ; 可以保持到下一个数据到来
// uart_data <= 0 ; 只保持在rx_done处于高电平的时候
/rx_done
always@(posedge sys_clk )
if(!rst_n)
rx_done <= 0 ;
else if (rx_flag)begin
if ( cnt_bit == 10 && cnt == MID/2 -1)
rx_done <= 1 ;
else
rx_done <= 0 ;
end
else
rx_done <= 0 ;
endmodule
tx模块:
`timescale 1ns / 1ps
module uart_tx(
input sys_clk ,
input rst_n ,
input [7:0] uart_data ,
input rx_done ,
output reg tx_data ,
output reg tx_done
);
parameter SYSCLK = 50_000_000 ;
parameter Baud = 115200 ;
parameter COUNT = SYSCLK/Baud ;//434 传输1比特所需要的时钟周期
parameter MID = COUNT/2 ;
wire start_flag ;
reg tx_flag ;
reg tx_reg1 ;
reg tx_reg2 ;
reg[4:0] cnt_bit ;
reg[10:0] cnt ;
tx_start
always@(posedge sys_clk)
if(!rst_n)begin
tx_reg1 <= 0 ;
tx_reg2 <= 0 ;
end
else begin
tx_reg1 <= rx_done ;
tx_reg2 <= tx_reg1 ;
end
assign start_flag = tx_reg1 & ~tx_reg2 ;
///tx_flag
always@(posedge sys_clk)
if(!rst_n)
tx_flag <= 0 ;
else if ( start_flag )
tx_flag <= 1 ;
else if ( cnt == COUNT -1 && cnt_bit == 10)
// else if ( cnt == MID -1 && cnt_bit == 10)
tx_flag <= 0 ;
else
tx_flag <= tx_flag ;
///计时器
cnt 434
always@(posedge sys_clk )
if(!rst_n)
cnt <= 0;
else if ( tx_flag == 1 )begin
if ( cnt == COUNT -1) ///一定要减一,如果不减一,实际会计到435次,反算回去波特率就不是115200了
cnt <= 0;
else
cnt <= cnt +1 ;
end
else
cnt <= 0 ;
/计数器
always@(posedge sys_clk )
if(!rst_n)
cnt_bit <= 0 ;
else if ( tx_flag )begin
if ( cnt == COUNT -1)begin
if(cnt_bit == 10)///0123456789 10
cnt_bit <= 0 ;
else
cnt_bit <= cnt_bit +1 ;
end
else
cnt_bit <= cnt_bit ;
end
else
cnt_bit <= 0 ;
parameter MODE_CHECK = 0 ;
赋值
always@(posedge sys_clk )
if(!rst_n)
tx_data <= 1 ; //表示没有数据
else if ( tx_flag )begin
if ( cnt_bit > 0 && cnt_bit < 9 )
///cnt_bit 0 12345678 9
///tx_data 0123456789
///uart_data 01234567
tx_data <= uart_data [cnt_bit-1]; //这里uart_data是不断随着cnt_bit变化的,只有在第九位的时候才有正确的最终值
else if(cnt_bit == 0)
tx_data <= 0 ;
else if(cnt_bit == 9)
tx_data <= (MODE_CHECK == 0)? ^uart_data: ~^uart_data;
/*
MODE_CHECK == 0是偶校验,假如uart_data是1110_0000,其异或的结果
是1,将异或的结果作为校验位,让数据位和校验位异或的结果为0,满足偶校验。
假如uart_data是1110_1000,其异或的结果是0,将异或的结果作为校验位,
让数据位和校验位异或的结果为0,满足偶校验。奇校验则相反。
*/
else if (cnt_bit == 10)///停止位
tx_data <= 1 ;
else
tx_data <= tx_data ;
end
else
tx_data <= 1 ;
always@(posedge sys_clk )
if(!rst_n)
tx_done <= 0 ;
else if (tx_flag)begin
if ( cnt_bit == 10 && cnt == COUNT -1)
// if ( cnt_bit == 10 && cnt == MID/2 -1)
tx_done <= 1 ;
else
tx_done <= 0 ;
end
else
tx_done <= 0 ;
endmodule
顶层RAM模块
`timescale 1ns / 1ps
/*
单端RAM回环测试
写入16个数据,读出16个数据
*/
module single_ram_test(
input sys_clk ,
input rst_n ,
input rx_data ,
output tx_data
);
wire [7:0] uart_data ;
wire [7:0] douta ;
wire rx_done ;
wire tx_done ;
reg tx_start ;
uart_rx uart_rx_1(
. sys_clk ( sys_clk ) ,
. rst_n ( rst_n ) ,
. rx_data ( rx_data ) ,
. uart_data (uart_data ) ,
. rx_done (rx_done )
);
uart_tx uart_tx_1(
. sys_clk ( sys_clk ) ,
. rst_n ( rst_n ) ,
. rx_done ( tx_start ) ,
. uart_data ( douta ) ,
. tx_data ( tx_data ) ,
. tx_done ( tx_done )
);
//ram_ctrl
wire ena ;
assign ena = 1 ;
wire wea ;
reg [6:0] addra ;
wire [7:0] dina ;
//addra
always@(posedge sys_clk)
if(!rst_n)
addra <= 15;
else if(rx_done)begin
if(addra == 4'd15)
addra <= 0;
else
addra <= addra + 1;
end
else
addra <= addra;
//wea
assign wea = 1;
//dina
assign dina = uart_data;
//tx_start
reg [2:0] cnt ;
always@(posedge sys_clk)
if(!rst_n)
cnt <= 0 ;
else if ( addra == 0 )begin
if ( cnt == 7 )
cnt <= cnt ;
else
cnt <= cnt +1 ;
end
else
cnt <= 0 ;
always@(posedge sys_clk)
if(!rst_n)
tx_start <= 0;
else if(addra == 0 && cnt == 6 )
// else if(addra == 0 )
/*addra[0]中存放着dina[0],我们想将发送数据的截至信号tx_done作为下一个
数据发送的条件,但是第一个数据是接收不到它上一个数据的tx_done的,所以只能
人为的将它的tx_start拉高,tx_start在tx模块中取代着rx_done的功能,意味着当
tx_start = 1的时候,tx模块的rx_done=1 ,则tx_flag将处于高电平状态,数据开始
在tx模块进行接收,数据接收完成后产生tx_done信号,作为下一个数据传输开始的条件。
*/
tx_start <= 1; 得到tx_start是一个尖峰脉冲,发送第一个信号
else if(addra == 15 && tx_done)//保证最后一个数据发完不会再发
tx_start <= 0;
else if ( tx_done)
tx_start <= 1 ;
else
tx_start <= 0;
//----------- Begin Cut here for INSTANTIATION Template ---// INST_TAG
single_ram your_instance_name (
.clka(sys_clk), // input wire clka
.wea(wea), // input wire [0 : 0] wea
.addra(addra), // input wire [6 : 0] addra
.dina(dina), // input wire [7 : 0] dina
.douta(douta) // output wire [7 : 0] douta
);
// INST_TAG_END ------ End INSTANTIATION Template ---------
endmodule
四、几个非常需要注意的点
(1)
(2)
always@(posedge sys_clk)
if(!rst_n)
tx_start <= 0;
else if(addra == 0 && cnt == 6 )
// else if(addra == 0 )
/*addra[0]中存放着dina[0],我们想将发送数据的截至信号tx_done作为下一个
数据发送的条件,但是第一个数据是接收不到它上一个数据的tx_done的,所以只能
人为的将它的tx_start拉高,tx_start在tx模块中取代着rx_done的功能,意味着当
tx_start = 1的时候,tx模块的rx_done=1 ,则tx_flag将处于高电平状态,数据开始
在tx模块进行接收,数据接收完成后产生tx_done信号,作为下一个数据传输开始的条件。
*/
tx_start <= 1; 得到tx_start是一个尖峰脉冲,发送第一个信号
else if(addra == 15 && tx_done)//保证最后一个数据发完不会再发
tx_start <= 0;
else if ( tx_done)
tx_start <= 1 ;
else
tx_start <= 0;
(3)
承接(2),如果非要让第一个tx_start不是一个尖峰脉冲信号,可以这样修改:
但是我们还是尽量让tx_start是一个尖峰脉冲,因为此时tx_start不仅仅代替了rx_done的功能,也因为将第一个数据写入地址0是比较规范的写法。
五、仿真程序
`timescale 1ns / 1ps
module test_bench( );
reg sys_clk ;
reg rst_n ;
reg [8:0] rx_data ;
wire tx_data ;
parameter SYSCLK = 50_000_000 ;
parameter Baud = 115200 ;
parameter COUNT = SYSCLK/Baud ;//434 传输1比特所需要的时钟周期
parameter MID = COUNT/2 ;
initial
begin
sys_clk = 0 ;
rst_n = 0 ;
#10
rst_n = 1 ;
end
always #1 sys_clk = ~sys_clk ;
initial
begin
uart_out (8'h33); //0011_0011
uart_out (8'hff); //1111_1111
uart_out (8'h18); //0001_1000
uart_out (8'h78); //0111_1000
uart_out (8'h66); //0110_0110
uart_out (8'h1E); //0001_1110
uart_out (8'hCC); //1100_1100
uart_out (8'h9F); //1001_1111
uart_out (8'h9F); //1001_1111
uart_out (8'h66);
uart_out (8'h33);
uart_out (8'h1E);
uart_out (8'hCC);
uart_out (8'h9F);
uart_out (8'h66);
uart_out (8'h66);
end
//任务函数
task uart_out ;
input [8:0] DATA ;
begin
rx_data = 1 ;///空闲位初始
#20
rx_data = 0 ;///起始位
///传输1bit的计时次数*1周期时间=总时间
#(COUNT*2) rx_data = DATA[0] ;///数据位第一位
#(COUNT*2) rx_data = DATA[1] ;///数据位第二位
#(COUNT*2) rx_data = DATA[2] ;
#(COUNT*2) rx_data = DATA[3] ;
#(COUNT*2) rx_data = DATA[4] ;
#(COUNT*2) rx_data = DATA[5] ;
#(COUNT*2) rx_data = DATA[6] ;
#(COUNT*2) rx_data = DATA[7] ;
#(COUNT*2) rx_data = 0 ;
/*这一步相当于代替了串口调试工具,串口调试工具根据选择的"偶校验"
自动在数据位后面生成"0"或者"1",我们这边只能赋值一个数"0"或者"1"。
我选择赋值为0,所以注定了"8'h31""8'hef""8'h10"是通不过的。
*/
#(COUNT*2) rx_data = 1 ;
#(COUNT*2) ;//停止位也需要时间
// # 80000 ;
end
endtask
single_ram_test single_ram_test_u1(
. sys_clk (sys_clk ) ,
. rst_n (rst_n ) ,
. rx_data (rx_data ) ,
. tx_data (tx_data )
);
endmodule
仿真结果: