понедельник, 30 марта 2015 г.

Сдвиговый регистр на RAM в MyHDL

В определенный момент, например при фильтрации, может понадобиться очень большой буфер для хранения отсчетов сигнала, который будет невозможно реализовать за счет регистров. Здесь нам на помощь приходит внутренняя память (RAM) ПЛИС, которую можно превратить в сдвиговый регистр, реализовав модуль обертку над ней.
Писать будем на Python с помощью MyHDL. Начну немного с того: с реализации ROM:
    def ROM(self, i_clk, i_rst, i_en, i_read_adr, o_data, CONTENT):
        DEPTH = len(CONTENT)
        WIDTH = len(o_data) - 1
        @always_comb
        def comb():
            o_data.next = CONTENT[i_read_adr]
        return comb#, seq

Поставим за правило: все необходимые параметры модуля выдергивать из входных данных. Например, если входной адрес состоит из 2 бит, то максимально можно адресовать 4 слова. Другой вариант применительно для ROM: начальная инициализация памяти массивом. По факту из примера можно выкинуть 2 и 3 строчки - ничего бы не изменилось. Теперь про RAM:
    def RAM(self, i_clk, i_rst, i_en, i_read_adr, i_write_adr, i_we, i_data, o_data):
        DEPTH = max(2**len(i_read_adr), 2**len(i_write_adr))
        WIDTH = len(i_data) - 1
        mem = [Signal(intbv(0, -2**WIDTH, 2**WIDTH)) for i in range(DEPTH)]
        r_dout = Signal(intbv(0, -2**WIDTH, 2**WIDTH))
        @always_comb
        def exits():
            o_data.next = r_dout
        @always(i_clk.posedge, i_rst.negedge)
        def seq():
            if i_rst == 0:
                r_dout.next = 0
            elif i_en == 1:
                r_dout.next = mem[i_read_adr]
                if i_we == 1:
                    mem[i_write_adr].next = i_data
        return exits, seq

Здесь описана dual port память. В общем случае, наверно, разрядность адресов на двух портах может быть разной. Поэтому глубина памяти (количество ячеек) считается исходя из "худшего" случая. Разрядность данных такая, потому что они считаются знаковыми. На четвертой строчке инициализируем память нулями и на пятой строчке объявляем выходной регистр. Двух портовая память потому что нужно по одному адресу читать и писать в определенное время (так можно сделать и с обычной памятью, но я не стал пробовать). Дальше все в принципе понятно, если нет, то разобраться можно, прочтя страницу проекта MyHDL.
Теперь поехали дальше: сдвиговый регистр или обертка над RAM. Сразу пример кода чего хотим:

    def Shift_reg(self, i_clk, i_rst, i_en, i_new_data, i_din, o_active, o_dout, o_addr, DEPTH):
        WIDTH = len(i_din) - 1
        w_din = Signal(intbv(0, -2**WIDTH, 2**WIDTH))
        w_dout = Signal(intbv(0, -2**WIDTH, 2**WIDTH))
        ###
        FFT_len = DEPTH
        ###
        n = int(np.log2(FFT_len))
        r_addr = Signal(intbv(0,0,2**n))
        r_we = Signal(bool(0))
        
        ram = self.RAM(i_clk, i_rst, i_en, r_addr, r_addr, r_we, w_din, w_dout)
        @always(i_clk.posedge, i_rst.negedge)
        def seq():
            if not i_rst:
                r_we.next = 0
                r_addr.next = 0
            elif i_en:
                if i_new_data:
                    r_we.next = 1
                    r_addr.next = 0
                else:
                    if r_addr != r_addr.max - 1:
                        r_addr.next = r_addr + 1
                        r_we.next = 1
                    else:
                        r_we.next = 0

        @always_comb
        def comb():
            if r_addr == 0:
                w_din.next = i_din
            else:
                w_din.next = w_dout
        @always_comb
        def dout():
            o_dout.next = w_din
        @always_comb
        def active():
            o_active.next = r_we
        @always_comb
        def addr():
            o_addr.next = r_addr
        return ram, seq, comb, dout, active,addr

Тут вся магия в мультиплексоре на строках 30-34. Фактически w_din обратная связь, которая идет на выход и на вход RAM. Эта обертка сдвигает адреса и ставит запись так, чтобы первый отсчет в один такт помещался в выходной регистр и в память записывалось новое значение на входе, а на следующем такте старое значение переписывалось в ячейку с увеличенным на единицу адресом и так до конца. Теперь можно легко реализовать фильтр как в прошлой статье, используя сдвиговый регистр и ROM с коэффициентами фильтра + добавить умножение с накоплением. Я же в качестве еще одного примера покажу процедуру умножения сдвигового регистра на оконную функцию для БПФ. Благодаря такому ходу можно разделить задачу подготовки данных для БПФ, алгоритм или конечный автомат его подсчета и подготовку данных на выкачку.

    def FFT_input(self, i_clk, i_rst, i_en, i_new_data, i_din, o_dout, o_active_write, o_addr, FFT_SIZE):
        WIDTH = len(i_din) - 1
        WINDOW_WIDTH = WIDTH - 1
        DEPTH = FFT_SIZE
        wo_union = Signal(bool(0))
        wo_shift_data = Signal(intbv(0,-2**WIDTH, 2**WIDTH))
        w_dout = Signal(intbv(0,-2**WIDTH, 2**WIDTH))
        wo_addr = Signal(intbv(0,0,FFT_SIZE))
        
        shift = self.Shift_reg(i_clk, i_rst, i_en, i_new_data, i_din, wo_union, wo_shift_data, wo_addr,FFT_SIZE)
        
        wo_window = Signal(intbv(0,-2**WIDTH, 2**WIDTH))
        W_blackman = np.blackman(DEPTH)
        W_blackman = W_blackman / max(W_blackman)
        CONTENT = tuple([int(W_blackman[i]*(2**WINDOW_WIDTH)) for i in range(DEPTH)])
        print CONTENT
        rom = self.ROM(i_clk, i_rst, i_en, wo_addr, wo_window, CONTENT)
        MULT_WIDTH = 2*len(i_din) - 1
        w_mult = Signal(intbv(0,-2**MULT_WIDTH, 2**MULT_WIDTH))
#        SHIFT_VAL = np.log2(max(CONTENT))
#        print SHIFT_VAL
        
        @always_comb
        def fmult():
            w_mult.next = (wo_shift_data*wo_window)# >> (WIDTH-1)
        @always_comb
        def fw_dout():
            w_dout.next = w_mult[len(i_din)+WINDOW_WIDTH:WINDOW_WIDTH]
        @always_comb
        def fo_dout():
            o_dout.next = w_dout
        @always_comb
        def fo_active_write():
            o_active_write.next = wo_union
        @always_comb
        def fo_addr():
            o_addr.next = wo_addr
        
        return shift, rom, fmult, fw_dout, fo_dout, fo_active_write, fo_addr

Дальше будем разбираться с конечными автоматами на MyHDL

Комментариев нет:

Отправить комментарий