数逻实验五.综合

UART发送模块 (uart_send)

模块功能

该模块用于将8位数据通过UART协议发送出去。它包括状态机、分频器和输出逻辑,以确保数据在正确的时序下发送。

主要实现逻辑

  1. 分频器:将系统时钟分频到波特率时钟。
  2. 状态机:根据当前状态和输入信号,决定下一个状态。
  3. 输出逻辑:根据当前状态,生成输出信号 dout

重点和难点

  • 分频器:确保分频器能够准确生成波特率时钟。
  • 状态机:正确处理状态转移,确保数据在正确的时序下发送。

输入输出信号

  • 输入信号
    • clk: 系统时钟。
    • rst: 复位信号。
    • valid: 数据有效信号。
    • data: 要发送的8位数据。
  • 输出信号
    • dout: 连接到USB UART TX引脚的输出信号。

关键代码

// 分频器产生分频时钟
always @(posedge clk or posedge rst) begin
    if (rst) begin
        clk_div <= 0;
    end else begin
        if (clk_div >= DIVISOR -1 ) begin
            clk_div <= 0;
        end else begin
            clk_div <= clk_div + 1;
        end
    end
end

assign div_check = (clk_div == DIVISOR - 1);

// 次态迁移到现态
always @(posedge clk or posedge rst) begin
    if(rst)  current_state <= IDLE;
    else     current_state <= next_state;
end

// 状态转移条件判断
always @(*) begin
    case (current_state)
        IDLE: if(valid) next_state = START;
              else    next_state = IDLE;
        START: if(div_check)   next_state = DATA;
              else    next_state = START;
        DATA: if(bit_cnt == 8)   next_state = STOP;
              else    next_state = DATA;
        STOP:if(div_check) next_state = IDLE;
              else    next_state = STOP;  
        default :     next_state = IDLE;
    endcase
end

// 输出逻辑
always @(posedge clk or posedge rst) begin
    if (rst) begin
        dout <= 1; 
        bit_cnt <= 0;
        data_send <= 0;
    end else  begin
        case (current_state)
            IDLE: begin
                if (valid) begin 
                    data_send <= data; 
                end
            end
            START: begin
                if (div_check) begin
                    dout <= 0; 
                    bit_cnt <= 0;
                end
            end
            DATA: begin
                if (div_check) begin
                    dout <= data_send[bit_cnt]; // 发送数据位
                    bit_cnt <= bit_cnt + 1;
                end
            end
            STOP: begin
                if (div_check) begin
                    dout <= 1; 
                end
            end
            default: ;
        endcase
    end
end

开关读取模块 (switch_reader)

模块功能

该模块用于读取8个开关的状态,并在按钮S3按下时输出开关状态。它还包括去抖动处理,以确保按钮输入的稳定性。

主要实现逻辑

  1. 去抖动处理:通过计数器实现按钮的去抖动。
  2. 状态机:根据按钮的状态,决定状态转移。
  3. 输出逻辑:在按钮按下时,输出开关状态。

重点和难点

  • 去抖动处理:确保按钮输入的稳定性,避免误触发。
  • 状态机:正确处理按钮的按下和释放状态。

输入输出信号

  • 输入信号
    • clk: 系统时钟。
    • rst: 复位信号。
    • sw: 8个开关的输入。
    • button_s3: 按钮S3的输入。
  • 输出信号
    • output_data: 8位输出数据,表示开关状态。

关键代码

// 检测按钮 S3 的上升沿和下降沿
always @(posedge clk or posedge rst) begin
    if (rst) begin
        button_s3_reg <= 1'b0;
    end else begin
        button_s3_reg <= button_s3;
    end
end

wire button_s3_posedge = button_s3 & ~button_s3_reg;
wire button_s3_negedge = ~button_s3 & button_s3_reg;

// 状态机逻辑
always @(posedge clk or posedge rst) begin
    if (rst) begin
        state <= IDLE;
    end else begin
        state <= next_state;
    end
end

// 状态转移逻辑
always @(*) begin
    next_state = state;
    case (state)
        IDLE: begin
            if (button_s3_posedge) begin
                next_state = DEBOUNCING_PRESS;
            end
        end
        DEBOUNCING_PRESS: begin
            if (dismiss_counter >= DEBOUNCE_TIME) begin
                next_state = TRIGGERED;
            end
        end
        TRIGGERED: begin
            if (button_s3_negedge) begin
                next_state = DEBOUNCING_RELEASE;
            end
        end
        DEBOUNCING_RELEASE: begin
            if (dismiss_counter >= DEBOUNCE_TIME) begin
                next_state = IDLE;
            end
        end
    endcase
end

// 去抖动计数器
always @(posedge clk or posedge rst) begin
    if (rst) begin
        dismiss_counter <= 0;
    end else if (state == DEBOUNCING_PRESS || state == DEBOUNCING_RELEASE) begin
        dismiss_counter <= dismiss_counter + 1;
    end else begin
        dismiss_counter <= 0;
    end
end

// 读取开关数据
always @(posedge clk or posedge rst) begin
    if (rst) begin
        output_data <= 8'b00000000;
    end else if (state == TRIGGERED) begin
        output_data <= sw;
    end
end

ASCII 转十六进制模块 (ascii_to_hex)

模块功能

该模块用于将输入的8位ASCII码转换为4位十六进制数。

主要实现逻辑

通过 case 语句将ASCII码映射到对应的十六进制数。

重点和难点

  • ASCII码映射:确保每个ASCII码都能正确映射到对应的十六进制数。

输入输出信号

  • 输入信号
    • ascii_in: 输入的8位ASCII码。
  • 输出信号
    • hex_out: 输出的4位十六进制数。

关键代码

always @(*) begin
    case (ascii_in)
        // 数字 '0' 到 '9'
        8'h30: hex_out = 4'h0;
        8'h31: hex_out = 4'h1;
        8'h32: hex_out = 4'h2;
        8'h33: hex_out = 4'h3;
        8'h34: hex_out = 4'h4;
        8'h35: hex_out = 4'h5;
        8'h36: hex_out = 4'h6;
        8'h37: hex_out = 4'h7;
        8'h38: hex_out = 4'h8;
        8'h39: hex_out = 4'h9;
      
        // 大写字母 'A' 到 'F'
        8'h41: hex_out = 4'hA;
        8'h42: hex_out = 4'hB;
        8'h43: hex_out = 4'hC;
        8'h44: hex_out = 4'hD;
        8'h45: hex_out = 4'hE;
        8'h46: hex_out = 4'hF;
      
        // 小写字母 'a' 到 'f'
        8'h61: hex_out = 4'hA;
        8'h62: hex_out = 4'hB;
        8'h63: hex_out = 4'hC;
        8'h64: hex_out = 4'hD;
        8'h65: hex_out = 4'hE;
        8'h66: hex_out = 4'hF;
      
        // 默认情况,返回 0
        default: hex_out = 4'h0;
    endcase
end

七段数码管解码模块 (segment_decoder)

模块功能

该模块用于将4位十六进制数转换为8位段码,以控制七段数码管的显示。

主要实现逻辑

通过 case 语句将十六进制数映射到对应的段码。

重点和难点

  • 段码映射:确保每个十六进制数都能正确映射到对应的段码。

输入输出信号

  • 输入信号
    • hex: 输入的4位十六进制数。
  • 输出信号
    • segment: 输出的8位段码,控制七段数码管。

关键代码

always @(*) begin
    case (hex)
        4'h0: segment = 8'b00000011;  // 0
        4'h1: segment = 8'b10011111;  // 1
        4'h2: segment = 8'b00100101;  // 2
        4'h3: segment = 8'b00001101;  // 3
        4'h4: segment = 8'b10011001;  // 4
        4'h5: segment = 8'b01001001;  // 5
        4'h6: segment = 8'b01000001;  // 6
        4'h7: segment = 8'b00011111;  // 7
        4'h8: segment = 8'b00000001;  // 8
        4'h9: segment = 8'b00011001;  // 9
        4'hA: segment = 8'b00010001;  // A
        4'hB: segment = 8'b11000001;  // b
        4'hC: segment = 8'b01100011;  // C
        4'hD: segment = 8'b10000101;  // d
        4'hE: segment = 8'b01100001;  // E
        4'hF: segment = 8'b01110001;  // F
        default: segment = 8'b11111111;  // 空白
    endcase
end

LED 显示控制模块 (led_display_ctrl)

模块功能

该模块用于控制8个七段数码管的显示。它通过UART接收数据,将接收到的ASCII码转换为十六进制数,并显示在数码管上。模块还包括刷新控制逻辑,以确保数码管的显示内容能够动态更新。

主要实现逻辑

  1. UART接收模块:接收UART数据,并生成数据有效信号。
  2. ASCII转十六进制模块:将接收到的ASCII码转换为十六进制数。
  3. 七段数码管解码模块:将十六进制数转换为七段数码管的段码。
  4. 显示缓冲区更新逻辑:根据接收到的数据更新显示缓冲区。
  5. 刷新控制逻辑:控制数码管的刷新频率,确保每个数码管都能显示正确的内容。

重点和难点

  • 显示缓冲区更新:确保接收到的数据能够正确更新到显示缓冲区。
  • 刷新控制逻辑:确保数码管的显示内容能够动态更新,避免闪烁。

输入输出信号

  • 输入信号
    • clk: 系统时钟。
    • rst: 复位信号。
    • din: 连接到USB UART接收端的输入信号。
  • 输出信号
    • A0_7: 控制哪个数码管显示。
    • CA_G: 控制数码管的显示内容。

关键代码

// UART 接收模块
uart_recv uart_recv_inst (
    .clk(clk),
    .rst(rst),
    .din(din),
    .valid(valid),
    .data(uart_data)
);
// ASCII 转十六进制
wire [3:0] hex_data0, hex_data1, hex_data2, hex_data3;
wire [3:0] hex_data4, hex_data5, hex_data6, hex_data7;
ascii_to_hex ascii_to_hex_inst0 (
    .ascii_in(display_buffer0),
    .hex_out(hex_data0)
);
......省略部分代码

// 7 段数码管解码模块
wire [7:0] segment0, segment1, segment2, segment3;
wire [7:0] segment4, segment5, segment6, segment7;

segment_decoder segment_decoder_inst0 (
    .hex(hex_data0),
    .segment(segment0)
);
......省略部分代码

// 显示缓冲区更新逻辑
always @(posedge clk or posedge rst) begin
    if (rst) begin
        display_index <= 0;
        display_buffer0 <= 8'h00;
        ......省略部分代码
        display_buffer7 <= 8'h00;
    end else if (valid) begin
        case (display_index)
            3'd0: display_buffer0 <= uart_data;
            3'd1: display_buffer1 <= uart_data;
           ......省略部分代码
            3'd7: display_buffer7 <= uart_data;
        endcase
 //            display_index <= display_index + 1;
//            if (display_index == 7) begin
//                display_index <= 0;
//            end
            // 在下一个时钟周期更新 display_index
        display_index <= (display_index == 7) ? 0 : display_index + 1;
    end
end

// 刷新控制逻辑
reg [2:0] digit_sel;
reg [29:0] refresh_counter;
reg [29:0] refresh_max_count = 30'd19999; // 2ms

always @(posedge clk or posedge rst) begin
    if (rst) begin
        refresh_counter <= 30'd0;
        digit_sel <= 3'd0;
    end else if (refresh_counter == refresh_max_count) begin
        refresh_counter <= 30'd0;
        digit_sel <= digit_sel + 1;
    end else begin
        refresh_counter <= refresh_counter + 1;
    end
end

// 根据当前选择的数码管位置显示内容
always @(*) begin
    case (digit_sel)
        3'd0: begin A0_7 = 8'b11111110; CA_G = (display_buffer0 == 8'h00) ? 8'b11111111 : segment0; end
        3'd1: begin A0_7 = 8'b11111101; CA_G = (display_buffer1 == 8'h00) ? 8'b11111111 : segment1; end
        3'd2: begin A0_7 = 8'b11111011; CA_G = (display_buffer2 == 8'h00) ? 8'b11111111 : segment2; end
        3'd3: begin A0_7 = 8'b11110111; CA_G = (display_buffer3 == 8'h00) ? 8'b11111111 : segment3; end
        3'd4: begin A0_7 = 8'b11101111; CA_G = (display_buffer4 == 8'h00) ? 8'b11111111 : segment4; end
        3'd5: begin A0_7 = 8'b11011111; CA_G = (display_buffer5 == 8'h00) ? 8'b11111111 : segment5; end
        3'd6: begin A0_7 = 8'b10111111; CA_G = (display_buffer6 == 8'h00) ? 8'b11111111 : segment6; end
        3'd7: begin A0_7 = 8'b01111111; CA_G = (display_buffer7 == 8'h00) ? 8'b11111111 : segment7; end
        default: begin A0_7 = 8'b11111111; CA_G = 8'b11111111; end
    endcase
end

总结

led_display_ctrl 模块通过集成多个子模块,实现了从UART数据接收、ASCII码转换、七段数码管解码到最终显示的全过程。模块的关键在于显示缓冲区的更新和刷新控制逻辑,确保数码管能够正确显示接收到的数据。

总结

以上是对各个子模块的详细说明,包括模块功能、主要实现逻辑、重点和难点、输入输出信号以及关键代码。每个模块都有其特定的功能和实现方式,通过这些模块的组合,可以实现更复杂的功能。

三段式状态机代码解释

三段式状态机(也称为Moore状态机)通常由以下三个部分组成:

  1. 状态寄存器:用于存储当前状态。
  2. 状态转移逻辑:根据当前状态和输入信号计算下一个状态。
  3. 输出逻辑:根据当前状态生成输出信号。

下面是对UART接收模块的三段式状态机代码的详细解释:

1. 状态寄存器

// 状态寄存器
always @(posedge clk or posedge rst) begin
    if (rst) begin
        current_state <= IDLE;
    end else begin
        current_state <= next_state;
    end
end
  • 功能:在时钟上升沿或复位信号有效时,更新当前状态。
  • 解释
    • 当复位信号 rst 为高时,current_state 被复位为 IDLE 状态。
    • 在正常操作中,current_state 在每个时钟上升沿更新为 next_state

2. 状态转移逻辑

// 状态转移逻辑
always @(*) begin
    case (current_state)
        IDLE:   next_state = (din == 0) ? START : IDLE;
        START:  next_state = (cnt_half) ? DATA : START;
        DATA:   next_state = (bit_cnt == 8 && cnt_half) ? STOP : DATA;
        STOP:   next_state = (cnt_half) ? IDLE : STOP;
        default: next_state = IDLE;
    endcase
end
  • 功能:根据当前状态和输入信号计算下一个状态。
  • 解释
    • IDLE 状态:如果输入信号 din 为低电平(表示起始位),则转移到 START 状态;否则保持在 IDLE 状态。
    • START 状态:如果分频信号 cnt_half 为高(表示到达位的中间位置),则转移到 DATA 状态;否则保持在 START 状态。
    • DATA 状态:如果位计数器 bit_cnt 达到8且 cnt_half 为高,则转移到 STOP 状态;否则保持在 DATA 状态。
    • STOP 状态:如果 cnt_half 为高,则转移到 IDLE 状态;否则保持在 STOP 状态。
    • default 状态:默认情况下,转移到 IDLE 状态。

3. 输出逻辑和数据接收

// 输出逻辑和数据接收
always @(posedge clk or posedge rst) begin
    if (rst) begin
        valid <= 0;
        data <= 0;
        data_reg <= 0;
        bit_cnt <= 0;
    end else begin
        valid <= 0; // valid 信号默认拉低

        case (current_state)
            IDLE: begin
                bit_cnt <= 0;
            end
            START: begin
                if (cnt_half) begin
                    bit_cnt <= 0;
                end
            end
            DATA: begin
                if (cnt_half) begin
                    data_reg[bit_cnt] <= din; // 中间时刻采样数据位
                    bit_cnt <= bit_cnt + 1;
                end
            end
            STOP: begin
                if (cnt_half) begin
                    data <= data_reg; // 将接收的数据输出
                    valid <= 1;       // 拉高 valid 信号一个时钟周期
                end
            end
        endcase
    end
end
  • 功能:根据当前状态执行相应的操作,包括数据采样、位计数和输出信号的生成。
  • 解释
    • IDLE 状态:复位位计数器 bit_cnt
    • START 状态:在中间位置时,复位位计数器 bit_cnt
    • DATA 状态:在中间位置时,采样数据位并存储到 data_reg 中,同时递增位计数器 bit_cnt
    • STOP 状态:在中间位置时,将接收到的数据输出到 data,并拉高 valid 信号一个时钟周期。

总结

三段式状态机的代码结构清晰,易于理解和维护。通过状态寄存器、状态转移逻辑和输出逻辑的分离,可以有效地实现状态机的功能。在UART接收模块中,这种结构使得状态转移和数据接收逻辑清晰明了,便于调试和扩展。

千里之行,始于足下