Universal Address Decoders

by GateForge Consulting Ltd.

When accessing memory-mapped hardware, we must check if the address lies between a base address and a higher, inclusive bound address within the decoded range. Rather than do so "manually" each time, we can create a single module that will decode any address range with minimal logic. In both of the designs that follow, we provide the CAD tools with enough information for them to perform Boolean optimization and minimize the decoding logic. We also don't have to figure out the width of the decoded address range ahead of time, but only have to give the width of the total address space.


The first decoder checks if the input address matches each possible valid address in the decoded range, then outputs the bitwise OR of all these checks. Some boolean algebra shows that this circuit will always optimize down to a minimal form: any sequence of address bits which iterate over their entire range become Boolean "don't care", leaving the other address bits to do the matching. The constant bits in the valid addresses range will also cause gates to simplify further.

This approach has one caveat: you may have to test, at synthesis time, up to all 2n possible valid addresses, and store the matches into a vector 2n bits wide. As far as I know, Verilog implementations have a maximum vector width of a few million, so this decoder may break for decoded address ranges more than 20-23 bits wide (1 to 8 Mega-locations), or have excessive synthesis times. However, this implementation yields the smallest, fastest logic possible for a fixed address range.

(It might be possible to avoid the wide vector by altering the implementation to cumulatively OR the address matches as they are generated, but that then describes a very long circuit, which the CAD tool may not optimize as expected. See the next decoder for such an optimization surprise.)

// A *universal* address decoder.
// Works for any static address range at any starting point.

module Address_Range_Decoder_Static
#(
    parameter       ADDR_WIDTH          = 0,
    parameter       ADDR_BASE           = 0,
    parameter       ADDR_BOUND          = 0
)
(
    input   wire    [ADDR_WIDTH-1:0]    addr,
    output  reg                         hit 
);
    initial begin
        hit = 0;
    end

    localparam ADDR_COUNT = ADDR_BOUND - ADDR_BASE + 1;

    integer                     i              = 0;
    reg     [ADDR_COUNT-1:0]    per_addr_match = 0;

    // Check each valid address in range for match
    always @(*) begin
        for(i = ADDR_BASE; i <= ADDR_BOUND; i = i + 1) begin : addr_decode
            per_addr_match[i-ADDR_BASE] <= (addr == i[ADDR_WIDTH-1:0]);
        end
    end

    // Do any of them match?
    always @(*) begin
        hit <= | per_addr_match;
    end 
endmodule

If we want to decode address ranges that are much larger, or if we want to dynamically change the decoded address range, we have to use an arithmetic bounds test which involves two subtractions (i.e.: is the address >= to the base and <= to the bound?). This is one case where expressing the function behaviourally, rather than structurally with explicit full-adder logic, yields a better synthesis result (but your particular CAD tool may do otherwise).

This approach maps well to FPGAs, since it will use the built-in carry-chain hardware, which can reach high speeds without pipelining (e.g.: ~500 MHz for a 32-bit adder on Stratix IV). Furthermore, making the base and bound address inputs constants might optimize the hardware down to plain logic without using subtractors (Quartus does it). The resulting circuit is not as optimal as the static decoder above, but still small and fast.

// A *universal* address decoder. 
// Works for any address range at any starting point.

module Address_Range_Decoder_Arithmetic
#(
    parameter       ADDR_WIDTH          = 0
)
(
    input   wire    [ADDR_WIDTH-1:0]    base_addr,
    input   wire    [ADDR_WIDTH-1:0]    bound_addr,
    input   wire    [ADDR_WIDTH-1:0]    addr,
    output  reg                         hit 
);
    initial begin
        hit = 0;
    end

    reg base_or_higher = 0;
    reg bound_or_lower = 0;

    always @(*) begin
        base_or_higher = (addr >= base_addr);
        bound_or_lower = (addr <= bound_addr);
        hit            = (base_or_higher && bound_or_lower);
    end

endmodule

fpgacpu.ca