Verilog学习-从FPGA的角度看Uart模块
想从零移植Uboot到FPGA上,但现有的小麻雀内核栈空间是使用FPGA内部的资源,它的内存十分有限,完全无法存放Uboot代码本体,故需要添加SDRAM控制器来调用其FPGA片内的SDRAM或者外部连接的SPI NorFlash,但是改动这些东西,就不可避免的需要涉及到对Verilog代码的修改,对Verilog的学习就此开始。
先看了一遍Verilog的基础语法教程,接下来该结合代码实际理解:
// 包含定义文件,其中包含了各种宏定义和参数
`include "defines.v"
// 模块声明,定义模块名称和端口列表
// module关键字用于声明一个模块,uart是模块名,括号内是输入输出端口列表
module uart(
// 时钟信号输入,wire表示连线类型,input表示输入端口
input wire clk,
// 异步复位信号输入,低电平有效(n表示negedge,下降沿触发)
input wire rst_n,
// 写地址输入,[7:0]表示8位宽的向量(从第7位到第0位)
input wire[7:0] waddr_i,
// 写数据输入,[`MemBus]表示使用MemBus宏定义的位宽(31:0,即32位)
input wire[`MemBus] data_i,
// 字节选通信号输入,用于指定写入哪个字节(4位,可选择4个字节中的任意组合)
input wire[3:0] sel_i,
// 写使能信号,高电平时表示写操作
input wire we_i,
// 读地址输入,8位宽
input wire[7:0] raddr_i,
// 读使能信号,高电平时表示读操作
input wire rd_i,
// 读数据输出,32位宽
output wire[`MemBus] data_o,
// UART发送引脚,输出到外部
output wire tx_pin,
// UART接收引脚,从外部输入
input wire rx_pin,
// UART发送完成中断信号输出,reg类型表示可以被过程赋值
output reg irq_uart_tx,
// UART接收数据中断信号输出,reg类型
output reg irq_uart_rx
);
// 设置串口配置:波特率115200,数据位8,无校验位,1个停止位
// localparam定义局部参数,类似于常量,BAUD_DEF是波特率值
localparam BAUD_DEF = 115200;
// 波特率分频系数,通过系统时钟频率除以波特率计算得到
// `CPU_CLOCK_HZ是定义在config.v中的宏,表示CPU时钟频率
localparam BAUD_DIV = `CPU_CLOCK_HZ / BAUD_DEF;
// 定义状态机的状态常量,使用4位编码
// S_IDLE表示空闲状态
localparam S_IDLE = 4'b0001;
// S_START表示发送起始位状态
localparam S_START = 4'b0010;
// S_SEND_BYTE表示发送数据位状态
localparam S_SEND_BYTE = 4'b0100;
// S_STOP表示发送停止位状态
localparam S_STOP = 4'b1000;
// 状态机相关变量声明
// 当前状态寄存器,4位宽
reg[3:0] state;
// 下一状态寄存器,4位宽
reg[3:0] next_state;
// 循环计数器,用于波特率分频,16位宽
reg[15:0] cycle_cnt;
// 发送数据位,1位宽
reg tx_bit;
// 发送位计数器,用于记录已经发送了多少位数据,4位宽
reg[3:0] bit_cnt;
// 接收相关变量声明
// 接收引脚采样寄存器0
reg rx_q0;
// 接收引脚采样寄存器1
reg rx_q1;
// 接收引脚下降沿检测信号,wire类型表示连线
wire rx_negedge;
// 接收使能信号
reg rx_start;
// 接收时钟边沿计数器,用于计数接收过程中采样的时钟边沿
reg[3:0] rx_clk_edge_cnt;
// 接收时钟边沿电平信号
reg rx_clk_edge_level;
// 接收完成标志
reg rx_done;
// 接收时钟计数器
reg[15:0] rx_clk_cnt;
// 接收分频计数器
reg[15:0] rx_div_cnt;
// 接收数据寄存器,8位宽
reg[7:0] rx_data;
// 接收结束标志
reg rx_over;
// UART寄存器地址定义(偏移地址)
// 控制寄存器地址
localparam UART_CTRL = 8'h0;
// 状态寄存器地址
localparam UART_STATUS = 8'h4;
// 波特率寄存器地址
localparam UART_BAUD = 8'h8;
// 发送数据寄存器地址
localparam UART_TXDATA = 8'hc;
// 接收数据寄存器地址
localparam UART_RXDATA = 8'h10;
// UART控制寄存器,可读可写,地址0x00
// [0]: UART发送使能, 1有效
// [1]: UART接收使能, 1有效
// [2]: UART发送完成中断使能, 1有效
// [3]: UART接收数据中断使能, 1有效
reg [3:0] uart_ctrl;
// UART状态寄存器,地址0x04
// 只读,bit[0]: 发送状态标志, 1: 忙碌, 0: 空闲
// 可读可写,bit[1]: 接收完成标志, 1: 完成, 0: 接收中
reg [1:0] uart_status;
// UART波特率寄存器(分频系数),可读可写,地址0x08
// 16位寄存器,用于存储波特率分频系数
reg [15:0] uart_div;
// UART发送数据寄存器,可读可写,地址0x0c
// 16位寄存器,用于存储待发送的数据
reg [15:0] uart_tx;
// UART接收数据寄存器,只读,地址0x10
// 8位寄存器,用于存储接收到的数据
reg [7:0] uart_rx;
// 生成写使能信号,当we_i为高时表示有写操作
wire wen = we_i;
// 生成读使能信号,当rd_i为高时表示有读操作
wire ren = rd_i;
// 写控制寄存器使能信号,当有写操作且地址匹配UART_CTRL时有效
wire write_reg_ctrl_en = wen & (waddr_i[7:0] == UART_CTRL);
// 写状态寄存器使能信号,当有写操作且地址匹配UART_STATUS时有效
wire write_reg_status_en = wen & (waddr_i[7:0] == UART_STATUS);
// 写波特率寄存器使能信号,当有写操作且地址匹配UART_BAUD时有效
wire write_reg_baud_en = wen & (waddr_i[7:0] == UART_BAUD);
// 写发送数据寄存器使能信号,当有写操作且地址匹配UART_TXDATA时有效
wire write_reg_txdata_en = wen & (waddr_i[7:0] == UART_TXDATA);
// 发送启动标志,当写发送数据寄存器、字节选择有效、发送使能且发送不忙碌时有效
wire tx_start = write_reg_txdata_en & sel_i[0] & uart_ctrl[0] & (~uart_status[0]);
// 接收完成标志,当接收使能且接收结束时有效
wire rx_recv_over = uart_ctrl[1] & rx_over;
// 将tx_bit信号连接到tx_pin输出端口
assign tx_pin = tx_bit;
// 写接收数据寄存器的过程块
// always @(posedge clk or negedge rst_n)表示在时钟上升沿或复位下降沿触发
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理,当rst_n为低电平时执行
if (!rst_n) begin
// 复位时将uart_rx清零
uart_rx <= 8'h0;
end else begin
// 接收完成时,保存接收到的数据
if (rx_recv_over) begin
// 将接收到的数据存入uart_rx寄存器
uart_rx[7:0] <= rx_data;
end
end
end
// 写发送数据寄存器的过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将uart_tx清零
uart_tx <= 15'h0;
end else begin
// 开始发送时,保存要发送的数据
if (tx_start) begin
// 将输入数据的低8位存入uart_tx寄存器
uart_tx[7:0] <= data_i[7:0];
end
end
end
// 写状态寄存器的过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将uart_status清零
uart_status <= 2'h0;
// 清除发送中断标志
irq_uart_tx <= 1'b0;
// 清除接收中断标志
irq_uart_rx <= 1'b0;
end else begin
// 默认情况下清除中断标志
irq_uart_tx <= 1'b0;
irq_uart_rx <= 1'b0;
// 当写状态寄存器且字节选择有效时
if (write_reg_status_en & sel_i[0]) begin
// 用于清接收完成标志
uart_status[1] <= data_i[1];
end
else begin
// 开始发送数据时,置位发送忙标志
if (tx_start) begin
uart_status[0] <= 1'b1;
// 发送完成时,清发送忙标志
end else if ((state == S_STOP) & (cycle_cnt == uart_div[15:0])) begin
uart_status[0] <= 1'b0;
// 发送完成,发出中断请求
irq_uart_tx <= uart_ctrl[2];
end
// 接收完成,置位接收完成标志
if (rx_recv_over) begin
uart_status[1] <= 1'b1;
// 接收完成,发出中断请求
irq_uart_rx <= uart_ctrl[3];
end
end
end
end
// 写控制寄存器的过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将uart_ctrl清零
uart_ctrl <= 4'h0;
end else begin
// 当写控制寄存器且字节选择有效时
if (write_reg_ctrl_en & sel_i[0]) begin
// 将输入数据的低4位存入uart_ctrl寄存器
uart_ctrl <= data_i[3:0];
end
end
end
// 写波特率寄存器的过程块,根据是否定义UART_DIV_ZERO有不同的实现
`ifdef UART_DIV_ZERO
// 如果定义了UART_DIV_ZERO,则波特率分频系数始终为0(用于仿真加速)
always @ (*) begin
// 组合逻辑,当rst_n为高电平时将uart_div设置为0
if (rst_n) uart_div = 16'h0;
end
`else
// 否则使用正常的波特率分频系数设置
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将uart_div设置为默认值(BAUD_DIV)
uart_div <= 16'h0;
end else begin
// 当写波特率寄存器时
if (write_reg_baud_en) begin
// 如果选择了第0个字节,则更新低8位
if (sel_i[0]) begin
uart_div[7:0] <= data_i[7:0];
end
// 如果选择了第1个字节,则更新高8位
if (sel_i[1]) begin
uart_div[15:8] <= data_i[15:8];
end
end
end
end
`endif
// 读数据寄存器,32位宽
reg[31:0] data_r;
// 读寄存器的过程块,同步时钟操作
always @ (posedge clk) begin
// 当有读操作时
if (ren) begin
// 根据读地址选择不同的寄存器进行读取
case (raddr_i[7:0])
// 读控制寄存器
UART_CTRL: data_r <= {28'h0, uart_ctrl};
// 读状态寄存器
UART_STATUS: data_r <= {30'h0, uart_status};
// 读波特率寄存器
UART_BAUD: data_r <= {16'h0, uart_div};
// 读接收数据寄存器
UART_RXDATA: data_r <= {24'h0, uart_rx};
// 其他情况返回0
default: data_r <= 32'h0;
endcase
end
else begin
// 没有读操作时不改变data_r的值
end
end
// 将读取的数据连接到输出端口
assign data_o = data_r;
// *************************** 发送(TX)部分 ****************************
// 状态机当前状态更新过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时状态设为空闲状态
state <= S_IDLE;
end else begin
// 更新状态为下一状态
state <= next_state;
end
end
// 状态机下一状态计算过程块(组合逻辑)
always @ (*) begin
// 根据当前状态确定下一状态
case (state)
// 当前状态为空闲状态
S_IDLE: begin
// 如果启动发送,则进入发送起始位状态
if (tx_start) begin
next_state = S_START;
end else begin
// 否则继续保持空闲状态
next_state = S_IDLE;
end
end
// 当前状态为发送起始位状态
S_START: begin
// 如果计数达到分频值,则进入发送数据位状态
if (cycle_cnt == uart_div[15:0]) begin
next_state = S_SEND_BYTE;
end else begin
// 否则继续保持发送起始位状态
next_state = S_START;
end
end
// 当前状态为发送数据位状态
S_SEND_BYTE: begin
// 如果计数达到分频值且已发送完7位数据,则进入发送停止位状态
if ((cycle_cnt == uart_div[15:0]) & (bit_cnt == 4'd7)) begin
next_state = S_STOP;
end else begin
// 否则继续保持发送数据位状态
next_state = S_SEND_BYTE;
end
end
// 当前状态为发送停止位状态
S_STOP: begin
// 如果计数达到分频值,则回到空闲状态
if (cycle_cnt == uart_div[15:0]) begin
next_state = S_IDLE;
end else begin
// 否则继续保持发送停止位状态
next_state = S_STOP;
end
end
// 默认情况回到空闲状态
default: begin
next_state = S_IDLE;
end
endcase
end
// 波特率分频计数器过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将计数器清零
cycle_cnt <= 16'h0;
end else begin
// 如果当前状态为空闲状态
if (state == S_IDLE) begin
// 将计数器清零
cycle_cnt <= 16'h0;
end else begin
// 如果计数达到分频值
if (cycle_cnt >= uart_div[15:0]) begin
// 将计数器清零
cycle_cnt <= 16'h0;
end else begin
// 否则计数器加1
cycle_cnt <= cycle_cnt + 16'h1;
end
end
end
end
// 发送位计数器过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将位计数器清零
bit_cnt <= 4'h0;
end else begin
// 根据当前状态进行处理
case (state)
// 空闲状态
S_IDLE: begin
// 将位计数器清零
bit_cnt <= 4'h0;
end
// 发送数据位状态
S_SEND_BYTE: begin
// 如果计数达到分频值
if (cycle_cnt == uart_div[15:0]) begin
// 位计数器加1
bit_cnt <= bit_cnt + 4'h1;
end
end
endcase
end
end
// 发送数据位过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将发送位设为0
tx_bit <= 1'b0;
end else begin
// 根据当前状态确定发送位的值
case (state)
// 空闲状态或停止位状态
S_IDLE, S_STOP: begin
// 发送位设为1(空闲状态)
tx_bit <= 1'b1;
end
// 起始位状态
S_START: begin
// 发送位设为0(起始位)
tx_bit <= 1'b0;
end
// 数据位状态
S_SEND_BYTE: begin
// 发送uart_tx中对应位的数据
tx_bit <= uart_tx[bit_cnt];
end
endcase
end
end
// *************************** 接收(RX)部分 ****************************
// 接收引脚采样过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将采样寄存器清零
rx_q0 <= 1'b0;
rx_q1 <= 1'b0;
end else begin
// 对接收引脚进行两级采样
rx_q0 <= rx_pin;
rx_q1 <= rx_q0;
end
end
// 下降沿检测(检测起始信号)
// assign语句用于连续赋值,这里检测接收引脚的下降沿
assign rx_negedge = rx_q1 & (~rx_q0);
// 产生开始接收数据信号的过程块,接收期间一直有效
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时清除接收使能信号
rx_start <= 1'b0;
end else begin
// 如果接收使能
if (uart_ctrl[1]) begin
// 如果检测到下降沿(起始位)
if (rx_negedge) begin
// 置位接收使能信号
rx_start <= 1'b1;
end else if (rx_clk_edge_cnt == 4'd9) begin
// 如果接收时钟边沿计数达到9,则清除接收使能信号
rx_start <= 1'b0;
end
end else begin
// 如果接收未使能,则清除接收使能信号
rx_start <= 1'b0;
end
end
end
// 接收分频计数器设置过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将分频计数器清零
rx_div_cnt <= 16'h0;
end else begin
// 第一个时钟沿只需波特率分频系数的一半(用于精确定位数据位中心)
if (rx_start == 1'b1 && rx_clk_edge_cnt == 4'h0) begin
rx_div_cnt <= {1'b0, uart_div[15:1]};
end else begin
// 其他情况下使用完整的波特率分频系数
rx_div_cnt <= uart_div[15:0];
end
end
end
// 接收时钟计数器过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将时钟计数器清零
rx_clk_cnt <= 16'h0;
end else if (rx_start == 1'b1) begin
// 如果正在接收
// 计数达到分频值
if (rx_clk_cnt >= rx_div_cnt) begin
// 将时钟计数器清零
rx_clk_cnt <= 16'h0;
end else begin
// 否则时钟计数器加1
rx_clk_cnt <= rx_clk_cnt + 16'h1;
end
end else begin
// 如果没有在接收,则将时钟计数器清零
rx_clk_cnt <= 16'h0;
end
end
// 接收时钟边沿计数器过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将时钟边沿计数器清零,并清除边沿电平信号
rx_clk_edge_cnt <= 4'h0;
rx_clk_edge_level <= 1'b0;
end else if (rx_start == 1'b1) begin
// 如果正在接收
// 计数达到分频值
if (rx_clk_cnt == rx_div_cnt) begin
// 时钟沿个数达到最大值(9个边沿,对应起始位+8个数据位+停止位)
if (rx_clk_edge_cnt == 4'd9) begin
// 将时钟边沿计数器清零,并清除边沿电平信号
rx_clk_edge_cnt <= 4'h0;
rx_clk_edge_level <= 1'b0;
end else begin
// 时钟沿个数加1
rx_clk_edge_cnt <= rx_clk_edge_cnt + 4'h1;
// 产生上升沿脉冲
rx_clk_edge_level <= 1'b1;
end
end else begin
// 没有达到分频值时清除边沿电平信号
rx_clk_edge_level <= 1'b0;
end
end else begin
// 没有在接收时将计数器清零并清除边沿电平信号
rx_clk_edge_cnt <= 4'h0;
rx_clk_edge_level <= 1'b0;
end
end
// 接收数据位过程块
always @ (posedge clk or negedge rst_n) begin
// 异步复位处理
if (!rst_n) begin
// 复位时将接收数据清零,并清除接收结束标志
rx_data <= 8'h0;
rx_over <= 1'b0;
end else begin
// 如果正在接收
if (rx_start == 1'b1) begin
// 在时钟边沿处采样数据
if (rx_clk_edge_level == 1'b1) begin
// 根据时钟边沿计数确定当前处理哪一位
case (rx_clk_edge_cnt)
// 起始位(第1个边沿)
1: begin
// 起始位不需要处理,只是用来同步
end
// 第1位数据位(第2个边沿)
2: begin
// 如果接收引脚为高电平
if (rx_pin) begin
// 将最高位设为1
rx_data <= 8'h80;
end else begin
// 否则将最高位设为0
rx_data <= 8'h0;
end
end
// 剩余数据位(第3到第9个边沿)
3, 4, 5, 6, 7, 8, 9: begin
// 将接收引脚的数据移入rx_data寄存器(右移)
rx_data <= {rx_pin, rx_data[7:1]};
// 最后一位接收完成,置位接收完成标志
if (rx_clk_edge_cnt == 4'h9) begin
rx_over <= 1'b1;
end
end
endcase
end
end else begin
// 没有在接收时将接收数据清零,并清除接收结束标志
rx_data <= 8'h0;
rx_over <= 1'b0;
end
end
end
// 模块结束
endmodule

浙公网安备 33010602011771号