数逻实验五.综合
UART发送模块 (uart_send
)
模块功能
该模块用于将8位数据通过UART协议发送出去。它包括状态机、分频器和输出逻辑,以确保数据在正确的时序下发送。
主要实现逻辑
- 分频器:将系统时钟分频到波特率时钟。
- 状态机:根据当前状态和输入信号,决定下一个状态。
- 输出逻辑:根据当前状态,生成输出信号
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按下时输出开关状态。它还包括去抖动处理,以确保按钮输入的稳定性。
主要实现逻辑
- 去抖动处理:通过计数器实现按钮的去抖动。
- 状态机:根据按钮的状态,决定状态转移。
- 输出逻辑:在按钮按下时,输出开关状态。
重点和难点
- 去抖动处理:确保按钮输入的稳定性,避免误触发。
- 状态机:正确处理按钮的按下和释放状态。
输入输出信号
- 输入信号:
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码转换为十六进制数,并显示在数码管上。模块还包括刷新控制逻辑,以确保数码管的显示内容能够动态更新。
主要实现逻辑
- UART接收模块:接收UART数据,并生成数据有效信号。
- ASCII转十六进制模块:将接收到的ASCII码转换为十六进制数。
- 七段数码管解码模块:将十六进制数转换为七段数码管的段码。
- 显示缓冲区更新逻辑:根据接收到的数据更新显示缓冲区。
- 刷新控制逻辑:控制数码管的刷新频率,确保每个数码管都能显示正确的内容。
重点和难点
- 显示缓冲区更新:确保接收到的数据能够正确更新到显示缓冲区。
- 刷新控制逻辑:确保数码管的显示内容能够动态更新,避免闪烁。
输入输出信号
- 输入信号:
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状态机)通常由以下三个部分组成:
- 状态寄存器:用于存储当前状态。
- 状态转移逻辑:根据当前状态和输入信号计算下一个状态。
- 输出逻辑:根据当前状态生成输出信号。
下面是对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接收模块中,这种结构使得状态转移和数据接收逻辑清晰明了,便于调试和扩展。