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

文章目录

      • [Async transmitter](#Async transmitter)
      • [Async receiver](#Async receiver)
        • [1. RS-232 串行接口的工作原理](#1. RS-232 串行接口的工作原理)
          • [DB-9 connector](#DB-9 connector)
          • [Asynchronous communication](#Asynchronous communication)
          • [How fast can we send data?](#How fast can we send data?)
        • 2.波特率时钟生成器
          • [Parameterized FPGA baud generator](#Parameterized FPGA baud generator)
        • [3.RS-232 transmitter](#3.RS-232 transmitter)
        • [4.RS-232 receiver](#4.RS-232 receiver)
        • [5.How to use the RS-232 transmitter and receiver](#5.How to use the RS-232 transmitter and receiver)
      • 龙芯杯个人赛串口信号连接

串行接口是将 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。

verilog 复制代码
// 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 "代码

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 为增量,每当累加器溢出时打一个勾。

verilog 复制代码
// 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 位累加器的设计。该设计是参数化的,因此很容易定制。

c 复制代码
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 位。为解决这个问题,请修改如下内容。

verilog 复制代码
parameter BaudGeneratorInc = ((Baud<<(BaudGeneratorAccWidth-4))+(ClkFrequency>>5))/(ClkFrequency>>4); 

这条线的另一个优点是将结果四舍五入,而不是截断。

现在我们有了足够精确的波特发生器,就可以继续使用 RS-232 发送器和接收器模块了。

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

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

    这个模块有三个端口:clkenable 作为输入,tick 作为输出。它旨在根据指定的波特率和过采样率生成一个时钟脉冲信号。

  • 参数声明:

    verilog 复制代码
    parameter ClkFrequency = 25000000;
    parameter Baud = 115200;
    parameter Oversampling = 1;

    这些参数定义了时钟频率 (ClkFrequency)、所需波特率 (Baud) 以及过采样因子 (Oversampling)。您可以根据需要自定义这些值。

  • Log2 函数:

    verilog 复制代码
    function integer log2(input integer v);
       begin
          log2 = 0;
          while (v >> log2)
             log2 = log2 + 1;
       end
    endfunction

    这是一个简单的函数,用于计算输入整数 v 的以2为底的对数。

  • 局部参数:

    verilog 复制代码
    localparam AccWidth = log2(ClkFrequency/Baud) + 8;

    AccWidth 是根据所需的位数来表示累加值 (Acc),以实现在一个字节内最大的时序误差不超过 +/- 2%。

    verilog 复制代码
    localparam ShiftLimiter = log2(Baud*Oversampling >> (31-AccWidth));

    ShiftLimiter 用于限制左移操作在计算 Inc 时以防止溢出。

    verilog 复制代码
    localparam Inc = ((Baud*Oversampling << (AccWidth-ShiftLimiter)) + (ClkFrequency>>(ShiftLimiter+1))) / (ClkFrequency>>ShiftLimiter);

    Inc 是每个时钟周期添加到累加器的增量值,以实现所需的波特率。

    verilog 复制代码
    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 的初始值。

  • 赋值语句:

    verilog 复制代码
    assign tick = Acc[AccWidth];

    这将累加器的最高位赋给 tick 输出,这就是生成的时钟脉冲信号。

3.RS-232 transmitter

我们正在构建一个参数固定的 "异步发射机":8 个数据位、2 个停止位、无奇偶校验。

工作原理是这样的:

发送器在 FPGA 内获取 8 位数据并将其序列化(从 "TxD_start "信号断定时开始)。
在传输过程中,"忙 "信号被断开(在此期间 "TxD_start "信号被忽略)。
数据序列化

要完成起始位、8 个数据位和停止位,似乎应该使用状态机。

verilog 复制代码
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 "输出。

verilog 复制代码
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); 
完整代码:
verilog 复制代码
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 触发器对其进行过采样,并使其与我们的时钟域同步。

verilog 复制代码
reg [1:0] RxD_sync;
always @(posedge clk) if(Baud8Tick) RxD_sync <= {RxD_sync[0], RxD}; 

我们对数据进行过滤,以免将 RxD 线路上的短尖峰误认为是起始位。

verilog 复制代码
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",状态机就会对接收到的每个比特进行处理。

verilog 复制代码
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 "信号,以便从一个比特到另一个比特。

verilog 复制代码
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); 

最后,移位寄存器将数据位收集起来。

verilog 复制代码
reg [7:0] RxD_data;
always @(posedge clk) if(Baud8Tick && next_bit && state[3]) RxD_data <= {RxD_bit, RxD_data[7:1]}; 
完整代码
verilog 复制代码
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 或咖啡机...

verilog 复制代码
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

龙芯杯个人赛串口信号连接

verilog 复制代码
// | 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
    );
相关推荐
DS小龙哥7 小时前
基于Zynq FPGA的雷龙SD NAND存储芯片性能测试
fpga开发·sd nand·雷龙·spi nand·spi nand flash·工业级tf卡·嵌入式tf卡
上理考研周导师17 小时前
第二章 虚拟仪器及其构成原理
fpga开发
FPGA技术实战18 小时前
《探索Zynq MPSoC》学习笔记(二)
fpga开发·mpsoc
bigbig猩猩1 天前
FPGA(现场可编程门阵列)的时序分析
fpga开发
Terasic友晶科技1 天前
第2篇 使用Intel FPGA Monitor Program创建基于ARM处理器的汇编或C语言工程<二>
fpga开发·汇编语言和c语言
码农阿豪1 天前
基于Zynq FPGA对雷龙SD NAND的测试
fpga开发·sd nand·spi nand·spi nand flash·工业级tf卡·嵌入式tf卡
江山如画,佳人北望1 天前
EDA技术简介
fpga开发
淘晶驰AK1 天前
电子设计竞赛准备经历分享
嵌入式硬件·fpga开发
最好有梦想~1 天前
FPGA时序分析和约束学习笔记(4、IO传输模型)
笔记·学习·fpga开发