022 – Floating-Point Conversion Update (POTD 01)


In this post we introduce our Paying Off Technical Debt (POTD) recurring series, in which we revisit and make improvements to an existing element of our design. In the first installment we will make some changes to the fixed- to floating-point conversion in our Audio Processor.

Technical Debt

This is the internet, so we are forced to use a Dilbert reference when talking about technical debt. Here it is:

Technical Debt. Dilbert by Scott Adams. Source dilbert.com
Technical Debt. Dilbert by Scott Adams. Source: dilbert.com

When I started RTL Audio Lab I naively though that technical debt would never become an issue. I’m the one setting the scope and deadlines for my work, so what could possibly go wrong? As it turns out, making progress and setting deadlines can put us in a position where it is easy to get into technical debt, regardless of what the ultimate goal of the project is. Without the accountability to customers and colleagues, it might even be easier.

I’ll acknowledge that it can be difficult to differentiate between technical debt and unnecessary optimizations in personal projects, where the scope can be changed at any time, and making progress is critical to staying motivated and productive in the long run. Having said that, there are instances where it is clear that the quality of the code that we are producing is not acceptable, even if it is only being used by us and (hopefully) a handful of strangers on the internet for fun.

I therefore decided to introduce the Paying Off Technical Debt series with the goal of making this repaying an integral part of my engineering practice and creative process. I’m constantly making small improvements to the Audio Processor design, and a lot of them won’t be featured in a standalone post, but some will.

Current Floating-Point Conversion

In a previous post we discussed the conversion between fixed- and floating-point representation for our Audio Processor. The figure below shows how the converter modules are currently instantiated.

Current Floating-Point Conversion
Current Floating-Point Conversion

There are two aspects of the current implementation that I would like to improve. First, the floating-point converter cores are instantiated at the same hierarchical level as the rest of our RTL modules, which is usually not ideal for reusability. Second, we are using two cores for each conversion where a single converter would suffice given the throughput requirements of our system.

Improved Floating-Point Conversion

We will describe the improved floating-point conversion by focusing on the fixed- to floating-point direction. It’s easy to understand how it works the other way around.

We implement a Fixed to Float Converter RTL module, which consists of two main elements: an instance of the Floating-Point Operator IP core, which performs the actual conversion, and an FSM, which controls the inputs and collects the outputs of the converter. By now this is a familiar design pattern in our project, and one we are likely to use often in the future.

The Fixed to Float FSM is quite simple, and works as follows:

  1. When new samples arrive, trigger the conversion of the left channel and store the right channel in an auxiliary signal.
  2. When the conversion of the left channel is completed, assign it to the left data output and trigger the conversion of the right channel.
  3. When the conversion of the right channel is completed, assign it to the right data output and generate the output data valid signal.
module fixed_to_float_converter # (
    parameter FIXED_POINT_BIT_WIDTH = 24,
    parameter FLOATING_POINT_BIT_WIDTH = 32
    ) (
    input   logic                                       i_clock,
    // Audio Input
    input   logic                                       i_data_valid,
    input   logic   [FIXED_POINT_BIT_WIDTH-1 : 0]       i_data_left,
    input   logic   [FIXED_POINT_BIT_WIDTH-1 : 0]       i_data_right,
    // Audio Output
    output  logic                                       o_data_valid,
    output  logic   [FLOATING_POINT_BIT_WIDTH-1 : 0]    o_data_left,
    output  logic   [FLOATING_POINT_BIT_WIDTH-1 : 0]    o_data_right
);

    timeunit 1ns;
    timeprecision 1ps;

    // Fixed-to-float Conversion
    logic                                   fixed_to_float_valid_in;
    logic [FIXED_POINT_BIT_WIDTH-1 : 0]     fixed_to_float_data_in;
    logic                                   fixed_to_float_valid_out;
    logic [FLOATING_POINT_BIT_WIDTH-1 : 0]  fixed_to_float_data_out;
    fixed_to_float fixed_to_float_inst (
        .aclk                   (i_clock),
        .s_axis_a_tvalid        (fixed_to_float_valid_in),
        .s_axis_a_tdata         (fixed_to_float_data_in),
        .m_axis_result_tvalid   (fixed_to_float_valid_out),
        .m_axis_result_tdata    (fixed_to_float_data_out)
    );

    // Fixed to Float FSM
    enum logic [1 : 0]  {IDLE,
                        CONVERT_LEFT_CHANNEL,
                        CONVERT_RIGHT_CHANNEL} fixed_to_float_fsm_state = IDLE;
    logic [FIXED_POINT_BIT_WIDTH-1 : 0] data_right = 'b0;

    always_ff @(posedge i_clock) begin : fixed_to_float_fsm
        case (fixed_to_float_fsm_state)
            IDLE : begin
                o_data_valid <= 1'b0;
                fixed_to_float_valid_in <= 1'b0;
                if (i_data_valid == 1'b1) begin
                    data_right <= i_data_right;
                    fixed_to_float_valid_in <= 1'b1;
                    fixed_to_float_data_in <= i_data_left;
                    fixed_to_float_fsm_state <= CONVERT_LEFT_CHANNEL;
                end
            end
            
            CONVERT_LEFT_CHANNEL : begin
                fixed_to_float_valid_in <= 1'b0;
                if (fixed_to_float_valid_out == 1'b1) begin
                    o_data_left <= fixed_to_float_data_out;
                    fixed_to_float_valid_in <= 1'b1;
                    fixed_to_float_data_in <= data_right;
                    fixed_to_float_fsm_state <= CONVERT_RIGHT_CHANNEL;
                end
            end
            
            CONVERT_RIGHT_CHANNEL : begin
                fixed_to_float_valid_in <= 1'b0;
                if (fixed_to_float_valid_out == 1'b1) begin
                    o_data_right <= fixed_to_float_data_out;
                    o_data_valid <= 1'b1;
                    fixed_to_float_fsm_state <= IDLE;
                end
            end

            default : begin
                fixed_to_float_fsm_state <= IDLE;
            end
        endcase
    end
    
endmodule

After simulating the improved conversion modules, we are ready to instantiate and connect them in the Audio Processor. The figure below shows how the elaborated design looks like with the new and improved architecture.

Improved Floating-Point Conversion
Improved Floating-Point Conversion

In the next post we will start exploring the world of frequency processing with an equalizer module. Stay tuned!

Cheers,

Isaac

The RTL and simulation files for this post are available in the FPGA Audio Processor repository under this tag.


Leave a Reply

Your email address will not be published. Required fields are marked *