MIPSfpga и UART

в 20:18, , рубрики: C, fpga, mips, MIPS microAptiv UP, MIPSfpga, SoC, Verilog, Анализ и проектирование систем, программирование микроконтроллеров, системное программирование

Прошло чуть больше месяца с тех пор, как я портировал open source модуль UART16550 на шину AHB-Lite. Писать об этом на тот момент было несколько не логично, так как еще не была опубликована статья про прерывания MIPSfpga.
Если вы опытный разработчик, то для вас только одна полезная новость: UART16550 добавлен в состав системы MIPSfpga-plus, дальше можете не читать. А тем, кого интересует разобранный пример использования этого модуля — добро пожаловать под кат.

image

Введение

Предполагается, что читатель:

  • знаком с предметной областью в объеме учебника Харрис-энд-Харрис [L1];
  • имеет доступ к исходным кодам MIPSfpga [L2] и mipsfpga-plus [L3];
  • имеет некоторый опыт программирования микроконтроллеров (любой архитектуры), включая использование UART. Освежить, что из себя представляет UART можно здесь [L4];

Что такое UART16550

История появления этой микросхемы неплохо описана в [L5], найти документацию на нее можно в google [L6], для нас важно другое:

  • она получила крайне широкое распространения;
  • работа с ней поддерживается ядром Linux;
  • для нее уже давно существует open source реализация на verilog [L7, L8, L9], но для шины Wishbone;
  • цена портирования этой реализации на используемую в MIPSfpga шину AHB-Lite — около 100 строчек кода [S1]:

mfp_ahb_lite_uart16550

module mfp_ahb_lite_uart16550(
    //ABB-Lite side
    input                   HCLK,
    input                   HRESETn,
    input       [ 31 : 0 ]  HADDR,
    input       [  2 : 0 ]  HBURST,
    input                   HMASTLOCK,  // ignored
    input       [  3 : 0 ]  HPROT,      // ignored
    input                   HSEL,
    input       [  2 : 0 ]  HSIZE,
    input       [  1 : 0 ]  HTRANS,
    input       [ 31 : 0 ]  HWDATA,
    input                   HWRITE,
    output  reg [ 31 : 0 ]  HRDATA,
    output                  HREADY,
    output                  HRESP,
    input                   SI_Endian,  // ignored

    //UART side
    input                   UART_SRX,   // UART serial input signal
    output                  UART_STX,   // UART serial output signal
    output                  UART_RTS,   // UART MODEM Request To Send
    input                   UART_CTS,   // UART MODEM Clear To Send
    output                  UART_DTR,   // UART MODEM Data Terminal Ready
    input                   UART_DSR,   // UART MODEM Data Set Ready
    input                   UART_RI,    // UART MODEM Ring Indicator
    input                   UART_DCD,   // UART MODEM Data Carrier Detect

    //UART internal
    output                  UART_BAUD,  // UART baudrate output
    output                  UART_INT    // UART interrupt
);

    parameter   S_INIT      = 0,
                S_IDLE      = 1,
                S_READ      = 2,
                S_WRITE     = 3;

    reg  [ 1:0 ]    State, Next;

    assign      HRESP  = 1'b0;
    assign      HREADY = (State ==  S_IDLE);

    always @ (posedge HCLK) begin
        if (~HRESETn)
            State <= S_INIT;
        else
            State <= Next;
    end

    reg  [ 2:0 ]    ADDR_old;
    wire [ 2:0 ]    ADDR = HADDR [ 4:2 ];
    wire [ 7:0 ]    ReadData;

    parameter       HTRANS_IDLE       = 2'b0;
    wire            NeedAction = HTRANS != HTRANS_IDLE && HSEL;

    always @ (*) begin
        //State change decision
        case(State)
            default     :   Next = S_IDLE;
            S_IDLE      :   Next = ~NeedAction  ? S_IDLE : (
                                    HWRITE      ? S_WRITE : S_READ );
        endcase
    end

    always @ (posedge HCLK) begin
        case(State)
            S_INIT      :   ;
            S_IDLE      :   if(HSEL) ADDR_old <= ADDR;
            S_READ      :   HRDATA <= { 24'b0, ReadData};
            S_WRITE     :   ;
        endcase
    end

    wire [ 7:0 ]    WriteData   = HWDATA [ 7:0 ];
    wire [ 2:0 ]    ActionAddr;
    wire            WriteAction;
    wire            ReadAction;
    reg  [ 10:0 ]   conf;

    assign { ReadAction, WriteAction, ActionAddr } = conf;

    always @ (*) begin
        //io
        case(State)
            default     :   conf = { 2'b00, 8'b0     };
            S_READ      :   conf = { 2'b10, ADDR     };
            S_WRITE     :   conf = { 2'b01, ADDR_old };
        endcase
    end

    // Registers
    uart_regs   regs(
        .clk            (   HCLK            ),
        .wb_rst_i       (   ~HRESETn        ),
        .wb_addr_i      (   ActionAddr      ),
        .wb_dat_i       (   WriteData       ),
        .wb_dat_o       (   ReadData        ),
        .wb_we_i        (   WriteAction     ),
        .wb_re_i        (   ReadAction      ),
        .modem_inputs   (   { UART_CTS, UART_DSR, UART_RI, UART_DCD }   ),
        .stx_pad_o      (   UART_STX        ),
        .srx_pad_i      (   UART_SRX        ),
        .rts_pad_o      (   UART_RTS        ),
        .dtr_pad_o      (   UART_DTR        ),
        .int_o          (   UART_INT        ),
        .baud_o         (   UART_BAUD       )
    );

endmodule

Основные особенности реализации

  • в качестве базового решения взят проект [L8], который, в свою очередь, основан на [L7, L9];
  • по сравнению с базовым решением из проекта исключен код, связанный с шиной Wishbone. Он заменен на соответствующий интерфейсный модуль для AHB-Lite (mfp_ahb_lite_uart16550) [S1];
  • весь заимствованный из базового проекта код размещен в каталоге uart16550 [S2];
  • модуль mfp_ahb_lite_uart16550 включен в состав системы на постоянной основе, опция MFP_USE_DUPLEX_UART [S3] в файле mfp_ahb_lite_matrix_config.vh определяет доступность сигналов UART_SRX и UART_STX;
  • для уже имеющихся сигналов UART_RX и UART_TX предусмотрен следующий порядок использования:
    UART_RX — вне зависимости от режима используется только для загрузки прошивки в память системы, как это и было ранее (модуль mfp_uart_receiver, к UART16550 не относится). Если опция MFP_USE_DUPLEX_UART не активна, то UART_TX используется для передачи данных от UART16550 наружу, прием в этом случае не доступен. Если опция MFP_USE_DUPLEX_UART активна, то UART_TX — не используются, для передачи данных из/в mfp_ahb_lite_uart16550 служат UART_SRX и UART_STX [S4];
  • вывод интерфейса управления модемом на уровень top модуля не производился. Соответствующие линии доступны в интерфейсе модуля ahb_lite_uart16550 и могут быть использованы при необходимости (UART_RTS, UART_CTS, UART_DTR, UART_DSR, UART_RI, UART_DCD) [S5];
  • сигнала прерывания (UART_INT) подключен к входу hw3 (сигнал SI_Int[3]) для режимов обратной совместимости и векторного. И к eic5 (сигнал EIC_input[5]) для контроллера внешних прерываний [S6];
  • при запуске в режиме симуляции линия UART_STX замкнута на UART_SRX [S7];
  • работоспособность полученного решения в режиме симуляции проверена с помощью Modelsim;
  • работоспособность на железе проверена на плате Terasic DE10-Lite [L10];
  • в качестве примера работы с UART16550 написаны две программы: 05_uart [S8] и 08_uart_irq [S9]: после сброса выполняется настройка uart (8n1, 115200), отправка приветствия после чего код каждого принятого символа отображается на LED и 7-сегментных индикаторах. При запуске на плате каждый принимаемый по uart символ отправляется обратно;
  • отладка разработанного модуля доступна "в отрыве" от кода mipsfpga+ в рамках проекта [L11];
  • для программного конфигурирования модуля следует использовать документацию базового проекта, [D1]. Рекомендую бегло ознакомиться с ней, чтобы лучше понимать приводимый ниже пример;
  • работа с шиной AHB-Lite детально описана в [D2];

Пример

Порядок запуска

  • проверить, что в файле mfp_ahb_lite_matrix_config.vh установлена следующая настройка [S3]:

    `define MFP_USE_DUPLEX_UART

  • перейти в каталог с программой: mipsfpga-plus/programs/05_uart [S8], либо 08_uart_irq [S9];
  • в файле main.c установить [S10]:

    #define RUNTYPE    SIMULATION

  • выполнить сборку программы и ее запуск в симуляторе:

    02_compile_and_link.bat
    05_generate_verilog_readmemh_file.bat
    06_simulate_with_modelsim.bat

  • после завершения работы скрипта симуляции нажать "нет", чтобы предотвратить закрытие симулятора;
  • то, как работает этот код "на железе", видно на заглавной gif-ке.

Описание программы и конфигурации системы

  • так как пример 08_uart_irq предусматривает помимо настройки самого UART16550 еще и использование генерируемых им прерываний, то ниже рассматривается именно эта конфигурация;
  • директивы, обеспечивающие работу с регистрами контроллера вынесены в заголовочный файл uart16550.h [S11];
  • при запуске производится настройка UART16550 [S12]:

    void uartInit(uint16_t divisor)
    {
    // 8n1 uart mode
    MFP_UART_LCR = MFP_UART_LCR_8N1;
    // Divisor Latches access enable
    MFP_UART_LCR |= MFP_UART_LCR_LATCH;
    // Divisor LSB
    MFP_UART_DLL = divisor & 0xFF;
    // Divisor MSB
    MFP_UART_DLH = (divisor >> 8) & 0xff;
    // Divisor Latches access disable
    MFP_UART_LCR &= ~MFP_UART_LCR_LATCH;
    //enable Received Data available interrupt
    MFP_UART_IER = MFP_UART_IER_RDA;
    //set 4 byte Receiver FIFO Interrupt trigger level
    MFP_UART_FCR = MFP_UART_FCR_ITL4;
    }

  • настройка прерываний [S13] (детально разобрана в [L12]):

    void mipsInterruptInit(void)
    {
    // Status.BEV  0 - vector interrupt mode
    mips32_bicsr (SR_BEV);
    // Cause.IV,   1 - special int vector (0x200)
    // where 0x200 - base for others interrupts;
    mips32_biscr (CR_IV);
    // get IntCtl reg value
    uint32_t intCtl = mips32_getintctl();
    // set interrupt table vector spacing (0x20 in our case)
    // see exceptions.S for details
    mips32_setintctl(intCtl | INTCTL_VS_32);
    // interrupt enable, HW3 unmasked
    mips32_bissr (SR_IE | SR_HINT3); 
    }

  • обработка прерывания предполагает проверку того, что оно вызвано именно наличием данных во входящем FIFO [S14]:

    // uart interrupt handler
    void __attribute__ ((interrupt, keep_interrupts_masked)) __mips_isr_hw3 ()
    {
    // Receiver Data available interrupt handler
    if(MFP_UART_IIR & MFP_UART_IIR_RDA)
        uartReceive();
    }

  • после чего следует их чтение (до тех пор, пока FIFO не опустеет) и вывод [S15]:

    void uartReceive(void)
    {
    // is there something in receiver fifo?
    while (MFP_UART_LSR & MFP_UART_LSR_DR)
    {
        // data receive
        uint8_t data = MFP_UART_RXR;
        receivedDataOutput(data);
    
        #if   RUNTYPE == HARDWARE
        uartTransmit(data);
        #endif
    }
    }

  • в случае, если работа идет на "железе", то все полученные данные отправляются обратно [S16]:
    void uartTransmit(uint8_t data)
    {
    // waiting for transmitter fifo empty
    while (!(MFP_UART_LSR & MFP_UART_LSR_TFE)); 
    // data transmit
    MFP_UART_TXR = data;
    }
  • ниже представлен результат работы программы. Так как при настройке модуля мы выставили режим срабатывания прерывания после приема 4х символов, то передача на время прерывается для приема. Оставшиеся символы получены нами после еще одного такого же прерывания, но которое уже было вызвано не наличием 4х символов в очереди, а не пустой очередью и таймаутом (контроллер понял, что больше символов не будет и сообщил, что очередь не пустая);

    image

  • для программы 05_uart прием выполняется уже после завершения передачи, все это время принятые данные ждут в FIFO приемника:

    image

Благодарности

Автор выражает благодарность коллективу переводчиков учебника Дэвида Харриса и Сары Харрис «Цифровая схемотехника и архитектура компьютера», компании Imagination Technologies за академическую лицензию на современное процессорное ядро и персонально Юрию Панчулу YuriPanchul за его работу по популяризации MIPSfpga.

Ссылки

[L1] — Цифровая схемотехника и архитектура компьютера;
[L2] — Как начать работать с MIPSfpga;
[L3] — Проект MIPSfpga-plus на github;
[L4] — Wikipedia: UART;
[L5] — Wikipedia: UART16550;
[L6] — Google: UART16550;
[L7] — Проект freecores/uart16550;
[L8] — Проект olofk/uart16550;
[L9] — Проект opencores/UART 16550 core;
[L10] -FPGA плата Terasic DE10-Lite;
[L11] — Проект ahb_lite_uart16550;
[L12] — MIPSfpga и прерывания;

Документация

[D1] — UART IP Core Specification;
[D2] — MIPS32 microAptiv UP Processor Core AHB-Lite Interface;

Изображения и таблицы

[P1] — Работа примера на отладочной плате;
[P2] — Работа в режиме обработки прерывания UART;
[P3] — Работа путем периодического опроса регистра;

Ссылки на исходный код

[S1] — Модуль mfp_ahb_lite_uart16550;
[S2] — Каталог mipsfpga-plus/uart16550;
[S3] — Опция MFP_USE_DUPLEX_UART;
[S4] — Сигналы UART_SRX и UART_STX;
[S5] — Интерфейс управления модемом;
[S6] — Подключение сигнала прерывания;
[S7] — Сигналы UART в режиме симуляции;
[S8] — Пример использования '05_uart';
[S9] — Пример использования '08_uart_irq';
[S10] — Настройка режима работы программы-примера;
[S11] — Заголовочный файл uart16550.h;
[S12] — Программная настройка UART16550;
[S13] — Настройка прерываний;
[S14] — Обработка прерывания UART;
[S15] — Чтение полученных данных UART;
[S16] — Обратная отправка принятых данных;

Автор: Stanislav Zhelnio

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js