How to use LUT table to implement combinational logic in FPGA?
Here’s a clear, hands-on way to think about (and use) LUTs for combinational logic on an FPGA.
1) What a LUT is (in practice)
A Lookup Table (LUT) is a tiny ROM inside each logic cell. With N inputs (e.g., 4–6 depending on device), it stores 2N2^N2N bits. The input lines form an address; the selected bit is the output. Any Boolean function of up to N inputs can be realized by programming those 2N2^N2N bits.
2) Three common ways to “use a LUT”
A) Write plain combinational HDL (let synthesis map to LUTs)
This is the normal, portable approach. The synthesizer turns your Boolean into LUT INIT bits automatically.
Verilog
module comb_logic(
input wire a, b, c, d,
output wire y
);
// Any Boolean expression you like:
assign y = ((a & b) | (~a & c)) ^ d;
endmoduleVHDL
library ieee; use ieee.std_logic_1164.all;
entity comb_logic is
port(a,b,c,d : in std_logic; y : out std_logic);
end;
architecture rtl of comb_logic is
begin
y <= ((a and b) or (not a and c)) xor d;
end;Pros: portable, readable, timing-friendly with tools.
Tip: keep the fan-in of individual expressions ≤ LUT input size (e.g., ≤6) or break into sub-terms so the tool can pack efficiently.
B) Write a truth table (ROM-style LUT) in HDL
Perfect when you literally have a truth table/S-box/decoder.
Verilog — case table
module seven_seg(
input wire [3:0] nibble,
output reg [6:0] seg // abcdefg
);
always @* begin
case (nibble)
4'h0: seg = 7'b1111110;
4'h1: seg = 7'b0110000;
4'h2: seg = 7'b1101101;
4'h3: seg = 7'b1111001;
// ...
default: seg = 7'b0000000; // safe default to avoid latches
endcase
end
endmoduleVerilog — ROM array (synthesizes to distributed LUT RAM)
module lut_rom #(
parameter WIDTH = 8
)(
input wire [5:0] addr, // 6-input address -> 64 entries
output wire [WIDTH-1:0] q
);
// Xilinx: keep small ROMs in LUTs (distributed)
(* rom_style = "distributed" *) reg [WIDTH-1:0] mem [0:63];
initial begin
// Fill with your truth table (hex or binary)
// e.g., $readmemh("table.hex", mem);
mem[ 0] = 8'h00; mem[ 1] = 8'h12; /* ... */ mem[63] = 8'hA5;
end
assign q = mem[addr];
endmoduleVHDL — with-else / case works similarly.
Pros: exact control, easy to generate from scripts, great for decoders/S-boxes/CRC tables.
C) (Advanced) Instantiate a vendor LUT primitive
If you need absolute control (e.g., for floorplanning or packing), you can instantiate device primitives and set the INIT bits yourself (not portable).
Get Ampheo’s stories in your inbox
Join Medium for free to get updates from this writer.
Xilinx (example, LUT6)
// O = INIT[{I5,I4,I3,I2,I1,I0}] (Bit addressing per device doc)
module lut6_thing(input wire I0,I1,I2,I3,I4,I5, output wire O);
// INIT is 64 bits (2^6): set to match your truth table
LUT6 #(.INIT(64'h6996_6996_6996_6996)) // example pattern
u_lut (.I0(I0),.I1(I1),.I2(I2),.I3(I3),.I4(I4),.I5(I5),.O(O));
endmoduleOnly use this when you truly need it; verify bit ordering in your device family guide.
3) Designing from a truth table (step-by-step)
- List all inputs → there are 2N2^N2N rows.
- Write desired output for each row (use don’t-cares if allowed).
- (Option A) Convert to Boolean and code it (the tool minimizes/K-maps for you).
- (Option B) Drop the table into a
case/ROM as above. - Simulate to verify every row.
- Check post-synthesis resource report (LUT count, levels of logic, timing).
4) Practical tips for tight, fast logic
- Keep logic depth low: pipeline wide functions (register between stages).
- Match LUT input limit: split >6-input terms into precomputed sub-terms.
- Exploit dedicated fabric: adders/comparators map better via carry chains than generic LUT equations (
+,==,<often synthesize faster). - Use don’t-cares carefully:
casez/casex(Verilog) or-(VHDL withstd_match) can reduce LUTs, but never drive1'bxto outputs in synth code. - Avoid unintended latches: always provide a
default/othersbranch. - Resource control: for small tables use
(* rom_style="distributed" *); for larger, consider block RAM.
5) Example: 4-bit Gray→Binary via logic (maps to LUTs)
Verilog
module gray_to_bin(
input wire [3:0] g,
output wire [3:0] b
);
assign b[3] = g[3];
assign b[2] = g[3] ^ g[2];
assign b[1] = g[3] ^ g[2] ^ g[1];
assign b[0] = g[3] ^ g[2] ^ g[1] ^ g[0];
endmoduleThis is purely combinational; the tool packs those XOR trees into LUTs and carry/XOR resources efficiently.
6) Example: Small 6-input S-box as a LUT ROM
Verilog
module sbox6(
input wire [5:0] in,
output wire [3:0] out
);
(* rom_style="distributed" *) reg [3:0] t [0:63];
initial begin
// Fill with your 64-entry 4-bit S-box
t[6'h00]=4'hC; t[6'h01]=4'h5; /* ... */ t[6'h3F]=4'hA;
end
assign out = t[in];
endmodule7) Verification checklist
- Simulate: cover all 2N2^N2N cases (easy if N≤10).
- Assertions: e.g.,
unique casein SystemVerilog to catch overlaps. - Post-synth: confirm the expected LUT count and path timing.
- Equivalence (optional): run formal check if you generated tables via scripts.
8) When to choose each method
- Plain Boolean HDL: most combinational logic, arithmetic, comparators.
- Table/ROM: decoders, encoders, S-boxes, protocol maps, arbitrary bit permutations.
- Primitive LUTs: rare, when squeezing last MHz/LUT in a carefully placed macro.







