An AES-128 encrypted communication system implemented on two Xilinx Basys3 FPGA boards. Board A accepts keyboard input, encrypts it using AES-128, and transmits the ciphertext over UART. Board B receives the data, decrypts it, and displays the plaintext on a VGA monitor.
- System Overview
- Architecture
- Directory Structure
- Hardware Requirements
- Module Documentation
- AES-128 Cryptographic Details
- UART Communication Protocol
- Pin Assignments
- Timing Specifications
- Build & Deployment
- Usage
- Key Parameters Reference
+--------------------------------------+ UART (9600 baud) +--------------------------------------+
| BOARD A | ----------------------> | BOARD B |
| (Basys3 - Encryption) | TX -----> RX | (Basys3 - Decryption) |
| | | |
| PS/2 Keyboard | | VGA Monitor |
| | | | | |
| v | | | |
| Scancode -> ASCII | | UART RX -> AES Decrypt --> VGA |
| | | | |
| v | | |
| AES-128 Encrypt | | |
| | | | |
| v | | |
| UART TX ---------------------------------------------------------> |
+--------------------------------------+ +--------------------------------------+
The user types a 16-character message on a PS/2 keyboard connected to Board A. After all 16 characters are entered, the system encrypts the 128-bit block with AES-128 and streams it via UART to Board B. Board B decrypts the ciphertext and renders the recovered plaintext on a VGA display.
Encryption Path (Board A)
PS/2 Keyboard
| (scancodes)
v
ps2_keyboard.vhd <-- Low-level PS/2 protocol handler
| (8-bit scancode)
v
ps2_keyboard_to_ascii.vhd <-- Scancode -> ASCII, accumulates 16 characters (128 bits)
| (128-bit plaintext)
v
encryption.vhd <-- AES-128: 1 initial AddKey + 9 MainRounds + 1 FinalRound
| (128-bit ciphertext)
v
UART_TX3.vhd <-- Serial transmitter @ 9600 baud
| (TX pin)
v
-------------------------------------> UART cable ---------------------------------------->
Decryption Path (Board B)
-------------------------------------> UART cable ---------------------------------------->
| (RX pin)
v
UART_RX3.vhd <-- Serial receiver @ 9600 baud, captures 128 bits
| (128-bit ciphertext)
v
Top_decryption.vhd <-- AES-128 inverse: 1 initial AddKey + 9 DecRounds + 1 FinalDecRound
| (128-bit plaintext)
v
VGA.vhd <-- Converts 128 bits to 16 ASCII characters
|
+-- Clock_Divider.vhd <-- Generates pixel clock
+-- Font_Rom.vhd <-- 8x16 character glyphs
+-- Pixel_On_Text.vhd <-- Rasterises characters onto VGA framebuffer
| (RGB + Sync signals)
v
VGA Monitor
FPGA-Secure-Communication-System/
|
+-- README.md
|
+-- Project/
|
+-- Communication/ # Standalone UART and debouncing modules
| +-- UART.vhd # Wrapper combining TX and RX
| +-- UART_TX3.vhd # UART transmitter (16-bit data)
| +-- UART_RX3.vhd # UART receiver (8-bit data)
| +-- UART_controller.vhd # Debounced transmission controller
| +-- Button_debounce.vhd # 4-stage switch debounce filter
|
+-- keyboard/ # Standalone PS/2 keyboard modules
| +-- ps2_keyboard.vhd # PS/2 protocol handler
| +-- ps2_keyboard_to_ascii.vhd # Scancode-to-ASCII converter
|
+-- project_A/ # Vivado project - Encryption board
| +-- project_A/
| +-- project_A.srcs/
| +-- sources_1/new/
| | +-- TOP.vhd # Top-level (Board A)
| | +-- encryption.vhd # AES-128 encryption top
| | +-- mainRound.vhd # Rounds 1-9
| | +-- finalRound.vhd # Round 10 (no MixColumns)
| | +-- addkey.vhd # AddRoundKey (XOR with key)
| | +-- subbyte.vhd # SubBytes (16x S-box)
| | +-- shiftrow.vhd # ShiftRows permutation
| | +-- mixcolumn.vhd # MixColumns (GF(2^8))
| | +-- sbox.vhd # 256-entry forward S-box
| | +-- UART_TX3.vhd # UART transmitter
| | +-- ps2_keyboard.vhd # PS/2 handler
| | +-- ps2_keyboard_to_ascii.vhd # ASCII converter
| +-- constrs_1/new/
| +-- BoardA.xdc # Board A pin constraints
|
+-- project_B/ # Vivado project - Decryption board
+-- project_b/
+-- project_b.srcs/
| +-- sources_1/
| +-- new/
| | +-- TOP.vhd # Top-level (Board B)
| +-- imports/Downloads/
| +-- decryption.vhd # Single decryption round
| +-- Top_decryption.vhd # AES-128 decryption top
| +-- inv_sbox.vhd # 256-entry inverse S-box
| +-- inv_shift_rows.vhd # Inverse ShiftRows
| +-- inv_mix_columns.vhd # Inverse MixColumns (GF(2^8))
| +-- inv_sub_bytes.vhd # InvSubBytes (16x inv S-box)
| +-- VGA.vhd # VGA display controller
| +-- Clock_Divider.vhd # Pixel clock generator
| +-- Font_Rom.vhd # Character glyph ROM
| +-- Pixel_On_Text.vhd # Text rasteriser
| +-- wrapper.vhd # Display subsystem wrapper
| +-- UART_RX3.vhd # UART receiver
| +-- twoMultip.vhd # GF multiply-by-2 helper
| +-- mixMultip.vhd # GF multiplication helper
| +-- commonPak.vhd # Shared type/constant package
+-- constrs_1/imports/Downloads/
+-- Basys3_Master.xdc # Board B pin constraints
| Item | Qty | Notes |
|---|---|---|
| Xilinx Basys3 FPGA Board | 2 | Artix-7 XC7A35T |
| PS/2 Keyboard | 1 | Connected to Board A |
| VGA Monitor | 1 | Connected to Board B |
| UART Jumper Wires | 1 set | TX (Board A) -> RX (Board B) + shared GND |
| USB Programming Cables | 2 | One per board |
| Xilinx Vivado Design Suite | — | 2019.x or later recommended |
UART Wiring:
Board A J1 (TX) ------------------> Board B RX pin
Board A GND ------------------> Board B GND
Top-level integrator for the encryption board.
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
clk |
in | 1 | 100 MHz system clock |
ps2_clk |
in | 1 | PS/2 clock from keyboard |
ps2_data |
in | 1 | PS/2 data from keyboard |
Enable |
in | 1 | Initiates UART transmission (slide switch SW0) |
TX |
out | 1 | UART serial output |
Behavior: Waits until the keyboard module signals that 16 characters have been entered (isDone). The 128-bit ASCII plaintext is then passed through the AES pipeline. When Enable is asserted, the resulting 128-bit ciphertext is serialised by UART_TX3 and driven on TX.
AES-128 encryption pipeline (fully combinatorial).
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
input |
in | 128 | Plaintext block |
output |
out | 128 | Ciphertext block |
Round Structure
input
|
v
AddKey (key[1407:1280]) <-- Round 0 (initial whitening)
|
v
MainRound (key[1279:1152]) <-- Round 1
|
v
MainRound (key[1151:1024]) <-- Round 2
|
... (rounds 3 through 8 follow the same pattern)
|
v
MainRound (key[255:128]) <-- Round 9
|
v
FinalRound (key[127:0]) <-- Round 10 (no MixColumns)
|
v
output
The pre-computed 1408-bit expanded key is stored as a constant inside addkey.vhd.
Implements one full AES main round (used for rounds 1–9).
Generic Parameters
| Name | Description |
|---|---|
index |
MSB position of the 128-bit round-key slice within the 1408-bit expanded key |
Internal Cascade
input --> subbyte --> shiftrow --> mixcolumn --> addkey --> output
Implements AES round 10 (final round). Identical to mainRound except the MixColumns step is omitted, as required by FIPS 197.
Internal Cascade
input --> subbyte --> shiftrow --> addkey (index=127) --> output
AddRoundKey transformation. XORs the 128-bit state with a 128-bit slice of the pre-computed expanded key.
Generic Parameters
| Name | Description |
|---|---|
index |
MSB position; selects key(index downto index-127) from the 1408-bit constant |
Operation
output <= input XOR key(index downto index-127)
The expanded key constant is the standard AES key schedule applied to the hardcoded 128-bit master key.
SubBytes transformation. Instantiates 16 parallel sbox components, one per byte of the 128-bit state, so all 16 bytes are substituted simultaneously.
256-entry combinatorial lookup table implementing the AES forward S-box (FIPS 197, Section 5.1.1).
Sample Mappings
| Input | Output |
|---|---|
0x00 |
0x63 |
0x01 |
0x7C |
0x53 |
0xED |
0xFF |
0x16 |
ShiftRows transformation. Rotates each row of the 4x4 state matrix left by its row index.
| Row | Left Rotation |
|---|---|
| 0 | 0 bytes (unchanged) |
| 1 | 1 byte |
| 2 | 2 bytes |
| 3 | 3 bytes |
MixColumns transformation. Treats each 4-byte column as a polynomial over GF(2^8) and multiplies it by the fixed AES matrix:
[ 02 03 01 01 ]
[ 01 02 03 01 ]
[ 01 01 02 03 ]
[ 03 01 01 02 ]
All 4 columns are processed in parallel (fully combinatorial). Multiplication by 2 in GF(2^8) is implemented as a left shift with conditional XOR of 0x1B (the AES irreducible polynomial reduction).
Low-level PS/2 serial protocol handler.
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
clk |
in | 1 | 100 MHz system clock |
ps2_clk |
in | 1 | PS/2 clock (10–16 kHz, keyboard-generated) |
ps2_data |
in | 1 | PS/2 serial data line |
ps2_code |
out | 8 | Captured scancode byte |
ps2_code_new |
out | 1 | Pulses high for one cycle when ps2_code is valid |
Operation: Each PS/2 frame is captured on the falling edge of ps2_clk. After receiving the start bit and 8 data bits the module asserts ps2_code_new for one system clock cycle.
Scancode-to-ASCII translator and 16-character accumulator.
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
clk |
in | 1 | 100 MHz system clock |
ps2_clk |
in | 1 | PS/2 clock |
ps2_data |
in | 1 | PS/2 data |
ascii_code |
out | 128 | 16 ASCII characters packed into 128 bits |
State Machine
ready --> new_code --> translate --> output --> ready
Supported Input
- Letters A–Z with Shift and Caps Lock support
- Digits 0–9 and their shifted symbol variants
- Space, Backspace, Tab, Enter, Escape, Delete
- All printable ASCII symbols on a standard US keyboard layout
Characters are accumulated into a 128-bit shift register. After 16 characters are collected, isDone is asserted, signalling the top level to proceed with encryption.
UART transmitter supporting 16-bit parallel data input.
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
clk |
in | 1 | 100 MHz system clock |
enable |
in | 1 | Starts transmission on rising edge |
data_in |
in | 16 | Parallel data to transmit |
TX |
out | 1 | Serial output (idle HIGH) |
Generic Parameters
| Name | Default | Description |
|---|---|---|
BAUD_RATE |
10416 | Clock cycles per bit (yields 9600 baud @ 100 MHz) |
Frame Format: 1 start bit (LOW) + 16 data bits (LSB-first) + 1 stop bit (HIGH)
State Machine: IDLE -> START -> DATA -> STOP
Top-level integrator for the decryption board.
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
clk |
in | 1 | 100 MHz system clock |
reset |
in | 1 | Active-high system reset |
RX |
in | 1 | UART serial input |
R, G, B |
out | 4 each | VGA colour channels (4-bit each) |
hsync, vsync |
out | 1 each | VGA horizontal / vertical sync |
Data Flow: RX --> UART_RX3 --> Top_decryption --> VGA
AES-128 decryption pipeline (fully combinatorial, mirrors encryption in reverse).
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
ciphertext |
in | 128 | Encrypted block |
plaintext |
out | 128 | Recovered plaintext |
Round Key Ordering
Decryption applies round keys in reverse order compared to encryption:
| Step | Key Slice | is_last_round |
|---|---|---|
| 0 (initial AddKey) | key[127:0] |
— |
| Round 1 | key[255:128] |
0 |
| Round 2 | key[383:256] |
0 |
| ... | ... | ... |
| Round 9 | key[1279:1152] |
0 |
| Round 10 (final) | key[1407:1280] |
1 (skips InvMixColumns) |
Single inverse AES round.
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
input |
in | 128 | Round input |
round_key |
in | 128 | This round's key slice |
is_last_round |
in | 1 | When '1', InvMixColumns is bypassed |
output |
out | 128 | Round output |
Internal Cascade
input --> InvShiftRows --> InvSubBytes --> AddRoundKey --> InvMixColumns --> output
^
(bypassed when is_last_round = '1')
256-entry combinatorial lookup table implementing the AES inverse S-box. The exact bitwise inverse of sbox.vhd.
Inverse ShiftRows. Rotates each row right (inverse of encryption's left rotation):
| Row | Right Rotation |
|---|---|
| 0 | 0 bytes (unchanged) |
| 1 | 1 byte |
| 2 | 2 bytes |
| 3 | 3 bytes |
Inverse MixColumns. Multiplies each column by the AES inverse matrix over GF(2^8):
[ 0E 0B 0D 09 ]
[ 09 0E 0B 0D ]
[ 0D 09 0E 0B ]
[ 0B 0D 09 0E ]
Helper modules twoMultip.vhd and mixMultip.vhd provide the required GF(2^8) field multiplication primitives.
UART receiver that reassembles 8-bit bytes from the serial stream.
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
clk |
in | 1 | 100 MHz system clock |
RX |
in | 1 | Serial input (idle HIGH) |
DATA_OUT |
out | 8 | Received byte |
DONE |
out | 1 | Pulses high for one cycle when a byte is complete |
Generic Parameters
| Name | Default | Description |
|---|---|---|
BAUD_RATE |
20833 | Half-period cycles; samples at bit centre for noise immunity |
State Machine: IDLE -> START -> DATA -> STOP
The receiver detects the falling edge of the start bit, waits BAUD_RATE/2 cycles to reach bit centre, then samples each subsequent data bit every BAUD_RATE cycles.
VGA display controller. Converts the 128-bit plaintext into 16 ASCII characters and renders them on screen.
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
clk |
in | 1 | 100 MHz system clock |
reset |
in | 1 | Active-high reset |
output |
in | 128 | Decrypted plaintext block |
vgaRed, vgaGreen, vgaBlue |
out | 4 each | VGA colour channels |
hsync, vsync |
out | 1 each | VGA sync signals |
Display: 800x600 resolution. Text is rendered starting at pixel position (50, 50).
Character Mapping:
displayTextout(15) <- output(7 downto 0) -- first character received
displayTextout(14) <- output(15 downto 8)
...
displayTextout(0) <- output(127 downto 120) -- last character received
Divides the 100 MHz system clock down to the ~40 MHz pixel clock required for 800x600 @ 60 Hz VGA output.
Read-only memory storing 8x16 pixel glyph bitmaps for the printable ASCII character set. Addressed by character code and row index.
Determines whether a given (x, y) VGA pixel coordinate falls within a rendered character glyph. Reads the appropriate bitmap row from Font_Rom and outputs a single bit that drives the display colour logic.
Convenience wrapper that combines UART_TX3 and UART_RX3 into one component for designs requiring full-duplex UART.
Ports
| Name | Dir | Width | Description |
|---|---|---|---|
clk |
in | 1 | 100 MHz clock |
data_in |
in | 8 | Byte to transmit |
enable |
in | 1 | Trigger transmission |
RX |
in | 1 | Serial receive |
TX |
out | 1 | Serial transmit |
data_out |
out | 8 | Received byte |
done |
out | 1 | Byte received flag |
Combines Button_debounce and UART_TX3. Ensures a single noisy button press triggers exactly one clean UART transmission.
Hardware switch debouncer using a 4-stage cascaded flip-flop filter.
Generic Parameters
| Name | Default | Description |
|---|---|---|
COUNTER_SIZE |
10000 | Debounce window in clock cycles (= 100 µs @ 100 MHz) |
Stages:
- Two input flip-flops detect level transitions.
- A counter waits
COUNTER_SIZEcycles for the signal to settle. - An output flip-flop delivers a single, clean pulse on each confirmed press.
This implementation follows FIPS 197 – Advanced Encryption Standard (AES) with a 128-bit key and 128-bit block size (AES-128), providing 10 rounds of encryption and decryption.
| Round | Transformations Applied |
|---|---|
| 0 (initial) | AddRoundKey |
| 1–9 (main) | SubBytes → ShiftRows → MixColumns → AddRoundKey |
| 10 (final) | SubBytes → ShiftRows → AddRoundKey |
| Round | Transformations Applied |
|---|---|
| 0 (initial) | AddRoundKey |
| 1–9 | InvShiftRows → InvSubBytes → AddRoundKey → InvMixColumns |
| 10 (final) | InvShiftRows → InvSubBytes → AddRoundKey |
The 128-bit master key is expanded to 1408 bits (11 x 128-bit round keys) using the standard AES key expansion algorithm. The complete expanded key is pre-computed offline and stored as a constant in addkey.vhd. This makes the encryption and decryption pipelines entirely combinatorial with no runtime latency for key expansion.
- Fully combinatorial pipeline – all 10 rounds are purely combinational; output is valid within one clock period of stable input.
- Parallel S-box instantiation – 16 independent
sbox(orinv_sbox) instances substitute all 16 bytes simultaneously. - GF(2^8) arithmetic – MixColumns uses XOR for field addition and a left-shift plus conditional XOR of
0x1Bfor multiplication by 2; no RAM-based lookup tables are used for field arithmetic. - No pipeline registers – the design trades timing closure at high frequencies for simplicity and minimum latency.
| Parameter | Value |
|---|---|
| Baud rate | 9600 bps |
| Data bits (TX) | 16 |
| Data bits (RX) | 8 |
| Parity | None |
| Stop bits | 1 |
| Idle state | HIGH |
| Bit order | LSB first |
UART_TX3 transmits 16-bit words. Sending the full 128-bit ciphertext requires 8 back-to-back transmissions (8 x 16 bits). UART_RX3 captures 8-bit bytes and the top-level on Board B assembles them into the 128-bit block before decryption.
| Signal | Pin | I/O Standard | Notes |
|---|---|---|---|
clk |
W5 | LVCMOS33 | 100 MHz on-board oscillator |
Enable |
V17 | LVCMOS33 | Slide switch SW0 |
TX |
J1 | LVCMOS33 | UART output – JA PMOD header, pin 1 |
ps2_clk |
C17 | LVCMOS33 | PS/2 clock, internal pull-up enabled |
ps2_data |
B17 | LVCMOS33 | PS/2 data, internal pull-up enabled |
| Signal | Pin(s) | I/O Standard | Notes |
|---|---|---|---|
clk |
W5 | LVCMOS33 | 100 MHz on-board oscillator |
vgaRed[3:0] |
G19, H19, J19, N19 | LVCMOS33 | VGA red channel |
vgaGreen[3:0] |
J17, H17, G17, D17 | LVCMOS33 | VGA green channel |
vgaBlue[3:0] |
N18, L18, K18, J18 | LVCMOS33 | VGA blue channel |
hsync |
P19 | LVCMOS33 | Horizontal sync |
vsync |
R19 | LVCMOS33 | Vertical sync |
RX |
— | LVCMOS33 | Uncomment the appropriate PMOD pin in the XDC |
reset |
— | LVCMOS33 | Uncomment the desired button or switch in the XDC |
| Metric | Value | Calculation |
|---|---|---|
| Bit period | 104.17 µs | 10,416 cycles / 100 MHz |
| 16-bit frame duration | ~1.88 ms | 18 bits x 104.17 µs |
| Full 128-bit transfer | ~15 ms | 8 frames x 1.88 ms |
| Metric | Value |
|---|---|
| PS/2 clock frequency | 10–16 kHz (keyboard-generated) |
| Bit capture edge | Falling edge of ps2_clk |
| Synchroniser debounce | ~5.12 µs (2^9 cycles / 100 MHz) |
| Metric | Value |
|---|---|
| Pipeline registers | 0 (fully combinatorial) |
| Latency | < 1 clock cycle (combinatorial propagation delay only) |
| Throughput | One 128-bit block per clock cycle once input is stable |
| Metric | Value |
|---|---|
| Target resolution | 800 x 600 |
| Refresh rate | 60 Hz |
| Pixel clock | ~40 MHz (generated by Clock_Divider) |
- Xilinx Vivado 2019.1 or later
- Basys3 board files installed in Vivado
- Two Basys3 boards and the required peripherals
- Open Vivado and load the existing project, or create a new RTL project targeting xc7a35tcpg236-1.
- Add all VHDL sources from
Project/project_A/project_A/project_A.srcs/sources_1/new/. - Add the constraint file
Project/project_A/project_A/project_A.srcs/constrs_1/new/BoardA.xdc. - Set
TOP.vhd(project_A) as the top-level design unit. - Run Synthesis, then Implementation, then Generate Bitstream.
- Connect Board A via USB, open Hardware Manager, and click Program Device.
- Open Vivado and load the existing project, or create a new RTL project targeting xc7a35tcpg236-1.
- Add all VHDL sources from
Project/project_B/project_b/project_b.srcs/sources_1/(bothnew/andimports/Downloads/subdirectories). - Add the constraint file
Project/project_B/project_b/project_b.srcs/constrs_1/imports/Downloads/Basys3_Master.xdc.- Uncomment the RX pin assignment that matches your wiring to Board A.
- Uncomment a reset button or switch assignment.
- Set
TOP.vhd(project_B) as the top-level design unit. - Run Synthesis, then Implementation, then Generate Bitstream.
- Connect Board B via USB, open Hardware Manager, and click Program Device.
- Wire the boards – connect Board A's TX pin (J1 on the JA PMOD header) to Board B's RX pin. Connect a common ground wire between both boards.
- Connect peripherals – attach the PS/2 keyboard to Board A and the VGA monitor to Board B.
- Power and program – program each board with its respective bitstream as described above.
- Type your message – type exactly 16 characters on the PS/2 keyboard. Each character is accumulated into the 128-bit plaintext register as you type.
- Transmit – flip slide switch SW0 (pin V17) on Board A to high. This triggers the AES encryption and UART transmission of the 128-bit ciphertext.
- View output – the decrypted 16-character message appears at position (50, 50) on the VGA monitor connected to Board B.
Note on message length: The system uses a fixed 16-character (128-bit) block. If fewer than 16 characters are typed before transmission, the lower bytes of the plaintext register will contain residual data from previous entries.
| Parameter | Value | Defined In |
|---|---|---|
| AES block size | 128 bits | encryption.vhd, Top_decryption.vhd |
| AES key size | 128 bits | addkey.vhd |
| AES rounds | 10 | encryption.vhd, Top_decryption.vhd |
| Expanded key size | 1408 bits | addkey.vhd |
| System clock | 100 MHz | TOP.vhd (both boards) |
| UART baud rate | 9600 bps | UART_TX3.vhd, UART_RX3.vhd |
| TX baud counter | 10,416 cycles/bit | UART_TX3.vhd |
| RX baud counter | 20,833 (half-period) | UART_RX3.vhd |
| Message length | 16 characters | ps2_keyboard_to_ascii.vhd |
| Button debounce window | 10,000 cycles (100 µs) | Button_debounce.vhd |
| PS/2 synchroniser depth | 512 cycles (5.12 µs) | ps2_keyboard.vhd |
| VGA resolution | 800 x 600 | VGA.vhd |
| VGA text position | (50, 50) px | VGA.vhd |
| S-box entries | 256 | sbox.vhd, inv_sbox.vhd |
| Parallel S-box instances | 16 | subbyte.vhd, inv_sub_bytes.vhd |