龙芯杯个人赛串口——做一个 UART串口——RS-232

2023-12-24 18:39:31


串行接口是将 FPGA 连接到 PC 的简单方法。我们只需要一个发射器和接收器模块。

Async transmitter

它通过对要传输的数据进行序列化,创建一个信号 “TxD”。
在这里插入图片描述

Async receiver

它从 FPGA 外部获取信号 “RxD”,并将其 “去序列化”,以便在 FPGA 内部使用。

在这里插入图片描述
本项目包括五个部分

    1. RS-232 串行接口的工作原理
    1. 波特发生器
    1. 发射器
    1. 接收器
    1. 使用示例
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 发送器和接收器模块了。

  1. 代码实现
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];
  1. 解释
    这段Verilog代码看起来是在实现一个串口通信的波特率时钟生成器。我们逐段解释一下代码:
  • 模块定义:

    module BaudTickGen(
       input  wire clk, enable,
       output wire tick  // 根据指定的波特率 * 过采样率生成时钟脉冲
    );
    

    这个模块有三个端口:clkenable 作为输入,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
    );

文章来源:https://blog.csdn.net/qq_46264636/article/details/135182086
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。