SVD file converter

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 EQUates are harmless, albeit noisy and inelegant. Personally, I prefer to remove the duplicates as it reduces the size of the file.

Time and date

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]

Data buffers and variables

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.

Tag handling

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

File version

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