KeeloqGrabber Part 4. Writing the Data Receiver Logic
In this article, we will cover the implementation of code for a KeeLoq data receiver.
The receiver will include synchronization based on the initial preamble, as well as error checking.

For correct reception, we need to determine the value Te from the preamble.
We will also define the following parameters:
parameter Te_div = 5'd20; // unit 10us
parameter Te_all = 8'd208; // 10 + 66 * 3
The base time unit in our project will be 10 µs.
The parameter Te_div defines the divider for the PLL clock frequency.
The parameter Te_all is the total number of Te units in the packet, where:
• 10 — header length
• 66 — encrypted + fixed parts, multiplied by 3
Description of Logic Blocks
1. Clock divider for generating 10 µs
reg [4:0] div_10us;
always @(posedge clk_2mhz) begin
if (~reset || ~|div_10us)
div_10us <= Te_div - 1'd1;
else
div_10us <= div_10us - 1'd1;
end
wire clk_10us = ~|div_10us;
2. Input signal change detector
To do this, we pass the receiver signal through the register s_signal_i, which allows detecting a change by comparing values with a one-clock delay.
reg s_signal_i;
always @(posedge clk_2mhz) begin
if (~reset)
s_signal_i <= 0;
else if (clk_10us)
s_signal_i <= signal_i;
end
wire signal_changed = s_signal_i ^ signal_i;
3. Counting Te units before signal change
We calculate the number of 10 µs intervals before the signal changes.
The value Te_sync_cnt[1] will be used to calculate Te.
reg [5:0] Te_sync_cnt[1:0];
always @(posedge clk_2mhz) begin
if (~reset) begin
Te_sync_cnt[0] <= 0;
Te_sync_cnt[1] <= 0;
end
else if (clk_10us) begin
if (signal_changed) begin
Te_sync_cnt[1] <= Te_sync_cnt[0];
Te_sync_cnt[0] <= 0;
end
else
Te_sync_cnt[0] <= Te_sync_cnt[0] + 1'd1;
end
end
4. Capturing Te value after preamble detection
After the signal preamble_found is asserted, we store the value of Te_val and use it later for reception.
reg [5:0] Te_val;
always @(posedge clk_2mhz) begin
if (~reset)
Te_val <= 0;
else if (preamble_found)
Te_val <= Te_sync_cnt[1];
end
5. Preamble counter
If the timing matches (Te_sync_cnt[1] == Te_sync_cnt[0]), we increment the counter.
The minimum half-period length is 10 Te.
reg [4:0] preamble_cnt;
always @(posedge clk_2mhz) begin
if (~reset)
preamble_cnt <= 0;
else if (flag_sync || preamble_found)
preamble_cnt <= 0;
else if (signal_changed && clk_10us) begin
if (Te_sync_cnt[1] == Te_sync_cnt[0] && Te_sync_cnt[0] > 6'd9)
preamble_cnt <= preamble_cnt + 1'd1;
else
preamble_cnt <= 0;
end
end
wire preamble_found = (preamble_cnt == 5'd22);
6. Synchronization flag
When the preamble is detected, we set flag_sync.
This flag is cleared in case of reception error or at the end of transmission.
reg flag_sync;
always @(posedge clk_2mhz) begin
if (~reset)
flag_sync <= 0;
else if (preamble_found)
flag_sync <= 1;
else if (basic_cnt == Te_all - 1'd1 && Te_clk)
flag_sync <= 0;
else if (flag_header && signal_i && Ack_clk)
flag_sync <= 0;
else if (flag_data && (Ack_clk && data_bit_cnt == 2'd0 && ~signal_i))
flag_sync <= 0;
else if (flag_data && (Ack_clk && data_bit_cnt == 2'd2 && signal_i))
flag_sync <= 0;
end
7. Te counter
After receiving the preamble, Te_cnt counts down from Te_val to zero.
reg [5:0] Te_cnt;
always @(posedge clk_2mhz) begin
if (~reset)
Te_cnt <= 0;
else if (~flag_sync)
Te_cnt <= 0;
else if (clk_10us) begin
if (~|Te_cnt)
Te_cnt <= Te_val;
else
Te_cnt <= Te_cnt - 1'd1;
end
end
For convenience:
wire Te_clk = ~|Te_cnt & clk_10us;
wire Ack_clk = (Te_cnt == 6'd8) & clk_10us;
8. Global Te counter
reg [7:0] basic_cnt;
always @(posedge clk_2mhz) begin
if (~reset)
basic_cnt <= 0;
else if (~flag_sync)
basic_cnt <= 0;
else if (Te_clk)
basic_cnt <= basic_cnt + 1'd1;
end
9. Header reception flag
Set when preamble_found is high.
Reset on error or after 10 Te of low level.
reg flag_header;
always @(posedge clk_2mhz) begin
if (~reset)
flag_header <= 0;
else if (preamble_found)
flag_header <= 1'b1;
else if (~flag_sync || (basic_cnt == 8'd10 && Te_clk))
flag_header <= 1'b0;
end
10. Data reception flag
Set after preamble, reset on error or when all data is received.
reg flag_data;
always @(posedge clk_2mhz) begin
if (~reset)
flag_data <= 0;
else if (~flag_sync || ((basic_cnt == Te_all - 1'd1) && Te_clk))
flag_data <= 0;
else if (basic_cnt == 8'd10 && Te_clk)
flag_data <= 1'd1;
end
11. Data bit timing (3 Te per bit)
According to the KeeLoq diagram:
• 0 Te — high level
• 1 Te — read value
• 2 Te — low level
reg [1:0] data_bit_cnt;
always @(posedge clk_2mhz) begin
if (~reset)
data_bit_cnt <= 0;
else if (~flag_sync || ~flag_data)
data_bit_cnt <= 0;
else if (Ack_clk) begin
if (data_bit_cnt == 2'd2)
data_bit_cnt <= 0;
else
data_bit_cnt <= data_bit_cnt + 1'd1;
end
end
12. Shift register for data reception
reg [65:0] data;
always @(posedge clk_2mhz) begin
if (~reset)
data <= 0;
else if (~flag_sync || ~flag_data)
data <= 0;
else if (data_bit_cnt == 2'd1 && Ack_clk) begin
data <= {data[64:0], ~signal_i};
end
end
13. FIFO write signal
Generated when no errors occur and all bits are received.
reg fifo_wreq;
always @(posedge clk_2mhz) begin
if (~reset)
fifo_wreq <= 0;
else if (basic_cnt == Te_all - 1'd1 && ~fifo_full && Te_clk)
fifo_wreq <= 1'b1;
else
fifo_wreq <= 0;
end
In the next part, we will implement the UART module for transmitting the received data to a computer.
The full project source code will also be available in the next article.
