Direct input-to-output mapping with expressions, case statements, and boolean logic. Pure functions in hardware.
Combinational logic is the foundation of all digital circuits. Unlike sequential logic, combinational components have no memory and no clock. The outputs are determined entirely by the current values of the inputs. When any input changes, the outputs update immediately (after propagation delay). This makes combinational components behave like pure functions: the same inputs always produce the same outputs.
In RHDL, combinational logic is expressed using the behavior block. Every signal assignment inside a behavior block creates a direct wire from inputs to outputs with no implicit state. This maps directly to hardware constructs like logic gates, multiplexers, and lookup tables in an FPGA.
| Property | Description |
|---|---|
| Clock | Not required. Outputs change whenever inputs change. |
| State | None. No memory between evaluations. |
| Propagation | Outputs reflect inputs after gate delay (combinational path). |
| Use cases | ALUs, decoders, multiplexers, encoders, comparators |
| RHDL keyword | behavior |
RHDL provides a rich set of operators for building combinational expressions. These map directly to hardware operations and can be freely composed to create complex logic.
Arithmetic and logical operators perform bitwise and mathematical operations on signals. All operations are defined on fixed-width bit vectors, and overflow behavior matches hardware semantics (wrap-around).
class ArithmeticOps < RHDL::Sim::Component
input :a, :b, width: 8
output :sum, width: 8
output :diff, width: 8
output :prod, width: 16
output :band, :bor, :bxor, width: 8
behavior do
# Arithmetic operations
sum <= a + b
diff <= a - b
prod <= a * b
# Bitwise logical operations
band <= a & b
bor <= a | b
bxor <= a ^ b
end
end
Comparison and shift operations produce boolean results or shifted bit patterns. These are essential building blocks for control logic, priority encoders, and barrel shifters.
class CompareShift < RHDL::Sim::Component
input :a, :b, width: 8
input :shamt, width: 3
output :eq, :lt, :gt
output :shl, :shr, :sra, width: 8
behavior do
# Comparison operators
eq <= a.eq?(b)
lt <= a.lt?(b)
gt <= a.gt?(b)
# Shift operations
shl <= a << shamt # logical left
shr <= a >> shamt # logical right
sra <= a.sra(shamt) # arithmetic right
end
end
The case_select construct is RHDL's way of expressing multiplexer-like selection logic. It maps a selector value to one of several output expressions, similar to a case/switch statement in software but synthesized as parallel hardware. Every case_select should include a default value to ensure all paths are covered, preventing unintentional latch inference.
class InstructionDecoder < RHDL::Sim::Component
input :opcode, width: 4
output :alu_ctrl, width: 3
output :reg_write
output :mem_enable
output :branch
behavior do
# Decode ALU control from opcode
alu_ctrl <= case_select(opcode, {
0b0000 => 0b000, # ADD
0b0001 => 0b001, # SUB
0b0010 => 0b010, # AND
0b0011 => 0b011, # OR
0b0100 => 0b100, # XOR
0b0101 => 0b101, # SLT
}, default: 0b000)
# Control signals with boolean case
reg_write <= opcode.lt?(0b1000)
mem_enable <= opcode.eq?(0b1000) | opcode.eq?(0b1001)
branch <= opcode[3] & opcode[1]
end
end
An Arithmetic Logic Unit is the quintessential combinational component. It takes two data inputs and an operation selector, then produces a result and status flags. This example shows a complete 8-bit ALU with eight operations and four condition flags.
class ALU < RHDL::Sim::Component
input :a, :b, width: 8
input :op, width: 3
output :result, width: 8
output :zero, :carry, :negative, :overflow
# Operation constants
OP_ADD = 0b000
OP_SUB = 0b001
OP_AND = 0b010
OP_OR = 0b011
OP_XOR = 0b100
OP_SHL = 0b101
OP_SHR = 0b110
OP_SLT = 0b111
behavior do
# Extended result for carry detection
wide_sum = a.zero_ext(9) + b.zero_ext(9)
wide_diff = a.zero_ext(9) - b.zero_ext(9)
# Select operation result
result <= case_select(op, {
OP_ADD => a + b,
OP_SUB => a - b,
OP_AND => a & b,
OP_OR => a | b,
OP_XOR => a ^ b,
OP_SHL => a << b[2..0],
OP_SHR => a >> b[2..0],
OP_SLT => cond(a.lt?(b), 1, 0),
})
# Condition flags
zero <= result.eq?(0)
negative <= result[7]
carry <= cond(op.eq?(OP_ADD), wide_sum[8],
cond(op.eq?(OP_SUB), wide_diff[8], 0))
overflow <= cond(op.eq?(OP_ADD),
(a[7].eq?(b[7])) & (a[7].neq?(result[7])),
cond(op.eq?(OP_SUB),
(a[7].neq?(b[7])) & (a[7].neq?(result[7])),
0))
end
end