I think gricardo99's example is best, but I'll give one too just to see if I can cover it differently.

FPGA stands for field programmable gate array, where "field" here actually means "in the field," not some electrical term.

And it really is an array of gates, where a gate is a very simple function taking from one to several single-bit inputs, and providing a single-bit output. Such as AND, OR, NOT. Actually, as sort of a quirk of the transistor level design, it takes fewer transistors to design a NOT-AND gate (called NAND) and a NOT-OR gate (called) NOR, so those are usually the primitives.

The output of these gates is updated continuously, with small delay as the electricity flows through the gate. Maybe 100 picoseconds of delay or so. Varies by technology, but it's non-zero. It's called the propagation delay.

Gates are often understood by looking at truth-tables and waveforms.

So the truth table for an AND gate with inputs a,b and output z would be:

ab|z
----
00|0
01|0
10|0
11|1

And a waveform could look like this:

+------------------+
a |
+----+
+--------------+
b |
+--------+

=========================

+----------+
z |
+------------+

the X axis is time, and each input can be low or high (0 or 1)

In this diagram, A goes high first, but it's only after having both inputs high that the output goes high. I put a small propagation delay in output.

If one of the inputs went low, a very brief time later the output would drop as well.

By cascading gates such that the outputs of one feed to the inputs of another, arbitrarily complex logic can be built.

A latch is one such complex gate. It has two inputs, one being the control, the other being data. Each a single bit. When the control line is high, the input will propagate to the output. When the control line is low, the output "freezes" at the last value it had when the control was high.

Another way to describe a latch would be as being "transparent" allowing the input to flow to the output unchanged (other than delayed) when the control line is high. And then maintaining the last value once the control line drops.

You can also design a latch so that it works with the control line inverted, so it's transparent when the control line is low, and freezes when it goes high. You could describe one as positive and one as negative. I forget what the real terms are, but that should work.

A flip flop is a storage element made out of two latches. It is said to capture data on the clock edge, but this is sort of emulated. What happens is the first latch is transparent while the clock is low (negative), and the second latch is transparent while the clock is high. The effect is that all the while the clock is low, the first latch is letting the input through. When the clock transitions high, it freezes, and then the second latch becomes transparent, letting the frozen value of the first one through.

This gives the appearance of capturing data on the rising edge. When the clock goes low again, the output is frozen on the second stage.

I don't know if flops are really made this way anymore, but that's a sort of naive way to make one and it works.

A group of flops designed to work together loading in parallel are called a "register."

Logic gates that do not use a clock directly (like AND, OR, NOT) are used to build up a cloud of combinational logic. Named because it is a combination of gates.

Logic gates that do use a clock directly (latches and flip flops), are called sequential. This name comes from how they are used... a series or cascade of flip flops will pass input to output on the rising (or falling if so designed) edge of the clock on one flip flop at a time, in sequence.

This works because the clock signal path is carefully designed to get to all flip flops at nearly the same time. However the data path from the first flip flop to the second flip flop has normal propagation delay. So all the flops see the same rising edge at the same time, but the data hasn't made it through the first flop and all the way to the second flop when the second flop sees that edge.

So the data sort of hop-scotches along through the flops. On each clock edge, it jumps to the next flop.

Now if you take a set of parallel flops making up a register, and put a combinational cloud of logic at the output (taking in the output of the register) and feed back the output of the cloud of combo logic to the input of the register (forming a circle), you can make all sorts of interesting things.

If you made the cloud of logic add 1 to the input, you have a counter that increments on every clock edge. You have to initialize the flops, and while I didn't talk about it, they are designed with an extra input to set them to zero. But once initialized, it will count. And if you don't design the logic to handle it, the count will overflow and start over eventually.

I don't know if that was understandable or not, but that's pretty much the crux of all logic design. Well, really taking the "counter" idea a few steps further and you are building state machines. They are the crux really. They don't seem to show up in software nearly as often as they do in digital logic. I'd say the state machine in logic design is about as common as a for-loop (or any other loop).

-----
edit:
-----

Forgot about "programming" an FPGA. So it's an array of gates, and depending on the type of fpga, there are either fuses between all the gates which get blown (or not) when programmed, setting up the connection.

Or there is extra "hidden" logic that emulates fuses and uses memory such as flash to maintain the connection state.

Some use sram instead of flash, and when they lose power they have to load their connection configuration for you application from external flash.

This configuration of connections is created by a tool called a Synthesizer (well nobody calls them that, they just say the tool does Synthesis), and when people talk about "programming" an FPGA, they mean feeding instructions to a synthesizer, and usually aren't referring to the loading of configuration bitfile into the FPGA.

Synthesis tools take VHDL, Verilog, SystemVerilog and SystemC as input, and these are "description" languages. At least they started that way. They were more akin to HTML than javascript, but as Synthesis tools have evolved, so have the "description" languages, and it really is much closer to programming now than ever.

Some tools will take in C or other high level languages (don't laugh, it's high level comparitively), and what they do is called High Level Synthesis.

The main problem is that C doesn't have the semantics to describe things like clock edges.

So the guru's came up with SystemC which is a C++ library that includes a simulation engine and classes for the semantics of describing logic. It's not nearly as popular as the other three, but it's probably a good starting point for someone with a CS background.