The code here converts ARM CMSIS System View Description (SVD) files to a set of Forth equates for a cross compiler. These equates are in the same format as the existing "Special Function Register" files usually named sfr<cpu>.fth in the Cortex target directory. Processing SVD files automatically to produce Forth source significantly reduces the effort involved in porting Forth to a CPU. This code requires the use of the file XML.fth supplied with VFX Forth. In the cross compiler, the files are in the ArmCortex/SVDparser or compiler/SVDparser directory, depending on the edition of the compiler.
include XML.fth
include SVDconv.fth
The converted SVD (XML) to Forth is output using TYPE
and EMIT
and friends. This can be copied to an editor
or the output redirected to a file. See SVDconv
at the
end of this chapter if you want the output sent to a file.
Otherwise just include
the SVD file.
include ATSAM4SD32C.svd
svdconv ATSAM4SD32C.svd sfrATSAM4SD32C.raw.fth
Because of lack of structure namespaces in Forth, most register names will need a prefix to identify the peripheral the register applies to. In the SVD specification, this is achieved using the prependToName tag, e.g.
<prependToName>HSMCI_</prependToName>
for an MMC/SD card peripheral. MPE standard practice is to use a short prefix such as "mmc.". To use these, add a tag line such as
<mpePrepends>mmc.</mpePrepends>
to each peripheral section before the first register definition.
Search for <peripheral> when editing the SVD file. You can select
whether or not text is added to register names using the
directives -prepends
, +prependC
and +prependMPE
below. The default is +prependMPE
.
The resulting output may/will require a little hand editing.
Many SVD files repeat the register definitions for identical
or similar peripherals such as UARTs. By using the same prefices,
you can remove the repetitions. Providing that the values are
the same, redefinitions of EQU
ates are harmless, albeit
noisy and inelegant. Personally, I prefer to remove the
duplicates as it reduces the size of the file.
These functions rely on the ANS Forth word
TIME&DATE ( -- s m h dd mm yyyy )
and the non-standard
DOW ( -- dow, 0=Sun)
to get the day of the week.
create days$ \ -- addr
String containing 3 character text for the days of the week.
create months \ -- addr
String containing 3 character text for the months.
: .dow \ dow --
Display day of week.
: .2r \ n --
Display n as a two digit number with leading zeros.
: .4r \ n --
Display n as a four digit number with leading zeros.
: .Time&Date \ s m h dd mm yy --
Display the system time
The format is:
hh:mm:ss dd Mmm yyyy
: .AnsiDate \ zone --
Display the day of week, date and time. If zone is 0
GMT (system time) is displayed, otherwise local time
is displayed. The format is:
dow, hh:mm:ss dd Mmm yyyy [GMT]
SVD files are fairly simple as XML files go, so data is extracted from tags and saved in global or dynamic buffers.
1024 buffer: opfile$ \ -- addr
If set, the name of the output file. 16 bit Word counted string.
32 kb buffer: description$ \ -- addr
Contents of the <description> tags. 16 bit Word counted string.
1024 buffer: name$ \ -- addr
Contents of the <name> tags. 16 bit Word counted string.
1024 buffer: revision$ \ -- addr
Contents of the <revision> tags. 16 bit Word counted string.
1024 buffer: endian$ \ -- addr
Contents of the <endian> tags. 16 bit Word counted string.
1024 buffer: mpupresent$ \ -- addr
Contents of the <mpupresent> tags. 16 bit Word counted string.
1024 buffer: fpupresent$ \ -- addr
Contents of the <fpupresent> tags. 16 bit Word counted string.
1024 buffer: nvicPrioBits$ \ -- addr
Contents of the <nvicPrioBits> tags. 16 bit Word counted string.
1024 buffer: vendorSystickConfig$ \ -- addr
Contents of the <vendorSystickConfig> tags. 16 bit Word counted string.
1024 buffer: prepend$ \ -- addr
Holds the text prepended to each register name. This text
is cleared by each peripheral tag. 16 bit Word counted string.
1024 buffer: clusName$ \ -- addr
Holds <name> from last <cluster>. 16 bit Word counted string.
0 value valueData \ -- int
Integer from the contents of the <value> tag.
0 value dimData \ -- int
Integer from the contents of the <dim> tag.
0 value inPeriph? \ -- flag
True between <peripheral> and </peripheral>.
0 value PeriphHead? \ -- flag
True when the peripheral header has been written.
0 value BaseAddrValue \ -- addr
Peripheral base address from the <baseAddress> tag.
0 value AddrOffset \ -- offset
Offset from the <AddressOffset> tag.
0 value ClusterOffset \ -- addr
Offset from the <AddressOffset> tag inside a cluster.
0 value InReg? \ -- flag
True between <register> and </register>.
0 value RegWritten? \ -- flag
True when the register data has been written.
0 value InCluster? \ -- flag
True between <cluster> and </cluster> tags.
0 value ClusterWritten? \ -- flag
True when the cluster header data has been written.
0 value prependC? \ -- flag
True to prepend C text to register names. This text is
provided by lines in the SVD file such as
<prependToName>HSMCI_</prependToName>
0 value prependMPE? \ -- flag
True to prepend MPE text to register names. This text is
provided by lines (that you must add) in the SVD file such as
<mpePrepends>mmc.</mpePrepends>
: -contents \ --
Discard the contents of the last tag.
: saveTagData \ spad dest --
Save the tag data (as given by the spad), to a given buffer.
: saveContents \ dest --
Save the last tag's contents to a buffer.
: contents>val \ -- int
Convert the last tag's contents to an integer.
: \n? \ caddr len -- caddr len flag
Return true if the string starts with '\n'.
: ?ignLineFeed \ caddr len -- caddr' len
Step over \n, ignore following LF or CRLF.
: .periphDesc \ buffer len --
Output a string translating \n pairs.
: ?.PeriphHead \ -- ; SFP002
Output the peripheral header if not already done.
: ?.%s \ --
Output the value of the <dim> tag.
: .regDesc \ buffer len --
Output a string translating \n pairs.
: ?.Register \ -- ; SFP002
Output the register line if not already done.
$xx equ regname \ description
: ?.Cluster \ -- ; SFP002
Output the cluster line if not already done.
\ Cluster: <name> - <description>
: .MPEhead \ --
Display an MPE header.
Tags that are handled when encountered are words of the same
name in the inputTags
vocabulary. There are separate
entries for the opening and closing tags. This can be useful
and is also much quieter when debugging. The tag handlers
have no overall stack effect. Many closing tags just save
the contents, others display data.
also inputTags definitions : description ( -- ) ; : /description \ -- description$ saveContents ; : cpu \ -- ." \ " opfile$ w@ if opfile$ w$. ." - " then description$ w$. ; : /cpu \ -- cr cr ." ((" .MPEhead cr cr cr description$ w$. cr ." CPU: " name$ w$. ." , " revision$ w$. cr ." Endian: " endian$ w$. cr ." MPU: " mpupresent$ w$. cr ." FPU: " fpupresent$ w$. cr ." NVIC priority bits: " nvicPrioBits$ w$. cr ." Vendor Systick Config: " vendorSystickConfig$ w$. cr ." ))" contents dup sdrop snew ; : value ( -- ) ; : /value ( -- ) contents>val to ValueData ; : dim ( -- ) ; : /dim ( -- ) contents>val to dimData ; : name ( -- ) ; : /name ( -- ) name$ saveContents ; : revision ( -- ) ; : /revision ( -- ) revision$ saveContents ; : endian ( -- ) ; : /endian ( -- ) endian$ saveContents ; : mpuPresent ( -- ) ; : /mpuPresent ( -- ) mpuPresent$ saveContents ; : fpuPresent ( -- ) ; : /fpuPresent ( -- ) fpuPresent$ saveContents ; : nvicPrioBits ( -- ) ; : /nvicPrioBits ( -- ) nvicPrioBits$ saveContents ; : vendorSystickConfig ( -- ) ; : /vendorSystickConfig ( -- ) vendorSystickConfig$ saveContents ; : interrupt ( -- ) ?.PeriphHead ; : /interrupt \ -- base @ >r decimal cr ." #" ValueData #16 + . ." equ " name$ w$. s" _IRQn" type r> base ! -contents ; : peripheral ( -- ) 1 to InPeriph? 0 to PeriphHead? 0 prepend$ c! ; : /peripheral ( -- ) ?.PeriphHead 0 to InPeriph? 0 to PeriphHead? -contents ; \ SFP001 : prependToName ( -- ) ; : /prependToName ( -- ) prependC? if prepend$ saveContents else -contents then ; : mpePrepends ( -- ) ; : /mpePrepends ( -- ) prependMPE? if prepend$ saveContents else -contents then ; : baseaddress ( -- ) ; : /baseaddress ( -- ) contents>val to BaseAddrValue ; : addressOffset ( -- ) ; : /addressOffset ( -- ) contents>val to AddrOffset ; : registers ( -- ) ?.PeriphHead ; : /registers ( -- ) ( key drop ) -contents ; : register ( -- ) ?.Cluster 1 to inReg? 0 to RegWritten? 0 -> dimData ; : /register ( -- ) ?.Register 0 to inReg? 0 to RegWritten? -contents ; : cluster ( -- ) InCluster? abort" Nested clusters not supported" 1 -> InCluster? 0 -> ClusterWritten? 0 -> AddrOffset 0 -> ClusterOffset ; : /cluster ( -- ) cr ." \ </cluster>" 0 -> InCluster? 0 -> ClusterWritten? 0 -> ClusterOffset -contents ; : fields ( -- ) s" /fields>" skipPast ; (( : fields ( -- ) ?.Cluster ?.Register ; : /fields ( -- ) -contents ; )) previous definitions
The word SVDconv
below redirects output to a named
file.
: -prepends \ --
Do not prepend text to register names.
: +prependC \ --
The C names are prepended to register names. These are found
in the SVD file in the form:
<prependToName>HSMCI_</prependToName>
: +prependMPE \ --
The MPE names are prepended to register names. These should be
in the SVD file in the form:
<mpePrepends>mmc.</mpePrepends>
Note that you will have to add these lines to the SVD file yourself.
: SVDconv \ -- ; SVDconv <ifile> <ofile>
Includes an SVD file and writes the output to a named file.
Use in the form:
SVDconv input.svd output.fth