与时序逻辑除法器结合的 UART 串口 Verilog 设计
2025-01-04 12:24:35
这篇文章记录了我在实现 uart 串口发送接收数据这一项目的一些精巧的设计和踩过的坑。
项目原码:Github
rx_uart 模块
单次接收 1 个 Byte(8 bits)
- rx:输入串行数据线。
- bps_cnt:波特率计数,记满 5208 个周期后,波特率计数器清零。(波特率为 9600)
- 计满后 end_bps_cnt 会出现一个持续一周期的脉冲信号,表示波特率计数器计满一位。
特色设计:
- bit_cnt:位计数,这里我们并没有使用常量 10 作为计数最大值,我们的 bit 级数最大值会在状态切换时改变。表示当前状态下要接收的位数。
- 当状态 state 为 START 时,bit_cnt 最大值为 1
- 当状态 state 为 DATA 时,bit_cnt 最大值为 8
- 当状态 state 为 STOP 时,bit_cnt 最大值为 1。
- 最大值保存在 bit_max 寄存器中。
- 计满后,end_bit_cnt 会出现一个持续一周期的脉冲信号。
- bit_cnt_take:取位记数器,目的是要在每个周期的 1/2 处取样数据线,采样更加稳定
- bit_cnt_take 的初始值(initial 块)即为 1/2 个周期,这样就可以使 end_bit_cnt_take 的脉冲早于 end_bps_cnt 脉冲 1/2 个周期。
- rx_data 和 rx_ready 同步变化:当 rx_ready 为高电平即表明 8 bits 数据已经被放到了 8 位的数据线上。
问题一:
- 最初 end_bit_cnt 和 end_bps_cnt 都是线网数据类型,并且我使用它们的上升沿作为 always 块的触发条件,这就导致微时序问题非常严重,后来我把它们改为了寄存器类型,并使用 if 条件判断和计数器产生脉冲,这样问题也就解决了
问题二:
- 这里遇到的问题就是位取样的问题,最开始我们在每个周期末取样,但是发现这样取样的数据有错,后来我们干脆放弃微调时序,直接改为每个周期的 1/2 处取样,问题也就随之解决。
ctrl_uart 模块
- 主要的控制模块,实现:
- 逐 Byte 调用 rx_uart 模块接收数据,共接收 4 Bytes
- 接收完成后进行除法运算
- 后依次向 tx_uart 模块发送 4 Bytes 数据。
- rx_done, d_done, tx_done:接收、除法、发送所有位全部完成信号。
- rx_data, d_data, tx_data:接收、除法、发送数据。
- rx_ready, tx_ready:每位的接收、发送完成信号。
- y_to_led:发送给 led_encoder 模块的除发器的运算结果
特色设计:
- tx_state:为实现逐 Byte 发送 4 Bytes 数据,我们使用了一个状态机 tx_state,每发送完成一 Byte 数据后,状态机会自动切换到下一个状态,表明下一发送周期中要发送下一位。
- 这里要发送的数据为 02 00 8D 00
问题一
- 应该先单独测试 UART 串口部分,即不加除法器,只测试输入输出模块的联动。也就是把输入的数据直接传送出去,测试是否能接受到正确的数据。
tx_uart 模块
- 思路和 rx_uart 模块大致相同,bit_max 同样可变。这里不做过多解释。
led_encoder 模块
- 晶体管编码模块,将传递的数据显示到 6 位每位 7 个晶体管上。
- out:输出 7 位数据,用于驱动单位(7 个)晶体管。
- dig:位选信号。
特色设计:
- 此模块内聚性极好,只需要将 6 个 4 bits 数据,共 24 bits,传递到 in[23:0],数据即可显示。
注:
- 操作串口使用的是 Python 库 pyserial。