How to use FPGA to emulate logic?

 Here’s a clean, practical path to use an FPGA as “universal glue logic” (i.e., to emulate gates, decoders, counters, small PLDs, even whole 74xx boards).



1) What “emulating logic” means

You implement a truth-table or state machine in HDL (Verilog/VHDL). The FPGA maps it to LUTs (for combinational logic) and flip-flops/BRAM (for state/ROM). Then you pin-map the FPGA I/Os to your real signals.

2) Minimal workflow (works for Xilinx/Intel/Lattice or Yosys+nextpnr)

  1. Sketch the logic (gates, truth table, or state diagram).

  2. Write HDL.

  3. Simulate (testbench) to prove function.

  4. Add pin constraints & I/O standards (usually LVCMOS33).

  5. Synthesize → place/route → program the dev board.

  6. Probe with a logic analyzer or LEDs; iterate.

3) Hardware cautions (don’t skip)

  • Voltage levels: Most FPGAs are not 5 V-tolerant. Use level shifters or series resistors when interfacing 5 V TTL.

  • Tri-state busses: Inside FPGAs, tri-state becomes multiplexers. Only pads can tri-state with an output-enable.

  • Async inputs: Use two-flip-flop synchronizers and debounce buttons/switches.

  • Clocks/timing: Constrain your clock; don’t rely on “free-running” tools defaults.

4) Tiny building blocks you’ll re-use

(a) 2-FF synchronizer (any async input)

module sync2(input clk, input din_async, output reg dout); reg s; always @(posedge clk) begin s <= din_async; dout <= s; end endmodule

(b) Button debouncer (parameterizable)

module debounce #(parameter N=19)(input clk, input din, output reg q); reg [N:0] cnt; reg d0; always @(posedge clk) begin if (din != d0) begin d0 <= din; cnt <= 0; end else if (!cnt[N]) cnt <= cnt + 1; if (cnt[N]) q <= d0; end endmodule

5) Example 1: emulate a simple combinational chip (3-input majority / NAND pack)

module majority3(input a,b,c, output y); assign y = (a&b) | (a&c) | (b&c); // replaces several 74xx gates endmodule

6) Example 2: emulate a classic 74xx counter (74LS161-style)

  • Features: sync clear, parallel load, enable, ripple-carry.

module ls161( input clk, input clr_n, input load_n, input enp, input ent, input [3:0] d, output reg [3:0] q, output rco ); assign rco = ent && enp && (q==4'hF); always @(posedge clk or negedge clr_n) begin if (!clr_n) q <= 0; else if (!load_n) q <= d; else if (enp && ent) q <= q + 1; end endmodule

7) Example 3: ROM/PLA-style logic (e.g., 7-segment decoder)

Use a case (synthesizes to LUTs/BRAM depending on size).

module seg7(input [3:0] x, output reg [6:0] seg); // abcdefg, active-high always @* begin case (x) 4'h0: seg=7'b1111110; 4'h1: seg=7'b0110000; 4'h2: seg=7'b1101101; 4'h3: seg=7'b1111001; 4'h4: seg=7'b0110011; 4'h5: seg=7'b1011011; 4'h6: seg=7'b1011111; 4'h7: seg=7'b1110000; 4'h8: seg=7'b1111111; 4'h9: seg=7'b1111011; default: seg=7'b0000001; // dash endcase end endmodule

8) Top level: map pins and stitch blocks

module top( input clk_in, input btn_n, // active-low button input a,b,c, // external logic signals output [6:0] seg, output [3:0] q, output y, rco ); wire clk = clk_in; // add PLL if you need another freq wire btn_sync, btn; sync2 u_sync (.clk(clk), .din_async(~btn_n), .dout(btn_sync)); debounce u_db (.clk(clk), .din(btn_sync), .q(btn)); majority3 u_maj (.a(a),.b(b),.c(c),.y(y)); ls161 u_ct (.clk(clk), .clr_n(~btn), .load_n(1'b1), .enp(1'b1), .ent(1'b1), .d(4'h0), .q(q), .rco(rco)); seg7 u_7s (.x(q), .seg(seg)); endmodule

9) Pin constraints (template)

  • Xilinx (XDC)

set_property PACKAGE_PIN W5 [get_ports clk_in] set_property IOSTANDARD LVCMOS33 [get_ports clk_in] set_property PACKAGE_PIN U16 [get_ports {a}] set_property PACKAGE_PIN V16 [get_ports {b}] set_property PACKAGE_PIN W16 [get_ports {c}] # ... repeat for seg[6:0], q[3:0], rco, btn_n create_clock -name sysclk -period 10.0 [get_ports clk_in] # 100 MHz
  • Intel (QSF/SDC) and Lattice (LPF/SDC) are similar—assign pins and add create_clock.

10) Simulate quickly (smoke test)

Write a tiny testbench:

module tb; reg clk=0, clr_n=1; wire [3:0] q; wire rco; always #5 clk = ~clk; // 100 MHz ls161 dut(.clk(clk), .clr_n(clr_n), .load_n(1'b1), .enp(1'b1), .ent(1'b1), .d(4'h0), .q(q), .rco(rco)); initial begin clr_n=0; #25; clr_n=1; repeat(20) @(posedge clk); $finish; end endmodule

Run in Vivado/Quartus/Radiant—or with Verilator—to verify behavior before touching hardware.

11) Scaling up (replace lots of 74xx)

  • Group related chips into one HDL module per “board section.”

  • Use block RAM for larger ROMs or lookup functions.

  • Replace internal tri-states with muxes + registered enables.

  • Keep a spreadsheet mapping each original 74xx pin to an FPGA pin.

  • If you need exact propagation behavior, model it with registered stages and timing constraints.

12) Debug & bring-up

  • Blink/LEDs for basic life signs; then use the vendor’s Integrated Logic Analyzer (ILA / SignalTap) to probe internal nets.

  • If timing fails, lower clock or pipeline (register between logic levels).

  • For bouncy external lines, confirm the synchronizer + debouncer are in place.

评论

此博客中的热门博文

How To Connect Stm32 To PC?

Detailed Explanation of STM32 HAL Library Clock System

How to add a GPS sensor to ESP32 for Wokwi?