Exlpamtion of Code
Exlpamtion of Code
✅ MODULE 1: frame_detector.v
📘 Purpose:
Detects when a new valid UART frame starts by looking for a special byte (FRAME_ID =
0xA5). It then sends a one-cycle pulse (frame_start) to trigger the command buffer.
module frame_detector (
output reg frame_start // 1-bit output; pulses high when frame ID is detected
);
📌 Signal Details:
frame_start 1-bit output Pulses high for 1 cycle when frame ID is detected
parameter FRAME_ID = 8'hA5;
Defines the expected frame identifier byte. If rx_data equals this, then it's the start of a
command.
reg rx_data_valid_d;
🧠 Holds the previous value of rx_data_valid — used for rising edge detection.
Creates a wire that becomes high (1) only when rx_data_valid just transitioned from 0 → 1.
📘 Why? This ensures that you only act once per new byte, even if rx_data_valid stays high
for multiple cycles.
if (rst) begin
rx_data_valid_d <= 0;
end
if (rst) begin
frame_start <= 0;
end else begin
frame_start <= 1;
frame_start <= 0;
end
🔍 Logic Breakdown:
• It tells the buffer: “Start storing the next 4 bytes coming from UART!”
✅ MODULE 2: command_buffer.v
📘 Purpose:
After frame_start is detected by the frame_detector, this module collects the next 4 bytes
received from the UART:
1. Command MSB
2. Command LSB
3. Data MSB
4. Data LSB
Once all 4 are received, it sets buffer_full = 1 to tell the FSM to start processing the
command.
🧠 Signal-by-Signal Definitions:
module command_buffer (
);
frame_start 1-bit input Comes from frame_detector; triggers new buffer cycle
🧮 Byte counter: Tracks how many of the 4 bytes we’ve received (0 → 3). Needs 3 bits
because max value = 3.
FILLING = 2'b01,
FULL = 2'b10,
PROCESSING = 2'b11;
📖 State Meaning:
State Description
if (rst) begin
byte_cnt <= 0;
buffer_full <= 0;
end
• byte_cnt set to 0
• buffer_full lowered
• FSM goes to IDLE
else begin
case (process_state)
📘 Case: IDLE
if (frame_start) begin
byte_cnt <= 0;
buffer_full <= 0;
end
💡 Logic:
📘 Case: FILLING
if (rx_data_valid) begin
case (byte_cnt)
0: begin
end
1: begin
cmd_lsb <= rx_data;
end
2: begin
end
3: begin
buffer_full <= 1;
end
endcase
📌 Summary:
if (frame_start) begin
byte_cnt <= 0;
end
📘 Case: FULL
✅ Transition to PROCESSING state, indicating FSM should now use the data.
📘 Case: PROCESSING
buffer_full <= 0;
✅ MODULE 3: command_fsm.v
📘 Purpose:
This is the command decoder and executor. Once command_buffer sets buffer_full = 1, this
module reads the command and data, decides if it’s a read or write, and:
• For READ: fetches data and sends it back using UART transmitter (tx_data, tx_start).
module command_fsm (
);
🧠 Internal Registers
EXECUTE = 3'b001,
WAIT_READ = 3'b010,
PREPARE_HIGH = 3'b011,
SEND_HIGH = 3'b100,
WAIT_TX1 = 3'b101,
PREPARE_LOW = 3'b110,
SEND_LOW = 3'b111,
WAIT_TX2 = 4'b1000;
📘 These are states in the FSM. They define the exact steps in the command flow:
State Meaning
reg buffer_full_d;
Stores register output temporarily so it won’t change while sending over UART.
buffer_full_d <= 0;
end
if (rst) begin
if (buffer_full_rising) begin
end
end
if (state == PREPARE_HIGH)
if (state == PREPARE_LOW)
end
📘 Logic:
• If buffer becomes full, extract the lower 4 bits of command as register address.
...
end
end
next_state = state;
write_en = 0;
read_en = 0;
tx_start = 0;
📘 For every state, we decide:
🧩 IDLE → EXECUTE
if (buffer_full_rising)
next_state = EXECUTE;
🧩 EXECUTE
write_en = 1;
next_state = IDLE;
read_en = 1;
next_state = WAIT_READ;
end else
next_state = IDLE;
Decodes command:
🧩 WAIT_READ → PREPARE_HIGH
🧩 PREPARE_HIGH → SEND_HIGH
🧩 SEND_HIGH
next_state = WAIT_TX1;
🧩 WAIT_TX1
🧩 PREPARE_LOW → SEND_LOW
🧩 SEND_LOW
🧩 WAIT_TX2 → IDLE
🔗 Module Connections
• Outputs to:
✅ MODULE 4: register_file.v
📘 Purpose:
• Reading 16-bit data out of any register It’s controlled by read_en, write_en, and the
addr signal.
module register_file (
);
🧠 Internal Registers
integer i;
The register file updates on the rising edge of the clock or during reset.
🧩 RESET LOGIC
if (rst) begin
end
else begin
if (write_en) begin
end
📘 Logic:
• If write_en is high,
o Example: if addr = 4'b0011 and data_in = 0xDEAD, this line writes 0xDEAD
into regs[3]
🧩 READ Operation
if (read_en) begin
end
end
📘 Logic:
• If read_en is high:
o This makes the value available to command_fsm, which will then send it via
UART
🧩 Debug Monitor
🧪 This block triggers whenever data_out changes and prints the new value. Very helpful
for:
🔗 Module Connections
🔁 Example Flow:
▶ Write:
• FSM issues:
o reg_addr = 4'h2
o reg_data_in = 0xBEEF
o write_en = 1
▶ Read:
• FSM issues:
o reg_addr = 4'h2
o read_en = 1
Module Role
✅ MODULE 1: UART_TX.v
🔷 Purpose:
• Breaks it into a UART frame: start bit + data bits + parity + stop bit.
output TX_data_out, // The UART serial output (bit stream goes out here)
This logic detects when TX_start goes from 0 → 1. We only want to start a new
transmission on the rising edge of this signal.
if (rst)
TX_start_d <= 0;
else
end
Stores TX_start every clock to compare with current value (for edge detection above).
if (TX_start_rising)
end
This prints when a transmission is starting, so you can track what's being sent during
simulation.
if (rst)
end
Submodule Connections
• Parity bit
Monitoring TX_busy
reg TX_busy_d;
always @(posedge clk) begin
end
This module:
Purpose:
This module loads an 8-bit parallel input (piso_in) and shifts it out serially, one bit at a
time, from LSB (bit 0) to MSB (bit 7) — which is how UART sends data.
Internal Storage:
• Initially empty.
• When shift = 1, it right-shifts by 1 (bit 1 → bit 0, etc.), and pads with 0 on the left.
if (rst)
else if (load)
else if (shift)
Serial Output:
Debug Logging:
if (load)
else if (shift)
$display("[PISO] Shifting, current reg: %b, output bit: %b", shift_reg, shift_reg[0]);
✅ MODULE 3: TX_PARITY.v
Purpose:
• Even parity means: total number of 1’s (including parity) should be even.
Logic:
if (rst)
else if (parity_load)
Debug Message:
✅ MODULE 4: TX_MUX.v
Purpose:
This is a 4-to-1 multiplexer that decides what bit goes to the UART TX line (tx_out):
• parity bit
Logic:
case (select)
endcase
end
Purpose:
Each part lasts for 1 or more clock cycles. This FSM generates:
• parity_load
reg count_en;
Counter Logic:
if (count_en)
else
count <= 0;
FSM STATES:
START = 3'b001,
DATA = 3'b010,
PARITY = 3'b011,
STOP = 3'b100;
State Register:
case (present_state)
endcase
load = 0;
shift = 0;
parity_load = 0;
count_en = 0;
case (present_state)
IDLE: begin
TX_busy = 0;
end
START: begin
load = 1;
parity_load = 1;
DATA: begin
shift = 1;
count_en = 1;
end
endcase
Debug Display:
Final Notes:
[Start Bit (0)] → [Data Bits (LSB first)] → [Even Parity Bit] → [Stop Bit (1)]
Each is sent over multiple clock cycles, managed by the FSM, and output via tx_out.