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

posted @ 2025-10-26 18:49  炽杨  阅读(10)  评论(0)    收藏  举报