In the previous post, Getting started with Fast Models and RTX, we created a custom Cortex-M33-based platform using the IP Components already available in the Fast Models IP Library. Most times you would want to add your own IP to the design and create a platform to test your software. In this post, we explore how to do that. If you want to add some of your own components in the platform, then you can export the assembled Cortex-M33 platform as a SystemC component (i.e subsystem) using System Canvas and then connect the generated subsystem with your own SystemC IP using AMBA-PV TLM-2.0 Compliant ports.
We will learn how-to do all the following:
You can download the code for Cortex-M33 subsystem and RTX app from our GitHub repo.
This part builds on the Cortex-M33 custom platform that we built in the previous blog post. If you have not looked at that I suggest you read that first. Once you are done reading that then return to proceed from here.
In this section, we make following changes to our System Canvas project:
Let's look at the detailed steps to achieve the above targets:
Now, let us have a look at the Fast Model generated SystemC Module. Fast Models generated code has filenames, classes, and components generated with "scx_" prefix. Fast Models SystemC framework uses the namespace "scx". You can recognize these patterns in the following code snippets.
In your working directory, you can see the following files and directories:
$ ls -l total 42992 drwxr-x--- 3 navkha01 navkha01 4096 Mar 2 16:50 Linux64-Release-GCC-6.4 -rw-r----- 1 navkha01 navkha01 1213 Mar 2 16:50 MyTopComponent.lisa -rw-r----- 1 navkha01 navkha01 298136 Mar 2 16:50 MyTopComponent.sgcanvas -rw-r----- 1 navkha01 navkha01 7166 Jan 8 10:16 MyTopComponent.sgproj -rw-rw-r-- 1 navkha01 navkha01 1588016 Apr 18 2019 libMAXCOREInitSimulationEngine.3.so -rw-r--r-- 1 navkha01 navkha01 1221177 Jan 26 2017 libSDL2-2.0.so.0.4.0 -rw-rw-r-- 1 navkha01 navkha01 39562768 Dec 10 18:23 libarmctmodel.so -rw-rw-r-- 1 navkha01 navkha01 68176 Nov 18 20:37 libarmfastmodelsanalytics.1.1.0.so -rw-r--r-- 1 navkha01 navkha01 1260624 May 15 2018 librui_5.2.0.x64.so $ ls -l Linux64-Release-GCC-6.4/ total 11796 -rw-r----- 1 navkha01 navkha01 14759 Jan 8 10:19 MyTopComponent_Linux64-Release-GCC-6.4_Makefile.sg drwxr-x--- 2 navkha01 navkha01 36864 Mar 2 16:50 gen -rw-rw-r-- 1 navkha01 navkha01 1588016 Apr 18 2019 libMAXCOREInitSimulationEngine.3.so -rwxr-x--- 1 navkha01 navkha01 4958288 Feb 28 14:01 libMyTopComponent-Linux64-Release-GCC-6.4.so -rw-r--r-- 1 navkha01 navkha01 1221177 Jan 26 2017 libSDL2-2.0.so.0.4.0 -rw-rw-r-- 1 navkha01 navkha01 68176 Nov 18 20:37 libarmfastmodelsanalytics.1.1.0.so -rw-r--r-- 1 navkha01 navkha01 1260624 May 15 2018 librui_5.2.0.x64.so -rw-r----- 1 navkha01 navkha01 2603702 Mar 2 16:50 libscx-MyTopComponent-Linux64-Release-GCC-6.4.a -rw-r----- 1 navkha01 navkha01 311056 Jan 8 10:19 libscx.a
Open the file "Linux64-Release-GCC-6.4/gen/scx_evs_MyTopComponent.h", it looks like this:
/* * * * This is an automatically generated file. Do not edit. * * Copyright 2011 - 2019 ARM Limited. * * All rights reserved. */ #ifndef scx_evs_MyTopComponent__H #define scx_evs_MyTopComponent__H /* Includes */ #include <sg/IncludeMeFirst.h> #if defined (SG_TARGET_SCX_CADI) || ! defined(SG_BUILDING_DSO_MyTopComponent) #if ! defined(SC_INCLUDE_DYNAMIC_PROCESSES) #define SC_INCLUDE_DYNAMIC_PROCESSES #endif #include <sg/SystemCInclude.h> #endif #include <eslapi/eslapi_stdint.h> #include <scx/scx.h> /* Includes from the 'includes' section of the protocols */ #line 9 "/work/tests/fmProduct/11.9/install/FastModelsPortfolio_11.9/LISA/AMBAPVProtocol.lisa" #include <amba_pv.h> #line 34 "./Linux64-Release-GCC-6.4/gen/scx_evs_MyTopComponent.h" namespace MyTopComponent_NMS { /* Forward declarations */ class AMBAPV_EB; class AMBAPVSignal_EB; /* Slave port classes TLM2 draft1*/ class sc_slave_port_class_slave_counter_irq_in: public amba_pv::signal_slave_base<bool> { public: sc_slave_port_class_slave_counter_irq_in(const std::string &portname); ~sc_slave_port_class_slave_counter_irq_in(); AMBAPVSignal_EB *sg_port; void set_state(int export_id, const bool& state); }; /* Slave port classes TLM2 final */ /* Master port clases TLM2 final */ class sc_master_port_class_m_port_to_external_counter: public amba_pv::amba_pv_master_base { public: sc_master_port_class_m_port_to_external_counter(const std::string &portname); ~sc_master_port_class_m_port_to_external_counter(); AMBAPV_EB *sg_port; void invalidate_direct_mem_ptr(int socket_id, sc_dt::uint64 start_range, sc_dt::uint64 end_range); }; /* System C wrapper class */ class scx_evs_MyTopComponent: public sc_core::sc_module, public scx::scx_evs_base { /* Ports */ public: /* Slave ports TLM2 final */ /* Slave ports TLM2 draft 1 and TLM 1 (Signal port interfaces) */ amba_pv::signal_slave_export<bool> slave_counter_irq_in; sc_slave_port_class_slave_counter_irq_in sc_slave_port_slave_counter_irq_in; /* Master ports TLM2 final */ amba_pv::amba_pv_master_socket<64> m_port_to_external_counter; sc_master_port_class_m_port_to_external_counter sc_master_port_m_port_to_external_counter; /* Master ports TLM2 draft 1 and TLM 1 (Signal port interfaces ) */ /* Construction */ explicit scx_evs_MyTopComponent(sc_core::sc_module_name); ~scx_evs_MyTopComponent() override; /* sc_object override-ables */ const char * kind() const override; /* Implementation */ protected: /* sc_module override-ables */ void before_end_of_elaboration() override; void end_of_elaboration() override; void start_of_simulation() override; void end_of_simulation() override; }; #if defined(SG_TARGET_SCX_CADI) void scx_evs_MyTopComponent_create_sim_thread(); #endif } /* namespace MyTopComponent_NMS */ using MyTopComponent_NMS::scx_evs_MyTopComponent; #endif /* defined(scx_evs_MyTopComponent__H) */
In the generated SystemC component, on line numbers 80 and 85, there is one master and one slave port respectively that we added into our subsystem.
Now we have a SystemC subsystem with AMBA-PV TLM ports exposed that can be connected to another SystemC IP with corresponding AMBA-PV TLM ports. We are ready to write a standalone SystemC Module that acts as an external IP with corresponding AMBA-PV TLM ports exposed. We can then connect our generated Cortex-M33 SystemC subsystem with the external IP.
Now let us write a simple SystemC Counter that we can connect with our exported SystemC subsystem and also write sc_main to make appropriate connections and simulate the system.
Before we write the Counter IP, we need to specify the it.
In our implementation, we declare a sc_module named "external_counter".
Here is the full implementation of the SystemC external Counter IP:
/* name: external_counter.h * desc: Example external counter * Copyright Arm Ltd 2020 */ #ifndef EXTERNAL_COUNTER_H #define EXTERNAL_COUNTER_H #include <sg/IncludeMeFirst.h> #include <sg/SystemCInclude.h> #include <iostream> #include "amba_pv.h" #include "sockets/amba_pv_slave_socket.h" using namespace amba_pv; template<unsigned int BUSWIDTH = 64> class external_counter: public sc_core::sc_module, public amba_pv_slave_base<BUSWIDTH> { public: /* Constructor/Destructor */ external_counter(sc_core::sc_module_name); ~external_counter(); /* Ports */ amba_pv_slave_socket<BUSWIDTH> amba_pv_s; signal_master_port<bool> irq_out; /* Registers */ static constexpr unsigned control_addr {0x0}; static constexpr unsigned status_addr {0x4}; static constexpr unsigned counter_addr {0x8}; uint32_t control{0}; unsigned control_ctrl_bit_mask{1}; uint32_t status{0}; unsigned status_clr_bit_mask{1}; uint32_t counter{0}; /* sc_object overridables */ virtual const char * kind() const; /* User-layer interface */ virtual amba_pv_resp_t read(int, const sc_dt::uint64 &, unsigned char *, unsigned int, const amba_pv_control *, sc_core::sc_time &); virtual amba_pv_resp_t write(int, const sc_dt::uint64 &, unsigned char *, unsigned int, const amba_pv_control *, unsigned char *, sc_core::sc_time &); virtual bool get_direct_mem_ptr(int, tlm::tlm_command, const sc_dt::uint64 &, const amba_pv_control *, tlm::tlm_dmi &); /* Debug interface */ virtual unsigned int debug_read(int, const sc_dt::uint64 &, unsigned char *, unsigned int, const amba_pv_control *); virtual unsigned int debug_write(int, const sc_dt::uint64 &, unsigned char *, unsigned int, const amba_pv_control *); private: sc_core::sc_event checkCounterEvent{}; void triggerIrq(); }; template<unsigned int BUSWIDTH> inline external_counter<BUSWIDTH>::external_counter(sc_core::sc_module_name name): sc_core::sc_module(name), amba_pv_slave_base<BUSWIDTH>((const char *) name), amba_pv_s("amba_pv_s"), irq_out("irq_out") { /* Bindings... */ amba_pv_s(* this); SC_HAS_PROCESS(external_counter); SC_METHOD(triggerIrq); sensitive << checkCounterEvent; dont_initialize(); } /* * Destructor. */ template<unsigned int BUSWIDTH> inline external_counter<BUSWIDTH>::~external_counter() { } /* * Returns the kind string of this memory. */ template<unsigned int BUSWIDTH> inline const char * external_counter<BUSWIDTH>::kind() const { return ("external_counter"); } /* * Completes a read transaction */ template<unsigned int BUSWIDTH> inline amba_pv_resp_t external_counter<BUSWIDTH>::read(int socket_id, const sc_dt::uint64 & addr, unsigned char * data, unsigned int size, const amba_pv_control * ctrl, sc_core::sc_time & t) { std::cout << this->name() << ": call to read() for addr=" << addr << std::endl; switch(addr) { case control_addr: memcpy(data, &control, sizeof(uint32_t)); break; case status_addr: memcpy(data, &status, sizeof(uint32_t)); break; case counter_addr: memcpy(data, &counter, sizeof(uint32_t)); break; default: std::cout << this->name() << ": address not found" << std::endl; } return (AMBA_PV_OKAY); } /* * Completes a write transaction. */ template<unsigned int BUSWIDTH> inline amba_pv_resp_t external_counter<BUSWIDTH>::write(int socket_id, const sc_dt::uint64 & addr, unsigned char * data, unsigned int size, const amba_pv_control * ctrl, unsigned char * strb, sc_core::sc_time & t) { std::cout << this->name() << ": call to write() for addr=" << addr << " value=" << *((uint32_t*)data) << std::endl; switch(addr) { case control_addr: memcpy(&control, data, sizeof(uint32_t)); if(control & control_ctrl_bit_mask) { checkCounterEvent.notify(sc_core::sc_time(counter, sc_core::SC_NS)); } break; case status_addr: memcpy(&status, data,sizeof(uint32_t)); if(status & status_clr_bit_mask) { control = 0; status = 0; irq_out.set_state(false); } break; case counter_addr: memcpy(&counter, data,sizeof(uint32_t)); break; default: std::cout << this->name() << ": address not found" << std::endl; } return (AMBA_PV_OKAY); } /* * Non-intrusive debug read transaction. */ template<unsigned int BUSWIDTH> inline unsigned int external_counter<BUSWIDTH>::debug_read(int socket_id, const sc_dt::uint64& addr, unsigned char * data, unsigned int length, const amba_pv_control * ctrl) { std::cout << this->name() << ": call to debug_read(), addr=0x" << std::hex << addr << std::endl; return 0; } /* * Non-intrusive debug write transaction. */ template<unsigned int BUSWIDTH> inline unsigned int external_counter<BUSWIDTH>::debug_write(int socket_id, const sc_dt::uint64 & addr, unsigned char * data, unsigned int length, const amba_pv_control * ctrl) { std::cout << this->name() << ": call to debug_write(), addr=0x" << std::hex << addr << std::endl; return 0; } /* * Requests DMI access to the specified address and returns a reference to a * DMI descriptor. */ template<unsigned int BUSWIDTH> inline bool external_counter<BUSWIDTH>::get_direct_mem_ptr(int socket_id, tlm::tlm_command command, const sc_dt::uint64 & addr, const amba_pv_control * ctrl, tlm::tlm_dmi & dmi_data) { return false; } template<unsigned int BUSWIDTH> inline void external_counter<BUSWIDTH>::triggerIrq() { std::cout << "IrqOut " << std::endl; status = 1; irq_out.set_state(true); } #endif /* EXTERNAL_COUNTER_H */
To assemble our platform, we write sc_main.
Here is the complete main.cpp:
/* * Copyright (c) 2020 Arm Limited. All rights reserved. */ #include "scx_evs_MyTopComponent.h" #include <amba_pv.h> #include "external_counter.h" int sc_main(int argc, char *argv[]) { double quantum = 10000.0; double latency = 100.0; scx::scx_initialize("MyTopComponent", scx::scx_get_default_simcontrol()); MyTopComponent_NMS::scx_evs_MyTopComponent cortex_m33_subsystem("cortex-m33-subsystem"); external_counter<64> ext_counter("ext_counter"); cortex_m33_subsystem.m_port_to_external_counter(ext_counter.amba_pv_s); ext_counter.irq_out(cortex_m33_subsystem.slave_counter_irq_in); /* Simulation quantum, i.e. seconds to run per quantum */ tlm::tlm_global_quantum::instance().set(sc_core::sc_time(quantum / 100000000.0, sc_core::SC_SEC)); /* Simulation minimum synchronization latency */ scx::scx_set_min_sync_latency(latency / 100000000.0); /* From command-line options... */ scx::scx_parse_and_configure(argc, argv, ""); sc_core::sc_start(); return 0; }
This completes our custom platform creation. Now, lets proceed to update our RTX app to program the external IP and then receive an interrupt when the external counter expires.
In our RTX app, we need to configure the external timer by writing a counter value into "counter" register and then writing "1" into "control" register to start the counter. We also register an interrupt handler for interrupt number 30 using NVIC_SetVector() and NVIC_EnableIRQ() calls.
When program the external counter hits the OneShotTimer callback, we setup the external counter in that callback and then wait for the interrupt to be generated. As soon as the interrupt is generated, out interrupt handler externalInterruptHandler() is called that clears the interrupt by writing "1" into the "status register".
Here is the updated app/timer.c file:
#include <stdio.h> #include "cmsis_os2.h" #include "ARMCM33.h" #include "core_cm33.h" static const uint32_t external_counter_base_addr = 0x21000000; static uint32_t * const external_counter_base = (uint32_t*)external_counter_base_addr; static const uint32_t counter_value = 5000000; static const uint32_t counter_enable = 1; static const uint32_t counter_register_addr_offset = 2; static const uint32_t control_register_addr_offset = 0; static const uint32_t status_register_addr_offset = 1; void periodicTimerCallback(void *argument); void oneShotTimerCallback(void *argument); void app_main (void *argument); osTimerId_t tidPeriodic; osTimerId_t tidOneShot; void periodicTimerCallback(void *argument) { printf("Periodic Timer hit\n"); } void oneShotTimerCallback(void *argument) { printf("One shot Timer hit\n"); uint32_t * const counter_addr = (external_counter_base + counter_register_addr_offset); *counter_addr = counter_value; uint32_t * const control_addr = (external_counter_base + control_register_addr_offset); *control_addr = counter_enable; } void app_main (void *argument) { tidPeriodic = osTimerNew(periodicTimerCallback, osTimerPeriodic, NULL, NULL); tidOneShot = osTimerNew(oneShotTimerCallback, osTimerOnce, NULL, NULL); osTimerStart(tidPeriodic, 100); osTimerStart(tidOneShot, 2000); while(1); } void externalInterruptHandler(void) { printf("External interrupt handler called \n"); uint32_t * const status_addr = (external_counter_base + status_register_addr_offset); *status_addr = 1; /* write 1 to clean interrupt and status register */ } void setupExternalInterrupt(unsigned irq_no) { NVIC_SetVector(irq_no, (uint32_t)(&externalInterruptHandler)); NVIC_EnableIRQ(irq_no); __enable_irq(); printf("External Interrupt Enabled \n"); } int main (void) { osKernelInitialize(); osThreadNew(app_main, NULL, NULL); setupExternalInterrupt(30); osKernelStart(); }
Final step is to run the compiled app on the custom platform. Change directory to "system/systemc-plt" and execute the platform binary as follows:
on Linux
$ ./Cortex-M33-example.x -a ../../software/test.axf
on Windows
Cortex-M33-example.exe -a ..\..\software\test.axf
Here is the command and the output:
$ ./Cortex-M33-example.x -a ../../software/test.axf Fast Models [11.9.47 (Dec 10 2019)] Copyright 2000-2019 ARM Limited. All Rights Reserved. External Interrupt Enabled Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit One shot Timer hit ext_counter: call to write() for addr=8 value=5000000 ext_counter: call to write() for addr=0 value=1 IrqOut External interrupt handler called ext_counter: call to write() for addr=4 value=1 Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit Periodic Timer hit ^C Stopping simulation... Info: /OSCI/SystemC: Simulation stopped by user.
This completes our demo on how-to generate a custom Cortex-M33 based subsystem, attach our own AMBA-PV TLM-2.0 compliant model to the subsystem and update our RTX App to run on the new platform.
To discuss Fast Models and other simulation models that Arm provides, please head over to our discussion forum:
[CTAToken URL = "https://community.arm.com/developer/tools-software/f/simulation-models" target="_blank" text="Models Discussion Forum" class ="green"]