逻辑设计部分可以参照uart进行设计,但是还是那句话——设计好再写代码!设计好再写代码!着急写代码是吧,着急写就白想活着!
1. baud_clk_gen
这个就单纯的根据波特率分频出一个sck时钟,不解释!
1.1. 代码
module baud_clk_gen#(
parameter BAUD_RATE = 12500000,
parameter PCLK_FREQ = 50000000
)(
input prstn_sck,
input pclk,
output tx_sck
);
localparam BAUD_CNT_END = PCLK_FREQ / BAUD_RATE;
localparam BAUD_CNT_WIDTH = $clog2(BAUD_CNT_END);
reg [BAUD_CNT_WIDTH-1:0] baud_cnt;
reg tx_sck_r;
always@(posedge pclk) begin
if(!prstn_sck)
baud_cnt <= 'b0;
else if(baud_cnt < BAUD_CNT_END-1)
baud_cnt <= baud_cnt + 'b1;
else
baud_cnt <= 'b0;
end
always@(posedge pclk) begin
if(!prstn_sck)
tx_sck_r<= 'b0;
else
tx_sck_r <= !tx_sck_r;
end
assign tx_sck = tx_sck_r;
endmodule
2. usart_tx
这部分内容与uart_tx基本相同,只需把波特率计数器baud_cnt去掉就ok了
仍然是基于状态机进行设计,状态转换图如下
2.1. 代码
module usart_tx#(
parameter PDATA_WIDTH = 16,
parameter ASYNC_FIFO_DEPTH = 4096
)(
input prstn,
input pclk,
input tx_sck,
input [PDATA_WIDTH-1:0] fifo_wdata,
input fifo_wdata_val,
output fifo_full,
output txd
);
localparam IDLE = 1'b0;
localparam TRANS = 1'b1;
localparam FIFO_ADDR_WIDTH = $clog2(ASYNC_FIFO_DEPTH/PDATA_WIDTH + 1);
localparam FRAME_WIDTH = PDATA_WIDTH + 3;
localparam BIT_CNT_WIDTH = $clog2(FRAME_WIDTH+1);
reg cur_state;
reg nxt_state;
reg [FRAME_WIDTH-1:0] data_shift;
reg [BIT_CNT_WIDTH-1:0] bit_cnt;
wire parity;
reg fifo_rd_en;
wire [PDATA_WIDTH-1:0] fifo_rdata;
wire fifo_rdata_val;
wire fifo_empty;
always@(posedge tx_sck) begin
if(!prstn)
cur_state <= IDLE;
else
cur_state <= nxt_state;
end
always@(*) begin
case(cur_state)
IDLE:
if(fifo_rdata_val)
nxt_state = TRANS;
else
nxt_state = IDLE;
TRANS:
if(bit_cnt == FRAME_WIDTH)
nxt_state = IDLE;
else
nxt_state = TRANS;
default: nxt_state = IDLE;
endcase
end
always@(posedge tx_sck) begin
if(!prstn)
fifo_rd_en <= 1'b1;
else if(cur_state == IDLE) begin
if(fifo_rd_en && !fifo_empty)
fifo_rd_en <= 1'b0;
end
else if(cur_state == TRANS) begin
if(bit_cnt == FRAME_WIDTH)
fifo_rd_en <= 1'b1;
end
end
always@(posedge tx_sck) begin
if(!prstn)
data_shift <= {FRAME_WIDTH{1'b1}};
else if(cur_state == IDLE) begin
if(fifo_rdata_val)
data_shift <= {1'b0,fifo_rdata,parity,1'b1};
end
else if(cur_state == TRANS)
data_shift <= {data_shift[FRAME_WIDTH-2:0],1'b1};
end
assign parity = (fifo_rdata_val)?(^fifo_rdata+1'b1):1'b0;
assign txd = data_shift[FRAME_WIDTH-1];
always@(posedge tx_sck) begin
if(!prstn)
bit_cnt <= 'd0;
else if(cur_state == IDLE) begin
if(fifo_rdata_val)
bit_cnt <= 'd1;
end
else if(cur_state == TRANS)
bit_cnt <= bit_cnt + 'd1;
end
async_fifo#(
.ASYNC_FIFO_DEPTH (ASYNC_FIFO_DEPTH ),
.WDATA_WIDTH (PDATA_WIDTH ),
.RDATA_WIDTH (PDATA_WIDTH ),
.PROG_DEPTH (ASYNC_FIFO_DEPTH ),
.ADDR_WIDTH (FIFO_ADDR_WIDTH )
)u_async_fifo(
.rstn (prstn ),
.wclk (pclk ),
.wdata ( fifo_wdata ),
.wr_en ( fifo_wdata_val ),
.full ( fifo_full ),
.rclk ( tx_sck ),
.rd_en ( fifo_rd_en ),
.rdata ( fifo_rdata ),
.valid ( fifo_rdata_val ),
.empty ( fifo_empty ),
.pfull ( )
);
endmodule
3. usart_rx
整个状态机的流程也与uart_rx类似,不过要注意由于是同步串口,usart_tx对txd是上升沿驱动,所以在对rxd采样时要使用下降沿,但将并数据写入FIFO则还是基于上升沿,所以这里有一个转化的过程。
3.1. IDLE
IDLE时等待rxd由高变低,表示开始传输。
状态机是rx_sck上升沿驱动,rxd也是rx_sck上升沿驱动,那么必然导致在rx_sck上升沿处对rxd采样以判断低电平到来,违背了在rx_sck下降沿采样rxd的初衷。
注意,由于usart_rx和usart_tx是同步的,所以rxd不同打拍
那怎么办?我们可以在rx_sck下降沿将rxd采样至某个信号,然后在rx_sck上升沿处对该信号采样以判断是否发生状态转换,而这个信号就可以是bit_cnt,如下图
3.3. REC
在此阶段不断在rx_sck下降沿将rxd串转并,由于最终要写入FIFO,故直接将数据存入fifo_wdata即可,存完fifo_wdata还需要存储校验位
如上图所示,由于是rx_sck下降沿采样,所以bit_cnt计数、fifo_wdata串转并、校验位parity都一定是在rx_sck下降沿驱动。
3.4. FIFO_WRITE
rx_sck下降沿采样rxd对上升沿采样fifo_wdata如FIFO没有影响。
3.5. 代码
module usart_rx#(
parameter PDATA_WIDTH = 16,
parameter ASYNC_FIFO_DEPTH = 4096
)(
input prstn,
input pclk,
input rx_sck,
input rxd,
input fifo_rd_en,
output [PDATA_WIDTH-1:0] fifo_rdata,
output fifo_rdata_val,
output fifo_empty
);
localparam IDLE = 2'b00;
localparam REC = 2'b01;
localparam FIFO_WRITE = 2'b11;
localparam FIFO_ADDR_WIDTH = $clog2(ASYNC_FIFO_DEPTH/PDATA_WIDTH + 1);
localparam FRAME_WIDTH = PDATA_WIDTH + 3;
localparam BIT_CNT_WIDTH = $clog2(FRAME_WIDTH+1);
reg [1:0] cur_state;
reg [1:0] nxt_state;
reg parity;
reg [PDATA_WIDTH-1:0] fifo_wdata;
reg fifo_wr_en;
reg [BIT_CNT_WIDTH-1:0] bit_cnt;
wire fifo_full;
always@(posedge rx_sck) begin
if(!prstn)
cur_state <= IDLE;
else
cur_state <= nxt_state;
end
always@(*) begin
case(cur_state)
IDLE:
if(bit_cnt == 'd1)
nxt_state = REC;
else
nxt_state = IDLE;
REC:
if(bit_cnt == FRAME_WIDTH) begin
if(parity == ^fifo_wdata + 1'b1)
nxt_state = FIFO_WRITE;
else
nxt_state = IDLE;
end
else
nxt_state = REC;
FIFO_WRITE:
if(fifo_wr_en && !fifo_full)
nxt_state = IDLE;
else
nxt_state = FIFO_WRITE;
default: nxt_state = IDLE;
endcase
end
always@(negedge rx_sck) begin
if(!prstn)
bit_cnt <= 'd0;
else if(cur_state == IDLE) begin
if(!rxd)
bit_cnt <= 'd1;
end
else if(cur_state == REC)
bit_cnt <= bit_cnt + 'd1;
end
always@(negedge rx_sck) begin
if(!prstn)
fifo_wdata <= 'd0;
else if(cur_state == REC) begin
if(bit_cnt >= 'd1 && bit_cnt <= PDATA_WIDTH)
fifo_wdata <= {fifo_wdata[PDATA_WIDTH-2:0],rxd};
end
end
always@(negedge rx_sck) begin
if(!prstn)
parity <= 1'b0;
else if(cur_state == REC) begin
if(bit_cnt == FRAME_WIDTH-2)
parity <= rxd;
end
end
always@(posedge rx_sck) begin
if(!prstn)
fifo_wr_en <= 1'b0;
else if(cur_state == REC) begin
if(bit_cnt == FRAME_WIDTH && (parity == ^fifo_wdata + 1'b1))
fifo_wr_en <= 1'b1;
end
else if(cur_state == FIFO_WRITE) begin
if(fifo_wr_en && !fifo_full)
fifo_wr_en <= 1'b0;
end
end
async_fifo#(
.ASYNC_FIFO_DEPTH (ASYNC_FIFO_DEPTH ),
.WDATA_WIDTH (PDATA_WIDTH ),
.RDATA_WIDTH (PDATA_WIDTH ),
.PROG_DEPTH (ASYNC_FIFO_DEPTH ),
.ADDR_WIDTH (FIFO_ADDR_WIDTH )
)u_async_fifo(
.rstn (prstn ),
.wclk (rx_sck ),
.wdata ( fifo_wdata ),
.wr_en ( fifo_wr_en ),
.full ( fifo_full ),
.rclk ( pclk ),
.rd_en ( fifo_rd_en ),
.rdata ( fifo_rdata ),
.valid ( fifo_rdata_val ),
.empty ( fifo_empty ),
.pfull ( )
);
endmodule
4. usart
顶层模块除了连连看,还要明确usart_tx和usart_rx各信号与usart的APB接口是什么关系,注意pready。
4.1. 代码
module usart#(
parameter BAUD_RATE = 10000000,
parameter PCLK_FREQ = 50000000,
parameter PADDR_WIDTH = 1,
parameter PDATA_WIDTH = 16,
parameter ASYNC_FIFO_DEPTH = 4096
)(
input prstn_sck,
input pclk,
input prstn,
input [PADDR_WIDTH-1:0] paddr,
input pwrite,
input psel,
input penable,
input [PDATA_WIDTH-1:0] pwdata,
output [PDATA_WIDTH-1:0] prdata,
output pready,
input rx_sck,
input rxd,
output tx_sck,
output txd
);
reg [PDATA_WIDTH-1:0] tx_fifo_wdata;
reg tx_fifo_wdata_val;
wire tx_fifo_full;
reg rx_fifo_rd_en;
wire [PDATA_WIDTH-1:0] rx_fifo_rdata;
wire rx_fifo_rdata_val;
wire rx_fifo_empty;
reg [PDATA_WIDTH-1:0] prdata_r;
reg pready_r;
baud_clk_gen#(
.BAUD_RATE (BAUD_RATE ),
.PCLK_FREQ (PCLK_FREQ )
)u_baud_clk_gen(
.prstn_sck (prstn_sck ),
.pclk (pclk ),
.tx_sck (tx_sck )
);
usart_tx#(
.PDATA_WIDTH (PDATA_WIDTH ),
.ASYNC_FIFO_DEPTH (ASYNC_FIFO_DEPTH )
)u_usart_tx(
.prstn (prstn ),
.pclk (pclk ),
.tx_sck (tx_sck ),
.fifo_wdata (tx_fifo_wdata ),
.fifo_wdata_val (tx_fifo_wdata_val ),
.fifo_full (tx_fifo_full ),
.txd (txd )
);
usart_rx#(
.PDATA_WIDTH (PDATA_WIDTH ),
.ASYNC_FIFO_DEPTH (ASYNC_FIFO_DEPTH )
)u_usart_rx(
.prstn (prstn ),
.pclk (pclk ),
.rx_sck (rx_sck ),
.rxd (rxd ),
.fifo_rd_en (rx_fifo_rd_en ),
.fifo_rdata (rx_fifo_rdata ),
.fifo_rdata_val (rx_fifo_rdata_val ),
.fifo_empty (rx_fifo_empty )
);
always@(*) begin
if(psel && penable && pwrite && paddr == 'b1) begin
tx_fifo_wdata = pwdata;
tx_fifo_wdata_val = 1'b1;
end
else begin
tx_fifo_wdata = 'd0;
tx_fifo_wdata_val = 1'b0;
end
end
always@(*) begin
if(psel && penable && !pwrite && paddr == 'b0)
prdata_r = rx_fifo_rdata;
else
prdata_r = 'd0;
end
assign prdata = prdata_r;
always@(posedge pclk) begin
if(!prstn)
rx_fifo_rd_en <= 1'b0;
else if(psel && !pwrite && paddr == 'b0) begin
if(!penable)
rx_fifo_rd_en <= 1'b1;
else if(rx_fifo_empty)
rx_fifo_rd_en <= 1'b1;
else
rx_fifo_rd_en <= 1'b0;
end
else
rx_fifo_rd_en <= 1'b0;
end
always@(*) begin
if(psel && penable && pwrite && paddr == 'b1)
pready_r = !tx_fifo_full;
else if(psel && penable && !pwrite && paddr == 'b0)
pready_r = rx_fifo_rdata_val;
else
pready_r = 1'b0;
end
assign pready = pready_r;
endmodule
5. usart_tb
此处依然使用回环测试,将usart_tx的txd和tx_sck与usart_rx的rxd和rx_sck直连,检测发送的数据和读出的数据是否一致。
`timescale 1ns/1ps
module usart_tb();
parameter BAUD_RATE = 10000000;
parameter PCLK_FREQ = 50000000;
parameter PADDR_WIDTH = 1;
parameter PDATA_WIDTH = 16;
parameter ASYNC_FIFO_DEPTH = 4096;
logic prstn_sck;
logic pclk;
logic prstn;
logic [PADDR_WIDTH-1:0] paddr;
logic pwrite;
logic psel;
logic penable;
logic [PDATA_WIDTH-1:0] pwdata;
logic [PDATA_WIDTH-1:0] prdata;
logic pready;
logic txd2rxd;
logic sck;
initial begin
pclk = 0;
#10;
forever #10 pclk = !pclk;
end
initial begin
prstn_sck = 1;
prstn = 1;
#50 prstn_sck = 0;
#50 prstn_sck = 1;
#50 prstn = 0;
#50 prstn = 1;
end
initial begin
paddr = 'd0;
pwrite = 1'b0;
psel = 1'b0;
penable = 1'b0;
pwdata = 'd0;
#200;
usart_loop_test();
end
task usart_loop_test();
usart_tx_sequence(13);
#50;
usart_tx_sequence(45);
#50;
usart_tx_sequence(23);
#50;
usart_tx_sequence(18);
#50;
usart_rx_sequence();
#50;
usart_rx_sequence();
#50;
usart_rx_sequence();
#50;
usart_rx_sequence();
endtask
task usart_tx_sequence(input int a);
@(posedge pclk);
#1;
paddr = 1'b1;
pwrite = 1'b1;
psel = 1'b1;
penable = 1'b0;
pwdata = a;
@(posedge pclk);
#1;
penable = 1'b1;
forever begin
@(posedge pclk);
if(psel && penable && pready && paddr == 1'b1) begin
$display("Writing data %h at usart_tx's FIFO success",a);
#1;
psel = 1'b0;
break;
end
end
endtask
task usart_rx_sequence();
@(posedge pclk);
#1;
paddr = 'b0;
pwrite = 1'b0;
psel = 1'b1;
penable = 1'b0;
@(posedge pclk);
#1;
penable = 1'b1;
forever begin
@(posedge pclk);
if(psel && penable && pready && paddr == 1'b0) begin
$display("usart_rx reads data %h from FIFO", prdata);
#1;
psel = 1'b0;
break;
end
end
endtask
usart#(
.BAUD_RATE (BAUD_RATE ),
.PCLK_FREQ (PCLK_FREQ ),
.PADDR_WIDTH (PADDR_WIDTH ),
.PDATA_WIDTH (PDATA_WIDTH ),
.ASYNC_FIFO_DEPTH (ASYNC_FIFO_DEPTH )
)u_usart(
.prstn_sck (prstn_sck ),
.pclk (pclk ),
.prstn (prstn ),
.paddr (paddr ),
.pwrite (pwrite ),
.psel (psel ),
.penable (penable ),
.pwdata (pwdata ),
.prdata (prdata ),
.pready (pready ),
.rx_sck (sck ),
.rxd (txd2rxd ),
.tx_sck (sck ),
.txd (txd2rxd )
);
endmodule
|