UART adapter
Description
This example shows:
How to develop a custom UART based on the UART adapter provided in GVSOC blocks.
How to instantiate it in a custom board.
How to access it from C code.
The UART model is instantiated inside a custom board on which simulation is launched to interact with it.
The UART model is relying on the UART adapter for the low-level interaction with the chip UART interface, so that it can just focus on sending and receiving bytes:
More information about modeling UART peripherals can be found in the GVSOC developer documentation, under “tutorials / 20 - How to model a UART peripheral”.
More information about the UART adapters can be found in the GVSOC developer documentation, under “Models / Utility blocks / UUART adapters”.
Code
#
# Copyright (C) 2022 GreenWaves Technologies
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import argparse
import gvsoc.runner
import gvsoc.systree
import gap.gap9.bare_board
import my_uart
# This is the Gapy descriptor for our board which will make it available on the command-line.
class Target(gvsoc.runner.Target):
gapy_description = "GAP9 bare board with uart"
def __init__(self, parser, options):
super(Target, self).__init__(parser, options,
model=MyBoard)
# This our custom board with our UART model.
# It just starts from a bare board which contains only a chip and the reset, and then add
# and connects our uart model
class MyBoard(gap.gap9.bare_board.Gap9BareBoard):
def __init__(self, parent: gvsoc.systree.Component, name: str, parser: argparse.ArgumentParser,
options: list):
super(MyBoard, self).__init__(parent, name, parser, options)
uart = my_uart.MyUart(self, 'uart')
# Note that we also specify the pads where the UART is connected.
# This is optional and is used to activate padframe model, which will check if pads
# are well configured from the SW.
self.get_chip().o_UART(0, uart.i_UART(), rx=60, tx=61, rts=62, cts=63)
#
# Copyright (C) 2022 GreenWaves Technologies
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
import gvsoc.systree
import utils.uart.uart_adapter
# This is the generator for our UART model.
# It allows the upper generator like the board instantiating it and binding it
class MyUart(gvsoc.systree.Component):
def __init__(self, parent: gvsoc.systree.Component, name: str):
super(MyUart, self).__init__(parent, name)
# Include the adapter. This will make sure the adapter sources are compiled
utils.uart.uart_adapter.UartAdapter(self)
# Our source code. This will make sure it gets compile when we compile GVSOC for our
# board.
self.add_sources(['my_uart.cpp'])
# Declare a user property so that the baudrate can be configured from the command-line
# Such properties can be displayed with gapy command "target_properties" and the baudrate
# can be set with option --target-property uart/baudrate=1000000
baudrate = self.declare_user_property(
name='baudrate', value=115200, cast=int,
description='Specifies uart baudrate in bps'
)
# Store this use property as component property so that the C++ code can get it
self.add_property('baudrate', baudrate)
# Create a method to let upper generator easily connect this component
def i_UART(self) -> gvsoc.systree.SlaveItf:
# This interface is the UART input interface. The name here must match the one used
# in the model when instantiating the adapter.
return self.slave_itf(name='uart', signature='uart')
/*
* Copyright (C) 2020 GreenWaves Technologies, SAS, ETH Zurich and
* University of Bologna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "vp/vp.hpp"
#include "vp/itf/uart.hpp"
#include "utils/uart/uart_adapter.hpp"
class MyUart : public vp::Component
{
public:
MyUart(vp::ComponentConf &conf);
// This can be defined to get called when the reset is changing so that we can properly
// reset our internal state
void reset(bool active) override;
private:
// The chip can send us these commands on the RX channel
typedef enum
{
UART_RX_CMD_PRINT=1,
UART_RX_CMD_LOOPBACK=2,
} uart_rx_cmd_e;
// Internal FSM state. First wait for a command, then data length and then the data.
typedef enum
{
UART_RX_WAITING_CMD,
UART_RX_WAITING_LEN,
UART_RX_WAITING_DATA,
} uart_rx_state_e;
// This gets called when the UART adapter receives a byte
static void rx_event_handler(vp::Block *__this, vp::TimeEvent *event);
// Handle a print commad
void handle_cmd_print();
// Handle a loopback command
void handle_cmd_loopback();
// TO dump debug messages about this component activity
vp::Trace trace;
// UART baudrate retrieved from component config
uint64_t baudrate;
// UART adapter taking care of low-level UART protocol.
// We get the buffered one to get an infinite FIFO
UartAdapterBuffered adapter;
// Events used to register to the UART adapter the callback for receving bytes
vp::TimeEvent rx_event;
// Our internal state to sample commands and handle them
uart_rx_state_e rx_state;
// Current command
uint8_t command;
// Current number of data bytes which remains to be received
int pending_data_len;
// The data currently received
std::queue<uint8_t> rx_data;
};
MyUart::MyUart(vp::ComponentConf &config)
: vp::Component(config),
adapter(this, this, "adapter", "uart"),
rx_event(this, &MyUart::rx_event_handler)
{
this->traces.new_trace("trace", &this->trace, vp::DEBUG);
// The baudrate is retrieved from the component configuration, which can be modified from
// the command line. Propagate it to the UART adapter
int baudrate = this->get_js_config()->get("baudrate")->get_int();
this->adapter.baudrate_set(baudrate);
// Register to the UART adapter our callback to receive bytes
this->adapter.rx_ready_event_set(&this->rx_event);
}
void MyUart::reset(bool active)
{
if (active)
{
// In case of a reset we need to clear any on-going activity to be able to
// receive again a command.
// Note that the adapter is automically reset.
this->rx_state = UART_RX_WAITING_CMD;
while (this->rx_data.size() > 0)
{
this->rx_data.pop();
}
}
}
void MyUart::rx_event_handler(vp::Block *__this, vp::TimeEvent *event)
{
MyUart *_this = (MyUart *)__this;
// We get here when the UART adapter receives a byte. Since the callback means at least
// once byte was received, we need to pop all of them
while (_this->adapter.rx_ready())
{
uint8_t byte = _this->adapter.rx_pop();
// Check our internal state to see what we do with the byte
switch (_this->rx_state)
{
case UART_RX_WAITING_CMD:
// Store the command and go on with the data length
_this->trace.msg(vp::Trace::LEVEL_DEBUG, "Received command (cmd: 0x%x)\n", byte);
_this->command = byte;
_this->rx_state = UART_RX_WAITING_LEN;
break;
case UART_RX_WAITING_LEN:
// Store the data length and go on with the command data
_this->trace.msg(vp::Trace::LEVEL_DEBUG, "Received data length (len: 0x%x)\n", byte);
_this->pending_data_len = byte;
_this->rx_state = UART_RX_WAITING_DATA;
break;
case UART_RX_WAITING_DATA:
// Enqueue the data byte until the length is 0
_this->trace.msg(vp::Trace::LEVEL_DEBUG, "Received data byte (byte: 0x%x)\n", byte);
_this->rx_data.push(byte);
_this->pending_data_len--;
if (_this->pending_data_len == 0)
{
// In case we are done, execute the command and go back to initial state
// to process the next command.
switch (_this->command)
{
case UART_RX_CMD_PRINT:
_this->trace.msg(vp::Trace::LEVEL_INFO, "Handling command print\n");
_this->handle_cmd_print();
break;
case UART_RX_CMD_LOOPBACK:
_this->trace.msg(vp::Trace::LEVEL_INFO, "Handling command loopback\n");
_this->handle_cmd_loopback();
break;
}
_this->rx_state = UART_RX_WAITING_CMD;
}
break;
}
}
}
void MyUart::handle_cmd_print()
{
while(this->rx_data.size() > 0)
{
printf("%c", this->rx_data.front());
this->rx_data.pop();
}
}
void MyUart::handle_cmd_loopback()
{
// The loopback command needs to forward all data bytes to the TX line.
// Since we are using the buffered adapter with an infinite FIFO, we can directly push all
// of them in the same cycle.
while(this->rx_data.size() > 0)
{
this->adapter.tx_send_byte(this->rx_data.front());
this->rx_data.pop();
}
}
extern "C" vp::Component *gv_new(vp::ComponentConf &config)
{
return new MyUart(config);
}
/*
* Copyright (C) 2023 GreenWaves Technologies, ETH Zurich and University of Bologna
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <stdio.h>
#include <pmsis.h>
#define CMD_PRINT 1
#define CMD_LOOPBACK 2
int main(int argc, char**argv)
{
pi_device_t uart;
// Open the UART at 1Mbps
pi_pad_function_set(PI_PAD_060, PI_PAD_FUNC0);
pi_pad_function_set(PI_PAD_061, PI_PAD_FUNC0);
struct pi_uart_conf conf;
pi_uart_conf_init(&conf);
conf.enable_tx = 1;
conf.enable_rx = 1;
conf.uart_id = 0;
conf.word_size = PI_UART_WORD_SIZE_8_BITS;
conf.baudrate_bps = 1000000;
pi_open_from_conf(&uart, &conf);
if (pi_uart_open(&uart)) return -1;
// First send a print command
uint8_t buffer[] = { CMD_PRINT, 6, 'h', 'e', 'l', 'l', 'o', '\n' };
pi_uart_write(&uart, buffer, sizeof(buffer));
// Then a loopback command
uint8_t tx_buffer_loopback[] = { CMD_LOOPBACK, 6, 'l', 'o', 'o', 'p', '\n', 0 };
uint8_t rx_buffer_loopback[6];
pi_evt_t event;
// Enqueue the read first asynchronously so that we are ready to receive the
// string back once our send is finished
pi_uart_read_async(&uart, rx_buffer_loopback, sizeof(rx_buffer_loopback),
pi_evt_sig_init(&event));
pi_uart_write(&uart, tx_buffer_loopback, sizeof(tx_buffer_loopback));
pi_evt_wait(&event);
printf("Received from loopback: %s\n", rx_buffer_loopback);
// Close Uart
pi_uart_close(&uart);
return 0;
}
# Copyright (c) 2022 GreenWaves Technologies SAS
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions are met:
#
# 1. Redistributions of source code must retain the above copyright notice,
# this list of conditions and the following disclaimer.
# 2. Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in the
# documentation and/or other materials provided with the distribution.
# 3. Neither the name of GreenWaves Technologies SAS nor the names of its
# contributors may be used to endorse or promote products derived from
# this software without specific prior written permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
# AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
# ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
# CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
cmake_minimum_required(VERSION 3.19)
###############################################################################
# Panel Control
###############################################################################
set(TARGET_NAME "uart")
set(TARGET_SRCS example.c)
###############################################################################
# CMake pre initialization
###############################################################################
include($ENV{GAP_SDK_HOME}/utils/cmake/setup.cmake)
project(${TARGET_NAME} C ASM)
add_executable(${TARGET_NAME} ${TARGET_SRCS})
# This baudrate option will be propagated to our uart custom model
list(APPEND GAPY_RUNNER_ARGS
--target-property uart/baudrate=1000000)
# Add our local model folder to GAPY target folders so that it can find our
# custom board
list(APPEND GAPY_RUNNER_ARGS
--target-dir ${CMAKE_CURRENT_SOURCE_DIR}/models )
###############################################################################
# CMake post initialization
###############################################################################
setupos(${TARGET_NAME})
Usage
To be able to use a custom board and model, gvsoc must be recompiled for the custom board.
For that we must first give to the SDK an additional path where it can find our board and model:
export GVSOC_MODULES="$GVSOC_MODULES;$PWD/models"
Then GVSOC must be recompiled for our board:
make all GVSOC_TARGETS=my_board