blob: 5cd11cf67f58552f5527edb720133c8b574ddc4c [file] [log] [blame]
/*
* Copyright 2023 Google LLC
*
* 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.
*/
// ISP live stream mode with user defined resolution: 320 x 240 in grayscale.
#include "hw/top_matcha/ip/ml_top/data/ml_top_regs.h" // Generated.
#include "hw/top_matcha/sw/autogen/top_matcha.h"
#include "sw/device/examples/testdata/kelvin_model_ml_bin.h" // Generated.
#include "sw/device/lib/arch/device.h"
#include "sw/device/lib/camera_hm01b0.h"
#include "sw/device/lib/dif/dif_isp_wrapper.h"
#include "sw/device/lib/dif/dif_ml_top.h"
#include "sw/device/lib/dif/dif_rv_plic.h"
#include "sw/device/lib/dif/dif_rv_timer.h"
#include "sw/device/lib/dif/dif_uart.h"
#include "sw/device/lib/runtime/irq.h"
#include "sw/device/lib/runtime/log.h"
#include "sw/device/lib/runtime/print.h"
#include "sw/device/lib/spi_display/LCD_Driver.h"
#include "sw/device/lib/spi_display/spi_display.h"
#include "sw/device/lib/testing/test_framework/check.h"
#include "sw/device/lib/testing/test_framework/ottf_test_config.h"
#include "sw/device/lib/testing/test_framework/status.h"
#include "sw/device/lib/util.h"
#define TOP_MATCHA_RAM_ML_DMEM_IMG0_OFFSET_ADDR 0x00300000
#define TOP_MATCHA_RAM_ML_DMEM_IMG1_OFFSET_ADDR 0x00340000
#define TOP_MATCHA_RAM_ML_DMEM_OUT_OFFSET_ADDR 0x00380000
#define TOP_MATCHA_RAM_ML_DMEM_CMD_OFFSET_ADDR 0x003FF000
#define IMAGE_FRAME_SIZE 320 * 240
OTTF_DEFINE_TEST_CONFIG();
static dif_isp_wrapper_t isp_wrapper;
static dif_ml_top_t ml_top;
static dif_rv_plic_t plic_smc;
static dif_uart_t smc_uart;
static dif_spi_host_t spi_host2;
static spi_display_t spi_display;
static dif_tlul_mailbox_t tlul_mailbox;
static dif_rv_timer_t rv_timer;
static uint16_t rgb565_buffer[LCD_WIDTH * LCD_HEIGHT];
static PAINT paint_ctx;
static char paint_str[128];
const uint32_t delay_time_micros = 10;
static volatile bool isp_frame_done = false;
static uint32_t img_offset = TOP_MATCHA_RAM_ML_DMEM_IMG0_OFFSET_ADDR;
static volatile bool ml_top_finish_done = false;
static void handle_isp_wrapper_isp_irq() {
sFONT *font = &Font16;
// Set address of image buffer.
mmio_region_t ml_dmem =
mmio_region_from_addr(TOP_MATCHA_ML_TOP_DMEM_BASE_ADDR);
mmio_region_write32(ml_dmem, TOP_MATCHA_RAM_ML_DMEM_CMD_OFFSET_ADDR,
img_offset);
// Start up Kelvin core running the model
ml_top_finish_done = false;
CHECK_DIF_OK(dif_ml_top_release_ctrl_en(&ml_top));
LOG_INFO("[SMC] Kelvin starts running model.");
// Inform the host that a new frame is available.
uint8_t *image_addr = (uint8_t *)(TOP_MATCHA_RAM_ML_DMEM_BASE_ADDR +
img_offset);
LOG_INFO("[SMC] Start of a frame, address %u.", image_addr);
// Generate an RGB565 frame, and write it to the display.
// We start drawing at an offset based on the height of our font,
// so that scores can be displayed.
for (int i = 0; i < LCD_WIDTH * LCD_HEIGHT; ++i) {
uint8_t y = *(image_addr + i);
uint16_t rgb565 =
((y >> 3) & 0x1F) << 11 | ((y >> 2) & 0x3F) << 5 | ((y >> 3) & 0x1F);
rgb565_buffer[i] = __builtin_bswap16(rgb565);
}
LCD_ClearToBufferWindow(&spi_display,
rgb565_buffer + (LCD_WIDTH * font->Height), 0,
font->Height, LCD_WIDTH, LCD_HEIGHT);
// Wait until Kelvin finishes. Reset Kelvin core.
while (!ml_top_finish_done) {
asm volatile("wfi");
}
LOG_INFO("[SMC] Kelvin finished running model.");
// Log model score
mmio_region_t ml_model_out_base =
mmio_region_from_addr(TOP_MATCHA_RAM_ML_DMEM_BASE_ADDR +
TOP_MATCHA_RAM_ML_DMEM_OUT_OFFSET_ADDR);
const int8_t model_out_val0 = mmio_region_read8(ml_model_out_base, 0);
const int8_t model_out_val1 = mmio_region_read8(ml_model_out_base, 1);
LOG_INFO("[SMC] Score 0: %d, Score 1: %d.", model_out_val0, model_out_val1);
// Render an RGB565 buffer with the score text.
memset(paint_ctx.Image, 0, LCD_WIDTH * font->Height * sizeof(uint16_t));
memset(paint_str, 0, sizeof(paint_str));
base_snprintf(paint_str, sizeof(paint_str), "Score 0: %d, Score 1: %d",
model_out_val0, model_out_val1);
Paint_DrawString_EN(&paint_ctx, 0, 0, paint_str, font, BLACK, WHITE);
LCD_ClearToBufferWindow(&spi_display, paint_ctx.Image, 0, 0, LCD_WIDTH,
font->Height);
if (img_offset == TOP_MATCHA_RAM_ML_DMEM_IMG0_OFFSET_ADDR) {
img_offset = TOP_MATCHA_RAM_ML_DMEM_IMG1_OFFSET_ADDR;
} else {
img_offset = TOP_MATCHA_RAM_ML_DMEM_IMG0_OFFSET_ADDR;
}
}
static void handle_isp_wrapper_isrs(const dif_rv_plic_irq_id_t interrupt_id) {
// NOTE: This initialization is superfluous, since the `default` case below
// is effectively noreturn, but the compiler is unable to prove this.
switch (interrupt_id) {
case kTopMatchaPlicIrqIdIspWrapperIsp:
isp_frame_done = true;
CHECK_DIF_OK(dif_isp_wrapper_isp_irq_done(&isp_wrapper));
break;
case kTopMatchaPlicIrqIdIspWrapperMi:
CHECK_DIF_OK(dif_isp_wrapper_mi_irq_done(&isp_wrapper));
break;
default:
LOG_FATAL("ISR is not implemented!");
}
}
static void handle_ml_top_isr(const dif_rv_plic_irq_id_t interrupt_id) {
switch (interrupt_id) {
case kTopMatchaPlicIrqIdMlTopFinish:
ml_top_finish_done = true;
break;
case kTopMatchaPlicIrqIdMlTopFinish | kTopMatchaPlicIrqIdMlTopFault:
LOG_ERROR("ML core raised fault interrupt.");
test_status_set(kTestStatusFailed);
default:
LOG_FATAL("ISR is not implemented!");
test_status_set(kTestStatusFailed);
}
CHECK_DIF_OK(dif_ml_top_reset_ctrl_en(&ml_top));
CHECK_DIF_OK(dif_ml_top_irq_acknowledge_all(&ml_top));
}
void ottf_external_isr(void) {
// Claim the IRQ by reading the Ibex specific CC register.
dif_rv_plic_irq_id_t interrupt_id;
CHECK_DIF_OK(dif_rv_plic_irq_claim(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
&interrupt_id));
// Check if the interrupted peripheral is ISP WRAPPER.
top_matcha_plic_peripheral_smc_t peripheral_id =
top_matcha_plic_interrupt_for_peripheral_smc[interrupt_id];
CHECK(peripheral_id == kTopMatchaPlicPeripheralIspWrapper ||
peripheral_id == kTopMatchaPlicPeripheralCamI2c ||
peripheral_id == kTopMatchaPlicPeripheralSpiHost2 ||
peripheral_id == kTopMatchaPlicPeripheralTlulMailboxSmc ||
peripheral_id == kTopMatchaPlicPeripheralMlTop,
"Unexpected peripheral in ISR: %d", peripheral_id);
switch (peripheral_id) {
case kTopMatchaPlicPeripheralCamI2c: {
CHECK_DIF_OK(camera_hm01b0_irq_handler(interrupt_id));
break;
}
case kTopMatchaPlicPeripheralIspWrapper:
handle_isp_wrapper_isrs(interrupt_id);
break;
case kTopMatchaPlicPeripheralTlulMailboxSmc: {
CHECK_DIF_OK(spi_display_smc_mailbox_irq_handler(&spi_display));
break;
}
case kTopMatchaPlicPeripheralSpiHost2: {
CHECK_DIF_OK(spi_display_spi_irq_handler(&spi_display));
break;
}
case kTopMatchaPlicPeripheralMlTop: {
handle_ml_top_isr(interrupt_id);
break;
}
default:
LOG_FATAL("Peripheral is not implemented!");
}
// Complete the IRQ by writing the IRQ source to the Ibex specific CC
// register.
CHECK_DIF_OK(dif_rv_plic_irq_complete(&plic_smc, kTopMatchaPlicTargetIbex0Smc,
interrupt_id));
}
/*
* Configures all the relevant interrupts in PLIC_SMC.
*/
static void plic_smc_configure_irqs(dif_rv_plic_t *plic) {
CHECK_DIF_OK(camera_hm01b0_irq_init(plic, kTopMatchaPlicTargetIbex0Smc));
// Set IRQ priorities to MAX
CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
plic, kTopMatchaPlicIrqIdIspWrapperIsp, kDifRvPlicMaxPriority));
CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
plic, kTopMatchaPlicIrqIdIspWrapperMi, kDifRvPlicMaxPriority));
CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
plic, kTopMatchaPlicIrqIdMlTopFinish, kDifRvPlicMaxPriority));
CHECK_DIF_OK(dif_rv_plic_irq_set_priority(plic, kTopMatchaPlicIrqIdMlTopFault,
kDifRvPlicMaxPriority));
// Set Ibex IRQ priority threshold level
CHECK_DIF_OK(dif_rv_plic_target_set_threshold(
plic, kTopMatchaPlicTargetIbex0Smc, kDifRvPlicMinPriority));
// Enable IRQs in PLIC
// Enable ISP IRQs
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
plic, kTopMatchaPlicIrqIdIspWrapperIsp, kTopMatchaPlicTargetIbex0Smc,
kDifToggleEnabled));
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
plic, kTopMatchaPlicIrqIdIspWrapperMi, kTopMatchaPlicTargetIbex0Smc,
kDifToggleEnabled));
// Enable Mailbox IRQs
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(
plic, kTopMatchaPlicIrqIdTlulMailboxSmcRtirq,
kTopMatchaPlicTargetIbex0Smc, kDifToggleEnabled));
CHECK_DIF_OK(dif_rv_plic_irq_set_priority(
plic, kTopMatchaPlicIrqIdTlulMailboxSmcRtirq, 1));
// Enable ML core IRQs
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFinish,
kTopMatchaPlicTargetIbex0Smc,
kDifToggleEnabled));
CHECK_DIF_OK(dif_rv_plic_irq_set_enabled(plic, kTopMatchaPlicIrqIdMlTopFault,
kTopMatchaPlicTargetIbex0Smc,
kDifToggleEnabled));
}
void _ottf_main(void) {
test_status_set(kTestStatusInTest);
CHECK_DIF_OK(dif_isp_wrapper_init(
mmio_region_from_addr(TOP_MATCHA_ISP_WRAPPER_BASE_ADDR), &isp_wrapper));
CHECK_DIF_OK(dif_rv_plic_init(
mmio_region_from_addr(TOP_MATCHA_RV_PLIC_SMC_BASE_ADDR), &plic_smc));
LOG_INFO("Configure isp into CAM live mode.");
CHECK_DIF_OK(dif_uart_init(
mmio_region_from_addr(TOP_MATCHA_SMC_UART_BASE_ADDR), &smc_uart));
CHECK_DIF_OK(
dif_uart_configure(&smc_uart, (dif_uart_config_t){
.baudrate = kUartBaudrate,
.clk_freq_hz = kClockFreqPeripheralHz,
.parity_enable = kDifToggleDisabled,
.parity = kDifUartParityEven,
.tx_enable = kDifToggleEnabled,
.rx_enable = kDifToggleDisabled,
}));
base_uart_stdout(&smc_uart);
LOG_INFO("[SMC] ISP_WRAPPER test start.");
// Configure Mailbox.
CHECK_DIF_OK(dif_tlul_mailbox_init(
mmio_region_from_addr(TOP_MATCHA_TLUL_MAILBOX_SMC_BASE_ADDR),
&tlul_mailbox));
CHECK_DIF_OK(dif_tlul_mailbox_irq_set_enabled(
&tlul_mailbox, kDifTlulMailboxIrqRtirq, kDifToggleEnabled));
CHECK_DIF_OK(dif_tlul_mailbox_irq_set_enabled(
&tlul_mailbox, kDifTlulMailboxIrqWtirq, kDifToggleEnabled));
CHECK_DIF_OK(dif_tlul_mailbox_irq_set_enabled(
&tlul_mailbox, kDifTlulMailboxIrqEirq, kDifToggleEnabled));
// Init ML_TOP
CHECK_DIF_OK(dif_ml_top_init(
mmio_region_from_addr(TOP_MATCHA_ML_TOP_CORE_BASE_ADDR), &ml_top));
CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFinish,
kDifToggleEnabled));
CHECK_DIF_OK(dif_ml_top_irq_set_enabled(&ml_top, kDifMlTopIrqFault,
kDifToggleEnabled));
dif_ml_top_reset_ctrl_en(&ml_top);
LOG_INFO("[SMC] ML_TOP INIT.");
// Fill the memory with zeroes.
mmio_region_t ml_dmem =
mmio_region_from_addr(TOP_MATCHA_ML_TOP_DMEM_BASE_ADDR);
for (int i = 0; i < TOP_MATCHA_ML_TOP_DMEM_SIZE_BYTES / sizeof(uint32_t);
++i) {
mmio_region_write32(ml_dmem, i * sizeof(uint32_t), 0);
}
LOG_INFO("[SMC] Finished initializing ML DMEM");
// Load Kelvin binary.
// TODO: update kelvin binary from greg
uint32_t *kelvin_bin_uint32 = (uint32_t *)kelvin_bin;
for (int i = 0; i < kelvin_bin_len / sizeof(uint32_t); ++i) {
mmio_region_write32(ml_dmem, i * sizeof(uint32_t), kelvin_bin_uint32[i]);
}
LOG_INFO("[SMC] Finished loading Kelvin binary");
mmio_region_write32(ml_dmem, TOP_MATCHA_RAM_ML_DMEM_CMD_OFFSET_ADDR,
TOP_MATCHA_RAM_ML_DMEM_IMG0_OFFSET_ADDR);
mmio_region_write32(ml_dmem,
TOP_MATCHA_RAM_ML_DMEM_CMD_OFFSET_ADDR + sizeof(uint32_t),
TOP_MATCHA_RAM_ML_DMEM_OUT_OFFSET_ADDR);
LOG_INFO("[SMC] Finished setting Kelvin input/output addresses");
plic_smc_configure_irqs(&plic_smc);
irq_global_ctrl(true);
irq_external_ctrl(true);
// Initialize the SPI host and display.
CHECK_DIF_OK(dif_spi_host_init(
mmio_region_from_addr(TOP_MATCHA_SPI_HOST2_BASE_ADDR), &spi_host2));
dif_spi_host_config_t config = {
.spi_clock = kClockFreqSpiDisplayHz,
.peripheral_clock_freq_hz = kClockFreqCpuHz,
};
CHECK_DIF_OK(dif_spi_host_configure(&spi_host2, config));
CHECK_DIF_OK(dif_spi_host_output_set_enabled(&spi_host2, /*enabled=*/true));
CHECK_DIF_OK(spi_display_init(&spi_display, &spi_host2, &tlul_mailbox));
CHECK_DIF_OK(spi_display_irq_init(&spi_display, &plic_smc,
kTopMatchaPlicIrqIdSpiHost2SpiEvent,
kTopMatchaPlicTargetIbex0Smc));
LOG_INFO("ISP interrupt finshed setup!");
// Initialize camera
CHECK_DIF_OK(camera_hm01b0_init());
// Initialize the written address of the frame
memset((void *)TOP_MATCHA_RAM_ML_DMEM_BASE_ADDR +
TOP_MATCHA_RAM_ML_DMEM_IMG0_OFFSET_ADDR,
0, IMAGE_FRAME_SIZE);
memset((void *)TOP_MATCHA_RAM_ML_DMEM_BASE_ADDR +
TOP_MATCHA_RAM_ML_DMEM_IMG1_OFFSET_ADDR,
0, IMAGE_FRAME_SIZE);
CHECK_DIF_OK(camera_hm01b0_set_default_registers(
kCameraHm01b0Resolution320x240, kCameraHm01b0FrameRate60Fps));
CHECK_DIF_OK(camera_hm01b0_set_test_pattern(kCameraHm01b0TestPatternNone));
// Set ISP into 320x320 mode and start camera
CHECK_DIF_OK(dif_isp_wrapper_set_ml_mem_byp_320x240_en(&isp_wrapper));
LOG_INFO("ISP finshed setup Cam Ctrl!");
Paint_NewImage(&paint_ctx, LCD_WIDTH, LCD_HEIGHT, ROTATE_0, MIRROR_NONE);
dif_rv_timer_tick_params_t tick_params;
CHECK_DIF_OK(dif_rv_timer_init(
mmio_region_from_addr(TOP_MATCHA_RV_TIMER_SMC_BASE_ADDR), &rv_timer));
CHECK_DIF_OK(dif_rv_timer_approximate_tick_params(kClockFreqPeripheralHz,
1000, &tick_params));
CHECK_DIF_OK(dif_rv_timer_set_tick_params(&rv_timer, 0, tick_params));
CHECK_DIF_OK(
dif_rv_timer_counter_set_enabled(&rv_timer, 0, kDifToggleEnabled));
while (true) {
uint64_t counter_start, counter_end;
CHECK_DIF_OK(dif_rv_timer_counter_read(&rv_timer, 0, &counter_start));
CHECK_DIF_OK(camera_hm01b0_set_frame_count(1));
CHECK_DIF_OK(
camera_hm01b0_set_streaming_mode(kCameraHm01b0StreamingModeCounter));
while (!isp_frame_done) {
asm volatile("wfi");
}
isp_frame_done = false;
handle_isp_wrapper_isp_irq();
CHECK_DIF_OK(dif_rv_timer_counter_read(&rv_timer, 0, &counter_end));
LOG_INFO("Capture + eval + render took %d ms",
(uint32_t)(counter_end - counter_start));
}
}