文章目录
- Async transmitter
- Async receiver
- 1. RS-232 串行接口的工作原理
- DB-9 connector
- Asynchronous communication
- How fast can we send data?
- 2.波特率时钟生成器
- Parameterized FPGA baud generator
- 3.RS-232 transmitter
- 数据序列化
- 完整代码:
- 4.RS-232 receiver
- Oversampling
- The design
- 完整代码
- 5.How to use the RS-232 transmitter and receiver
- 龙芯杯个人赛串口信号连接
串行接口是将 FPGA 连接到 PC 的简单方法。我们只需要一个发射器和接收器模块。
Async transmitter
它通过对要传输的数据进行序列化,创建一个信号 “TxD”。
Async receiver
它从 FPGA 外部获取信号 “RxD”,并将其 “去序列化”,以便在 FPGA 内部使用。
本项目包括五个部分
-
- RS-232 串行接口的工作原理
-
- 波特发生器
-
- 发射器
-
- 接收器
-
- 使用示例
1. RS-232 串行接口的工作原理
RS-232 接口具有以下特点:
- 使用 9 针连接器 “DB-9”(老式 PC 使用 25 针连接器 “DB-25”)。
- 允许双向全双工通信(个人电脑可同时发送和接收数据)。
- 最大通信速度约为 10KB/s。
DB-9 connector
可能在电脑背面看到过这个连接器。
它有 9 个引脚,但重要的有 3 个:
第 2 针:RxD(接收数据)。
第 3 针:TxD(发送数据)。
第 5 针:GND(接地)。
只需 3 根导线,就能发送和接收数据。
数据通常以 8 位为单位发送(我们称之为字节),并进行 “序列化”:先发送 LSB(数据位 0),然后是位 1,…最后是 MSB(位 7)。
Asynchronous communication
该接口使用异步协议。这意味着没有时钟信号与数据一起传输。接收器必须有办法根据接收到的数据位 “计时”。
在 RS-232 的情况下,可以这样做:
电缆两端事先就通信参数(速度、格式......)达成一致。这需要在通信开始前手动完成。
只要线路处于空闲状态,发送器就会发送 "空闲"(="1")。
发送器在传输每个字节前都会发送 "开始"(="0"),以便接收器知道有字节到来。
发送字节数据的 8 位。
发送器在每个字节后发送 "停止"(="1")。
让我们看看 0x55 字节在传输时的样子:
字节 0x55 的二进制形式是 01010101。
但由于它是先传输 LSB(第 0 位),因此该行会像这样切换: 1-0-1-0-1-0-1-0.
下面是另一个例子:
How fast can we send data?
速度以波特为单位,即每秒可发送多少比特。例如,1000 波特表示每秒 1000 比特,或每个比特持续一毫秒。
RS-232 接口的常见实现(如 PC 中使用的接口)不允许使用任何速度。如果你想使用 123456 波特,那就没戏了。你必须采用某种 "标准 "速度。常见的值有
1200 波特。
9600 波特。
38400 波特。
115200 波特(通常是最快的速度)。
115200 波特时,每个比特持续 (1/115200) = 8.7µs。如果传输 8 位数据,则持续时间为 8 x 8.7µs = 69µs。但每个字节需要一个额外的起始位和停止位,因此实际上需要 10 x 8.7µs = 87µs。这意味着最高速度为每秒 11.5KB。
在 115200 波特的情况下,一些带有错误芯片的 PC 需要一个 "长 "停止位(1.5 或 2 位长…),这使得最高速度降至每秒 10.5KB 左右。
物理层
电线上的信号使用正/负电压方案。
1 "使用 -10V(或 -5V 至 -15V)电压发送。
0 "使用 +10V(或 5V 至 15V 之间)发送。
因此,空闲线路的电压类似于 -10V。
2.波特率时钟生成器
在这里,我们希望以最大速度使用串行链路,即 115200 波特(较慢的速度也很容易生成)。FPGA 通常以 MHz 速度运行,远高于 115200Hz(按当今标准,RS-232 的速度相当慢)。我们需要找到一种方法,(通过 FPGA 时钟)产生尽可能接近每秒 115200 次的 "滴答 "声。
传统上,RS-232 芯片使用 1.8432MHz 时钟,因为这样可以很容易地产生标准波特频率… 1.8432MHz 除以 16 得到 115200Hz。
// let's assume the FPGA clock signal runs at 1.8432MHz
// we create a 4-bit counter
reg [3:0] BaudDivCnt;
always @(posedge clk) BaudDivCnt <= BaudDivCnt + 1; // count forever from 0 to 15
// and a tick signal that is asserted once every 16 clocks (so 115200 times a second)
wire BaudTick = (BaudDivCnt==15);
这很简单。但如果时钟频率不是 1.8432MHz,而是 2MHz,该怎么办呢?要从 2MHz 时钟产生 115200Hz 的频率,我们需要将时钟除以 “17.361111111…”。这可不是一个整数。解决办法是有时除以 17,有时除以 18,确保比率保持 “17.361111111”。这其实很容易做到。
请看下面的 "C "代码
while(1) // repeat forever
{
acc += 115200;
if(acc>=2000000) printf("*"); else printf(" ");
acc %= 2000000;
}
这样,平均每 "17.361111111… "循环一次,就能以精确的比例打印出 “*”。
要在 FPGA 中高效地实现同样的功能,我们需要依靠串行接口能够容忍波特频率发生器中百分之几的误差。
我们希望 2000000 是 2 的幂。显然,2000000 不是。因此,我们要改变比率… 用 “1024/59” = 17.356 代替 “2000000/115200”。这非常接近我们的理想比率,而且可以高效地在 FPGA 上实现:我们使用一个 10 位累加器,以 59 为增量,每当累加器溢出时打一个勾。
// let's assume the FPGA clock signal runs at 2.0000MHz
// we use a 10-bit accumulator plus an extra bit for the accumulator carry-out
reg [10:0] acc; // 11 bits total!
// add 59 to the accumulator at each clock
always @(posedge clk)
acc <= acc[9:0] + 59; // use 10 bits from the previous accumulator result, but save the full 11 bits result
wire BaudTick = acc[10]; // so that the 11th bit is the accumulator carry-out
使用我们的 2MHz 时钟,"BaudTick "每秒断言 115234 次,与理想的 115200 误差为 0.03%。
Parameterized FPGA baud generator
之前的设计使用的是 10 位累加器,但随着时钟频率的增加,需要更多的位数。
下面是一个使用 25MHz 时钟和 16 位累加器的设计。该设计是参数化的,因此很容易定制。
parameter ClkFrequency = 25000000; // 25MHz
parameter Baud = 115200;
parameter BaudGeneratorAccWidth = 16;
parameter BaudGeneratorInc = (Baud<<BaudGeneratorAccWidth)/ClkFrequency;
reg [BaudGeneratorAccWidth:0] BaudGeneratorAcc;
always @(posedge clk)
BaudGeneratorAcc <= BaudGeneratorAcc[BaudGeneratorAccWidth-1:0] + BaudGeneratorInc;
wire BaudTick = BaudGeneratorAcc[BaudGeneratorAccWidth];
最后一个实现问题:"BaudGeneratorInc "的计算是错误的,这是因为 Verilog 使用 32 位中间结果,而计算结果超过了 32 位。为解决这个问题,请修改如下内容。
parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4);
这条线的另一个优点是将结果四舍五入,而不是截断。
现在我们有了足够精确的波特发生器,就可以继续使用 RS-232 发送器和接收器模块了。
- 代码实现
module BaudTickGen(
input wire clk, enable,
output wire tick // generate a tick at the specified baud rate * oversampling
);
parameter ClkFrequency = 25000000;
parameter Baud = 115200;
parameter Oversampling = 1;
function integer log2(input integer v); begin log2=0; while(v>>log2) log2=log2+1; end endfunction
localparam AccWidth = log2(ClkFrequency/Baud)+8; // +/- 2% max timing error over a byte
reg [AccWidth:0] Acc = 0;
localparam ShiftLimiter = log2(Baud*Oversampling >> (31-AccWidth)); // this makes sure Inc calculation doesn't overflow
localparam Inc = ((Baud*Oversampling << (AccWidth-ShiftLimiter))+(ClkFrequency>>(ShiftLimiter+1)))/(ClkFrequency>>ShiftLimiter);
always @(posedge clk) if(enable) Acc <= Acc[AccWidth-1:0] + Inc[AccWidth:0]; else Acc <= Inc[AccWidth:0];
assign tick = Acc[AccWidth];
- 解释
这段Verilog代码看起来是在实现一个串口通信的波特率时钟生成器。我们逐段解释一下代码:
-
模块定义:
module BaudTickGen( input wire clk, enable, output wire tick // 根据指定的波特率 * 过采样率生成时钟脉冲 );
这个模块有三个端口:
clk
和enable
作为输入,tick
作为输出。它旨在根据指定的波特率和过采样率生成一个时钟脉冲信号。 -
参数声明:
parameter ClkFrequency = 25000000; parameter Baud = 115200; parameter Oversampling = 1;
这些参数定义了时钟频率 (
ClkFrequency
)、所需波特率 (Baud
) 以及过采样因子 (Oversampling
)。您可以根据需要自定义这些值。 -
Log2 函数:
function integer log2(input integer v); begin log2 = 0; while (v >> log2) log2 = log2 + 1; end endfunction
这是一个简单的函数,用于计算输入整数
v
的以2为底的对数。 -
局部参数:
localparam AccWidth = log2(ClkFrequency/Baud) + 8;
AccWidth
是根据所需的位数来表示累加值 (Acc
),以实现在一个字节内最大的时序误差不超过 +/- 2%。localparam ShiftLimiter = log2(Baud*Oversampling >> (31-AccWidth));
ShiftLimiter
用于限制左移操作在计算Inc
时以防止溢出。localparam Inc = ((Baud*Oversampling << (AccWidth-ShiftLimiter)) + (ClkFrequency>>(ShiftLimiter+1))) / (ClkFrequency>>ShiftLimiter);
Inc
是每个时钟周期添加到累加器的增量值,以实现所需的波特率。always @(posedge clk) if(enable) Acc <= Acc[AccWidth-1:0] + Inc[AccWidth:0]; else Acc <= Inc[AccWidth:0];
这个总是块在时钟的上升沿触发 (
posedge clk
)。如果enable
为真,则将Inc
的值累加到累加器Acc
中;否则,将Acc
设置为Inc
的初始值。 -
赋值语句:
assign tick = Acc[AccWidth];
这将累加器的最高位赋给
tick
输出,这就是生成的时钟脉冲信号。
3.RS-232 transmitter
我们正在构建一个参数固定的 “异步发射机”:8 个数据位、2 个停止位、无奇偶校验。
工作原理是这样的:
发送器在 FPGA 内获取 8 位数据并将其序列化(从 "TxD_start "信号断定时开始)。
在传输过程中,"忙 "信号被断开(在此期间 "TxD_start "信号被忽略)。
数据序列化
要完成起始位、8 个数据位和停止位,似乎应该使用状态机。
reg [3:0] state;
// the state machine starts when "TxD_start" is asserted, but advances when "BaudTick" is asserted (115200 times a second)
always @(posedge clk)
case(state)
4'b0000: if(TxD_start) state <= 4'b0100;
4'b0100: if(BaudTick) state <= 4'b1000; // start
4'b1000: if(BaudTick) state <= 4'b1001; // bit 0
4'b1001: if(BaudTick) state <= 4'b1010; // bit 1
4'b1010: if(BaudTick) state <= 4'b1011; // bit 2
4'b1011: if(BaudTick) state <= 4'b1100; // bit 3
4'b1100: if(BaudTick) state <= 4'b1101; // bit 4
4'b1101: if(BaudTick) state <= 4'b1110; // bit 5
4'b1110: if(BaudTick) state <= 4'b1111; // bit 6
4'b1111: if(BaudTick) state <= 4'b0001; // bit 7
4'b0001: if(BaudTick) state <= 4'b0010; // stop1
4'b0010: if(BaudTick) state <= 4'b0000; // stop2
default: if(BaudTick) state <= 4'b0000;
endcase
现在,我们只需生成 "TxD "输出。
reg muxbit;
always @(state[2:0])
case(state[2:0])
0: muxbit <= TxD_data[0];
1: muxbit <= TxD_data[1];
2: muxbit <= TxD_data[2];
3: muxbit <= TxD_data[3];
4: muxbit <= TxD_data[4];
5: muxbit <= TxD_data[5];
6: muxbit <= TxD_data[6];
7: muxbit <= TxD_data[7];
endcase
// combine start, data, and stop bits together
assign TxD = (state<4) | (state[3] & muxbit);
完整代码:
module async_transmitter(
input wire clk,
input wire TxD_start,
input wire [7:0] TxD_data,
output wire TxD,
output wire TxD_busy
);
// Assert TxD_start for (at least) one clock cycle to start transmission of TxD_data
// TxD_data is latched so that it doesn't have to stay valid while it is being sent
parameter ClkFrequency = 25000000; // 25MHz
parameter Baud = 115200;
// generate
// if(ClkFrequency<Baud*8 && (ClkFrequency % Baud!=0)) ASSERTION_ERROR PARAMETER_OUT_OF_RANGE("Frequency incompatible with requested Baud rate");
// endgenerate
`ifdef SIMULATION
wire BitTick = 1'b1; // output one bit per clock cycle
`else
wire BitTick;
BaudTickGen #(ClkFrequency, Baud) tickgen(.clk(clk), .enable(TxD_busy), .tick(BitTick));
`endif
reg [3:0] TxD_state = 0;
wire TxD_ready = (TxD_state==0);
assign TxD_busy = ~TxD_ready;
reg [7:0] TxD_shift = 0;
always @(posedge clk)
begin
if(TxD_ready & TxD_start)
TxD_shift <= TxD_data;
else
if(TxD_state[3] & BitTick)
TxD_shift <= (TxD_shift >> 1);
case(TxD_state)
4'b0000: if(TxD_start) TxD_state <= 4'b0100;
4'b0100: if(BitTick) TxD_state <= 4'b1000; // start bit
4'b1000: if(BitTick) TxD_state <= 4'b1001; // bit 0
4'b1001: if(BitTick) TxD_state <= 4'b1010; // bit 1
4'b1010: if(BitTick) TxD_state <= 4'b1011; // bit 2
4'b1011: if(BitTick) TxD_state <= 4'b1100; // bit 3
4'b1100: if(BitTick) TxD_state <= 4'b1101; // bit 4
4'b1101: if(BitTick) TxD_state <= 4'b1110; // bit 5
4'b1110: if(BitTick) TxD_state <= 4'b1111; // bit 6
4'b1111: if(BitTick) TxD_state <= 4'b0010; // bit 7
4'b0010: if(BitTick) TxD_state <= 4'b0000; // stop1
//4'b0011: if(BitTick) TxD_state <= 4'b0000; // stop2
default: if(BitTick) TxD_state <= 4'b0000;
endcase
end
assign TxD = (TxD_state<4) | (TxD_state[3] & TxD_shift[0]); // put together the start, data and stop bits
endmodule
4.RS-232 receiver
我们正在构建一个 “异步接收器”:
我们的实施工作就是这样:
模块从 RxD 线路中收集数据。
当接收到一个字节时,它就会出现在 "数据 "总线上。一旦接收到一个完整的字节,"data_ready "就会断言一个时钟。
请注意,"数据 "只有在 "数据就绪 "断言时才有效。其余时间不要使用它,因为可能会有新的数据出现,从而对它进行洗牌。
Oversampling
异步接收器必须以某种方式与接收到的信号同步(它通常无法访问发送器使用的时钟)。
为了确定新数据字节何时到来,我们以波特率频率的倍数对信号进行过采样,寻找 "起始 "位。
一旦检测到 "起始 "位,我们就以已知的波特率对线路进行采样,以获取数据位。
接收器通常以 16 倍波特率对输入信号进行过采样。这里我们使用 8 倍… 对于 115200 波特,采样率为 921600Hz。
假设我们有一个 "Baud8Tick "信号,每秒发出 921600 次。
The design
首先,输入的 "RxD "信号与我们的时钟无关。我们使用两个 D 触发器对其进行过采样,并使其与我们的时钟域同步。
reg [1:0] RxD_sync;
always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0], RxD};
我们对数据进行过滤,以免将 RxD 线路上的短尖峰误认为是起始位。
reg [1:0] RxD_cnt;
reg RxD_bit;
always @(posedge clk)
if(Baud8Tick)
begin
if(RxD_sync[1] && RxD_cnt!=2'b11) RxD_cnt <= RxD_cnt + 1;
else
if(~RxD_sync[1] && RxD_cnt!=2'b00) RxD_cnt <= RxD_cnt - 1;
if(RxD_cnt==2'b00) RxD_bit <= 0;
else
if(RxD_cnt==2'b11) RxD_bit <= 1;
end
一旦检测到 “start”,状态机就会对接收到的每个比特进行处理。
reg [3:0] state;
always @(posedge clk)
if(Baud8Tick)
case(state)
4'b0000: if(~RxD_bit) state <= 4'b1000; // start bit found?
4'b1000: if(next_bit) state <= 4'b1001; // bit 0
4'b1001: if(next_bit) state <= 4'b1010; // bit 1
4'b1010: if(next_bit) state <= 4'b1011; // bit 2
4'b1011: if(next_bit) state <= 4'b1100; // bit 3
4'b1100: if(next_bit) state <= 4'b1101; // bit 4
4'b1101: if(next_bit) state <= 4'b1110; // bit 5
4'b1110: if(next_bit) state <= 4'b1111; // bit 6
4'b1111: if(next_bit) state <= 4'b0001; // bit 7
4'b0001: if(next_bit) state <= 4'b0000; // stop bit
default: state <= 4'b0000;
endcase
请注意,我们使用了一个 "next_bit "信号,以便从一个比特到另一个比特。
reg [2:0] bit_spacing;
always @(posedge clk)
if(state==0)
bit_spacing <= 0;
else
if(Baud8Tick)
bit_spacing <= bit_spacing + 1;
wire next_bit = (bit_spacing==7);
最后,移位寄存器将数据位收集起来。
reg [7:0] RxD_data;
always @(posedge clk) if(Baud8Tick && next_bit && state[3]) RxD_data <= {RxD_bit, RxD_data[7:1]};
完整代码
module async_receiver(
input wire clk,
input wire RxD,
output reg RxD_data_ready,
input wire RxD_clear,
output reg [7:0] RxD_data // data received, valid only (for one clock cycle) when RxD_data_ready is asserted
);
parameter ClkFrequency = 25000000; // 25MHz
parameter Baud = 115200;
parameter Oversampling = 8; // needs to be a power of 2
// we oversample the RxD line at a fixed rate to capture each RxD data bit at the "right" time
// 8 times oversampling by default, use 16 for higher quality reception
// generate
// if(ClkFrequency<Baud*Oversampling) ASSERTION_ERROR PARAMETER_OUT_OF_RANGE("Frequency too low for current Baud rate and oversampling");
// if(Oversampling<8 || ((Oversampling & (Oversampling-1))!=0)) ASSERTION_ERROR PARAMETER_OUT_OF_RANGE("Invalid oversampling value");
// endgenerate
// We also detect if a gap occurs in the received stream of characters
// That can be useful if multiple characters are sent in burst
// so that multiple characters can be treated as a "packet"
wire RxD_idle; // asserted when no data has been received for a while
reg RxD_endofpacket; // asserted for one clock cycle when a packet has been detected (i.e. RxD_idle is going high)
reg [3:0] RxD_state = 0;
`ifdef SIMULATION
wire RxD_bit = RxD;
wire sampleNow = 1'b1; // receive one bit per clock cycle
`else
wire OversamplingTick;
BaudTickGen #(ClkFrequency, Baud, Oversampling) tickgen(.clk(clk), .enable(1'b1), .tick(OversamplingTick));
// synchronize RxD to our clk domain
reg [1:0] RxD_sync = 2'b11;
always @(posedge clk) if(OversamplingTick) RxD_sync <= {RxD_sync[0], RxD};
// and filter it
reg [1:0] Filter_cnt = 2'b11;
reg RxD_bit = 1'b1;
always @(posedge clk)
if(OversamplingTick)
begin
if(RxD_sync[1]==1'b1 && Filter_cnt!=2'b11) Filter_cnt <= Filter_cnt + 1'd1;
else
if(RxD_sync[1]==1'b0 && Filter_cnt!=2'b00) Filter_cnt <= Filter_cnt - 1'd1;
if(Filter_cnt==2'b11) RxD_bit <= 1'b1;
else
if(Filter_cnt==2'b00) RxD_bit <= 1'b0;
end
// and decide when is the good time to sample the RxD line
function integer log2(input integer v); begin log2=0; while(v>>log2) log2=log2+1; end endfunction
localparam l2o = log2(Oversampling);
reg [l2o-2:0] OversamplingCnt = 0;
always @(posedge clk) if(OversamplingTick) OversamplingCnt <= (RxD_state==0) ? 1'd0 : OversamplingCnt + 1'd1;
wire sampleNow = OversamplingTick && (OversamplingCnt==Oversampling/2-1);
`endif
// now we can accumulate the RxD bits in a shift-register
always @(posedge clk)
case(RxD_state)
4'b0000: if(~RxD_bit) RxD_state <= `ifdef SIMULATION 4'b1000 `else 4'b0001 `endif; // start bit found?
4'b0001: if(sampleNow) RxD_state <= 4'b1000; // sync start bit to sampleNow
4'b1000: if(sampleNow) RxD_state <= 4'b1001; // bit 0
4'b1001: if(sampleNow) RxD_state <= 4'b1010; // bit 1
4'b1010: if(sampleNow) RxD_state <= 4'b1011; // bit 2
4'b1011: if(sampleNow) RxD_state <= 4'b1100; // bit 3
4'b1100: if(sampleNow) RxD_state <= 4'b1101; // bit 4
4'b1101: if(sampleNow) RxD_state <= 4'b1110; // bit 5
4'b1110: if(sampleNow) RxD_state <= 4'b1111; // bit 6
4'b1111: if(sampleNow) RxD_state <= 4'b0010; // bit 7
4'b0010: if(sampleNow) RxD_state <= 4'b0000; // stop bit
default: RxD_state <= 4'b0000;
endcase
always @(posedge clk)
if(sampleNow && RxD_state[3]) RxD_data <= {RxD_bit, RxD_data[7:1]};
//reg RxD_data_error = 0;
always @(posedge clk)
begin
if(RxD_clear)
RxD_data_ready <= 0;
else
RxD_data_ready <= RxD_data_ready | (sampleNow && RxD_state==4'b0010 && RxD_bit); // make sure a stop bit is received
//RxD_data_error <= (sampleNow && RxD_state==4'b0010 && ~RxD_bit); // error if a stop bit is not received
end
`ifdef SIMULATION
assign RxD_idle = 0;
`else
reg [l2o+1:0] GapCnt = 0;
always @(posedge clk) if (RxD_state!=0) GapCnt<=0; else if(OversamplingTick & ~GapCnt[log2(Oversampling)+1]) GapCnt <= GapCnt + 1'h1;
assign RxD_idle = GapCnt[l2o+1];
always @(posedge clk) RxD_endofpacket <= OversamplingTick & ~GapCnt[l2o+1] & &GapCnt[l2o:0];
`endif
endmodule
5.How to use the RS-232 transmitter and receiver
该设计允许通过 PC 控制几个 FPGA 引脚(通过 PC 的串行端口)。
它在 FPGA 上创建了 8 个输出(端口名为 "GPout")。FPGA 接收到的任何字符都会更新 GPout。
同时在 FPGA 上创建 8 个输入端(名为 "GPin "的端口)。每当 FPGA 接收到一个字符,GPin 就会传输一次。
GP 输出可用于从电脑远程控制任何东西,可能是 LED 或咖啡机…
module serialGPIO(
input clk,
input RxD,
output TxD,
output reg [7:0] GPout, // general purpose outputs
input [7:0] GPin // general purpose inputs
);
wire RxD_data_ready;
wire [7:0] RxD_data;
async_receiver RX(.clk(clk), .RxD(RxD), .RxD_data_ready(RxD_data_ready), .RxD_data(RxD_data));
always @(posedge clk) if(RxD_data_ready) GPout <= RxD_data;
async_transmitter TX(.clk(clk), .TxD(TxD), .TxD_start(RxD_data_ready), .TxD_data(GPin));
endmodule
龙芯杯个人赛串口信号连接
// | 0xBFD003F8 | [7:0] | 串口数据,读、写地址分别表示串口接收、发送一个字节 |
// | 0xBFD003FC | [0] | 只读,为1时表示串口空闲,可发送数据 |
// | 0xBFD003FC | [1] | 只读,为1时表示串口收到数据 |
module uart(
input wire clk,
input wire resetn,
// read and write from cpu
// input wire conf_en,
input wire conf_re,conf_we,
// input wire [3 :0] conf_wen,
input wire [31:0] conf_addr,
input wire [31:0] conf_wdata,
output wire [31:0] conf_rdata,
// read and write to device on board
//直连串口信号
output wire txd, //直连串口发送端
input wire rxd //直连串口接收端
// output reg [15:0] led,
// input wire [7:0] switch
);
wire read_flag;
wire write_uart;
wire read_uart;
assign read_flag = ((conf_addr == 32'hbfd003fc) && (conf_re)) ? 1'b1:1'b0;
assign read_uart = ((conf_addr == 32'hbfd003f8) && (conf_re)) ? 1'b1:1'b0;
assign write_uart = ((conf_addr == 32'hbfd003f8) && (conf_we)) ? 1'b1:1'b0;
wire [7:0] ext_uart_wdata;// write data
// buffers
reg [7:0] ext_uart_rbuffer;// read buffer
reg [1:0] ext_uart_flag; // uart flag for read and write at addr 0xbfd003fc
assign conf_rdata = read_flag?{30'h0,ext_uart_flag}:read_uart?{24'h0,ext_uart_rbuffer}:32'h0;
wire [7:0] ext_uart_rx;
wire ext_uart_ready,ext_uart_clear;
wire ext_uart_busy; // transmitter busy flag
reg ext_uart_start; // transmitter start work signal
always @(posedge clk) begin
if(!resetn)
ext_uart_flag <= 2'h1;
else begin
if(ext_uart_ready)
ext_uart_flag[1] <= 1;
else if(read_uart)
ext_uart_flag[1] <= 0;
// write flag
// if(write_uart)
// ext_uart_flag[0] <= 0;
// else if(!ext_uart_busy)
// ext_uart_flag[0] <= 1;
if (!ext_uart_busy)
ext_uart_flag[0] <= 1;
else if(write_uart)
ext_uart_flag[0] <= 0;
end
end
//uart reciever
async_receiver #(.ClkFrequency(60000000),.Baud(9600))
ext_uart_r(
.clk(clk),
.RxD(rxd),
.RxD_data_ready(ext_uart_ready),
.RxD_clear(ext_uart_clear),
.RxD_data(ext_uart_rx)
);
// store RxD_data to read buffer and clear RxD_data after store
assign ext_uart_clear = ext_uart_ready;
// assign ext_uart_rdata = ext_uart_rx;
always @(posedge clk) begin
if(ext_uart_ready)
ext_uart_rbuffer <= ext_uart_rx;
end
// assign ext_uart_rbuffer = ext_uart_rx;
always @(posedge clk) begin
if(!ext_uart_busy && write_uart)
ext_uart_start <= 1'b1;
else
ext_uart_start <= 1'b0;
end
wire [7:0] ext_uart_tx;// write data
reg [7:0] last_data;
always @(posedge clk) begin
last_data <= ext_uart_tx[7:0];
end
assign ext_uart_tx = write_uart?conf_wdata[7:0]:last_data;
// assign ext_uart_tx = write_uart?conf_wdata[7:0]:8'h0;
async_transmitter #(.ClkFrequency(6000_0000),.Baud(9600))
ext_uart_t(
.clk(clk),
.TxD(txd),
.TxD_busy(ext_uart_busy),
.TxD_start(ext_uart_start),
.TxD_data(ext_uart_tx) // transmit the data in buffer to txd
);