TLC(Target Language Compiler)是一种为转换为目标语言而存在的额解释性语言,其目的就是将模型中编译出来的rtw文件转换为目标代码(C/C++等)。与M语言类似,既可以写成脚本文件,也能够作为函数存在,都是解释性语言,更相似的是它们都提供具有强大功能的内建函数库。
18.1 TLC的作用
- 支持模型针对通用或特定目标硬件的代码生成功能;
- 为S函数模块提供代码生成功能,可以让用户自己增加支持代码生成的模块;
- 在代码生成过程中,生成不依赖S函数模块的自定义过程代码。
Simulink中提供了很多既有TLC文件,如果擅自修改可能导致Simulink Coder功能性错误,故Mathworks提倡用户尽量不要修改TLC。但是如果能熟练掌握TLC的运行机制和编写方法,不仅不会伤害SImulink的功能,还可以巧妙利用TLC语言实现更多的自动化代码生成功能。
18.2 TLC的语法
TLC是一种以单个%打头的关键字为命令,空格之后跟参数的脚本语言,自身包含了流控制语法、内建函数、关键字和常用命令。
18.2.1 基本语法
- [text|%
]* text表示字符串,将原原本本地展开到输出流中。在%<>之中的是TLC变量,通过%<>作用将变量的执行结果显示到输出流中。
- %keyword[arguement1,arguement2,...]
%keyword表示TLC语言中的命令符,[arguement1,arguement2,...]则表示这个命令符所操作的参数。
如:
% assign Str = "Hello World"
%warning、%error、%trace命令可以将其后的变量或字符串的内容输出。
>> tlc text.tlc Warning: Simulink User
>> tlc text.tlc Error: Simulink User Main program: ==> [00] text.tlc:(1) 错误使用 tlc_new Error: Errors occurred - aborting 出错 tlc (line 88) tlc_new(varargin{:});
若用%trace命令代替%warning命令显示信息,只有在执行TLC文件时在最后增加-v或者-v1才能将%trace后的信息显示出来。
TLC语言有两个内建宏TLC_TRUE=1和TLC_FALSE=0,在TLC语言的编写中会经常用到。
18.2.2 常用指令
注释
单行注释:双百分号%%
多行注释:/% comment %/
变量内容扩展
即通过%<>操作符将其内容扩展到输出流中。
%%text.tlc %assign input1 = 3 %assign input2 = 5 %warning %+ % = %
>> tlc text.tlc Warning: 3 + 5 = 8
注意:%<>不能嵌套使用。
条件分支
%if expression
%elseif expression
%else
%endif
例:
%%text.tlc %assign var = 2 %if ISEQUAL(var,1) %warning evering is OK. %else %warning var should be 1 but now there is something wrong. %endif
>> tlc text.tlc Warning: var should be 1 but now there is something wrong.
在内建函数ISEQUAL中不需要使用%<>。
开关分支
%switch expression
%case expression
%break
%default
%break
%endswitch
每个%case分支后必须跟%break才能起到真正的选择作用,否则将执行下一个%case语句。
%%text.tlc %assign data = [1,2,3,4,5] %switch TYPE(data) %case "Number" %warning Type is Number. %break %case "String" %warning Type is String. %break %case "Vector" %warning Type is Vector. %break %case "Matrix" %warning Type is Matrix. %break %endswitch
>> tlc text.tlc Warning: Type is Vector.
循环
含%foreach、%roll、%for 3种常用方式。
(1)%foreach
%foreach loopIdx = iterNum
xxxxx
%endforeach
上述语句将loopIdx作为循环体句柄变量控制循环进行,从0开始,每次增加1,一直循环到iterMum-1为止。每个%foreach需要使用%endforeach来终止。
在循环体中可以使用%continue终止当前循环进入下一个循环,或者使用%break直接跳出循环。
%%text.tlc %assign data = [1,2,3,4,5] %foreach idx = 5 %if ISEQUAL(idx,1) %continue %elseif ISEQUAL(idx,4) %break %endif %warning data[%] = %[idx]> %endforeach
>> tlc text.tlc Warning: data[0] = 1 Warning: data[2] = 3 Warning: data[3] = 4
(2)%roll
TLC提供%roll这种循环方式主要是为Simulink模块端口信号相关代码生成定义时使用的。当模块的输入/输出信号是多维时,需要通过%roll对信号的每一维进行循环,使生成的代码同在Simulink环境中进行仿真时具有相同的维数。
%roll
%endroll
例:y=2×u
/% roll normally used in block tlc files to roll the inport/outport/parameter to access each dimension of them. %/ /* %Block: % = ["U", "Y"] %roll sigIdx = RollRegions, lcv = RollThreshold, block,... "Roller", rollVars %assign y = LibBlockOutputSignal(0, "", lcv, sigIdx) %assign u = LibBlockInputSignal(0, "", lcv, sigIdx) %*/ %assign rollVars = % * 2; %endroll
%assign rollVars = ["U", "Y", "P"]定义rollVars变量存储循环体所涉及的模块要素,U表示输入端口,Y表示输出端口,在带有参数的模块中,P代表模块的参数。rollVars将被传递给Roller,设置模块输入/输出或参数中每一维roll的结构体。
%roll sigIdx = RollRegions定义了循环体的循环变量sigIdx,RollRegions是自动计算出来的模块输入/输出或参数的维数向量,如20维输入信号的RollRegions为[0:19],sigIdx按照这个向量进行逐一循环。
lcv = RollThreshold中lcv是循环控制变量(loop control variable),从RollThreshold获取值,表示当信号维数小于此数值时不生成for循环语句,而逐条生成语句,只有信号或参数的维数大于等于此数值时才生成for循环。TLC全局变量RollThreshold的值可以在Configuration Parameter→Code Generation→Advanced parameters中的Loop unrolling threshold设定,默认值为5。
LibBlockOutputSignal(portIdx, ucv, lcv, sigIdx)函数包含4个参数,portIdx表示模块输入端口的索引号(对于使能或触发端口,可以使用字符串enable和trigger获取),ucv通常为空,lcv和sigIdx同前面定义。该函数使用TLC库函数获取模块的输入/输出的参数中第sigIdx维所对应的变量,再通过%
在循环体的结尾,使用%endroll作为终止符号。
(3)for
%for的使用方法与%foreach基本相同,并且加入了对于是否执行body以外部分进行roll的判断。
%for ident1 = const-exp1, const-exp2, ident2 = const-exp3
Code section1
%body
Code section2
%endbody
Code section3
%endfor
当const-exp2为非零值时,则Code section1/2/3所有代码就会执行一次,并且ident2会接收const-exp3的值;当const-exp2为0时则仅执行Code section2段,其他段不执行,并且其以ident1为循环变量进行循环,ident2为空值。
%%text.tlc %for idx = 3, 0, str = "yes" %warning OK? %body %warning Answer is %. %endbody %warning Over %endfor
>> tlc text.tlc Warning: Answer is . Warning: Answer is . Warning: Answer is .
%%text.tlc %for idx = 3, 1, str = "yes" %warning OK? %body %warning Answer is %. %endbody %warning Over %endfor
>> tlc text.tlc Warning: OK? Warning: Answer is yes . Warning: Over
文件流
TLC语言使用%openfile创建文件流缓存或打开一个文件,%selectfile选中或激活一个存在的文件流缓存或文件,%closefile关闭一个文件流缓存或文件。
%openfile streamId = "filename.txt" mode {open for writing}
%selectfile streanId {select an open file}
%closefile streamId {close an open file}
%openfile在打开文件时,第2个参数mode可以是a或者w,表示以“追加”或者“重写”的方式创建文件或缓存块,默认重写。当所填的文件名不存在时,则使用StreamId为名创建一个缓存块buffer进行后续操作。在%openfile和%closefile之间的内容将被写入到缓存块buffer中作为变量保存起来,并可以使用%
%%text.tlc %openfile buffer = "my_flow_control.txt" This is the first time flow control is used. %closefile buffer
>> tlc text.tlc
StreamID所表示的参数有2个内建流变量:NULL_FILE和STDOUT,分别表示无输出和使用终端输出文件流内容。%selectfile同STDOUT联合使用时,也可以将字符串输出到MATLAB的Command Window中。
%%text.tlc
%selectfile STDOUT
text to be placed in the 'buffer' variable.
>> tlc text.tlc text to be placed in the 'buffer' variable.
记录
TLC记录相当于M语言或C语言中的结构体类型,但形式不同,是构成rtw文件的基本元素,格式为record_item{Name Value}。Name是字符串格式,按照字母顺序进行排列;Value既可以是字符串也可以是数据类型,这个数据可以是scalar、向量或矩阵类型。
(1)创建一个新纪录
使用%createrecord命令创建一个新纪录。
%createrecord NEW_RECORD {foo 1;SUB_RECORD {foo 2}}
NEW_RECORD为新创建的纪录名,{ }内是其所属的子纪录。用一层次的子纪录使用";"隔开,子纪录再创建嵌套子纪录时使用record_item {Name Value}方式。
可以通过最上层记录名访问其子纪录内容。
%assign var1 = NEW_RECORD.foo
%assign var2 = NEW_RECORD.SUB_RECORD.foo
也可以创建具有全局访问权限的纪录(::表示全局标量标识符)
%createrecord::NEW_RECORD {foo 1;SUB_RECORD {foo 2}}
(2)追加纪录
使用%addtorecord命令向已经存在的额记录中追加新的子纪录。
%addtorecord OLD_RECORD NEW_FIELD_NAME NEW_FIELD_VALUE
3个参数,第一个参数为追加纪录的对象,第2个和第3个为子纪录的名字和值。
%%text.tlc %createrecord NEW_RECORD {foo 1;SUB_RECORD {foo 2}} %addtorecord NEW_RECORD str "I love Simulink" %warning %
>> tlc text.tlc Warning: { SUB_RECORD { foo 2 }; foo 1; str "I love Simulink" }
(3)合并纪录
使用%mergerecord命令合并既存的两个纪录。
%mergerecord OLD_RECORD NEW_RECORD
合并后的内容保存在第一个参数中,第二个参数的值不变。
当新旧两个记录中同样层次下存在相同名的纪录时,则保留这项纪录各自的值不合并。
%%text.tlc
%createrecord NEW_RECORD {foo 1;SUB_RECORD {foo 2}} %createrecord NEW_RECORD2 {str "I love Simulink"} %mergerecord NEW_RECORD NEW_RECORD2 %warning NEW_RECORD = %NEW_RECORD2 = % %% another demo %createrecord NEW_RECORD2 {foo 12} %mergerecord NEW_RECORD NEW_RECORD2 %warning NEW_RECORD = %
%createrecord NEW_RECORD {foo 1;SUB_RECORD {foo 2}}NEW_RECORD2 = %
Warning: { SUB_RECORD { foo 2 }; foo 1; str "I love Simulink" } >> tlc text.tlc Warning: NEW_RECORD = { SUB_RECORD { foo 2 }; foo 1; str "I love Simulink" } NEW_RECORD2 = { str "I love Simulink" } Warning: NEW_RECORD = { SUB_RECORD { foo 2 }; foo 1 } NEW_RECORD2 = { foo 12 }
(4)拷贝纪录
使用%copyrecord命令进行纪录拷贝。
%copyrecord NEW_RECORD OLD_RECORD
OLD_RECORD是一个既存的纪录,NEW_RECORD则是OLD_RECORD的一份拷贝。
%%text.tlc %createrecord NEW_RECORD {foo 1;SUB_RECORD {foo 2}} %copyrecord NEW_RECORD2 NEW_RECORD %warning NEW_RECORD = %NEW_RECORD2 = %
>> tlc text.tlc Warning: NEW_RECORD = { SUB_RECORD { foo 2 }; foo 1 } NEW_RECORD2 = { SUB_RECORD { foo 2 }; foo 1 }
(5)删除纪录
使用%undef命令删除记录中的域或者整个纪录。
%undef var
var表示一个TLC变量、一个记录名或一个纪录中的域的名字。
要删除一个域成员,可以借助%with进行范围指定。
%%text.tlc %createrecord NEW_RECORD {foo 1;SUB_RECORD {foo 2}} %with NEW_RECORD %undef foo %endwith %warning NEW_RECORD = %
>> tlc text.tlc Warning: NEW_RECORD = { SUB_RECORD { foo 2 } }
变量清除
使用%under命令可以删除TLC变量。
%assign data = [1,2,3,4,5] %undef data %foreach idx = 5 %warning data[%] = %[idx]> %endforeach %% error will occur because data is deleted.
>> tlc text.tlc 错误使用 tlc_new Error: File: text.tlc Line: 5 Column: 31 Undefined identifier data 出错 tlc (line 88) tlc_new(varargin{:});
语句换行连接
换行符有两种,C语言的“\”和M语言的“...”。
%%text.tlc %createrecord NEW_RECORD1... {foo 1;SUB_RECORD {foo 2}} %warning NEW_RECORD1 = %%createrecord NEW_RECORD2\ {foo 1;SUB_RECORD {foo 2}} %warning NEW_RECORD2 = %
>> tlc text.tlc Warning: NEW_RECORD1 = { SUB_RECORD { foo 2 }; foo 1 } Warning: NEW_RECORD2 = { SUB_RECORD { foo 2 }; foo 1 }
访问范围
使用%assign str = "I Love Simulink"建立的string型变量str是局部变量,如果定义在TLC脚本中,只有在此脚本内的语句可以访问;如果是定义在函数里,仅此函数内能访问该变量;特别地,如果变量定义在for等循环语句块内部,只有在这个语句块内部能够访问。
%%text.tlc %assign idx_i = "original string" %assign data = [[1, "good"];[3, 4.5F]] %foreach idx_i = 2 %foreach idx_j = 2 %warning The element of data[%][% ] is ... %[idx_i][idx_j]> %endforeach %endforeach %warning The original is %
>> tlc text.tlc Warning: The element of data[0][0] is 1 Warning: The element of data[0][1] is good Warning: The element of data[1][0] is 3 Warning: The element of data[1][1] is 4.5E+0F Warning: The original is original string
"::"为全局变量标识符。
%assign::str = "I Love Simulink"
输入文件控制
输入文件控制包括两种方式:%include string和%addincludepath string
%include string将在搜索路径下寻找名为string的文件,并在%include语句出现的地方将此文件的内容内联展开,类似于C语言中的#include。
%addincludepath string中string是一个绝对路径或相对路径,%addincludepath将这个路径添加到TLC的搜索路径中,以便出现%include语句时搜索器包含的文件,类似于MATLAB中的addpath命令。
TLC搜索路径依照如下顺序:
- 当前路径;
- 所有的%addincludepath添加的路径,对于多个%addincludepath,依照从下到上的搜索顺序;
- 命令行中通过-I命令添加的路径。
%addincludepath "C:\\folder1\\folder2" %%添加一个绝对路径
%addincludepath "\\folder2" %%添加一个相对路径
输出格式控制
使用%realformat命令控制输出实变量所显示的格式。
如使用16位精度的指数显示。
%realformat "EXPONENTIAL"
或者无精度损失和最小字符数格式(为S函数提供生成代码功能的模块级TLC文件就使用这种格式)
%realformat "CONCISE"
例:
%%text.tlc %createrecord NEW_RECORD {foo 100.0;SUB_RECORD {foo 2}} %realformat "EXPONENTIAL" %warning %%realformat "CONCISE" %warning %
>> tlc text.tlc Warning: 1.0E+2 Warning: 100.0
指定模块生成代码的语言类型
使用%language命令指定该模块生成代码的语种。如果自定义支持嵌入式代码生成的模块及其TLC文件,那么在TLC文件中就可以使用%language C来指定语言类型为C语言。
对于Simulink模块的TLC文件,其中必须包含%implements指令。
%implements "Block-Type" "Language"
Block-Type是指模块的S函数名,TLC文件也是此名字。Language表示生成的目标代码的语言类型。
对于自定义的为了生成嵌入式C语言的MCU芯片驱动中断控制器模块,其TLC文件开头必须包含这样一个命令:
%implements Interrupt "C"
紧接此句的是%function的函数定义,实现S函数代码生成的具体功能。
另外,%generatefile提供了一个匹配关系,将Simulink模块和TLC文件联系起来。
%generatefile "Type" "blockwise.tlc"
Type为rtw文件中模块的记录中Type参数的值,也即模块的blocktype属性,如:%generatefile "Sin" "sin_wave.tlc"。
断言
使用%assert命令为TLC断言。
%assert expression
当expression结果为TLC_FALSE时,TLC将进行堆栈追踪。
为了开启TLC的断言功能,必须在Configuration Parameter中勾选Enable TLC assertion。
函数
使用%function为开头定义函数,以%endfunction为终止符结束函数体。
%function name(optional-arguments) void
%return
%endfunction
%function LibGetMathConstant(ConstName,ioTypeId) void %assign constInfo = SLibGetMathConstantInfo(ConstName,ioTypeId) %if !ISEMPTY(constInfo) %return constInfo.Expr %else %return "" %endif %endfunction
18.2.3 变量类型
TLC语言使用的变量类型和MATLAB变量所使用的内建类型有所不同。在TLC语言中不仅是数据的类型,甚至数据的组织方式都被作为一个单独的类型,如Matrix、Vector。Range等。
数据类型名(简写) | 表现形式举例 | 说明 |
Boolean(B) | 0==0 | 返回值为TLC_TRUE或TLC_FALSE,由逻辑操作返回 |
Number(N) | 100 | 整型数 |
Real(D)s | 3.14159 | 浮点数 |
Real32(F)s | 3.14159F | 32位浮点数 |
Complex(C) | 1.0+2.0i | 64位双精度浮点数 |
Complex32(C32) | 10.F+2.0Fi | 32位单精度浮点数 |
Gaussion(G) | 1+2i | 32位整型复数 |
Unsigned(U) | 100U | 32位无符号整数 |
Unsigned Gaussion(UG) | 1U+2Ui | 32位无符号整型复数 |
String | "I Love MATLAB" | 包含在双引号中的字符串 |
Identifier | abc | 此类型仅出现在rtw记录中,不可直接在TLC表达式中使用。如果希望比较其内容,可直接与String类型比较,Identifer将自动转换为String类型 |
File | %openfile out="xx.c" %openfile buffer |
使用%openfile打开的字符串缓冲或文件 |
Function | %function my_func ... | TLC的函数类型,需要使用%endfunction结束 |
Range | [1:10] | 表示一组整数序列,如RollRegions使用在Roll循环体中表示循环变量遍历的值 |
Vector | [1,2.0F,"good"] | 向量类型,每个元素可以是TLC的内建类型,但不能是Vector或Matrix |
Matrix | %assign a=[[1,"good"];[3,4.5F]] | 矩阵类型,矩阵中每个元素类型可以不同,但是不能为Vector或Matrix |
Scope | system {...} | 范围类型,如rtw中的CompiledModel、block记录的范围 |
Subsystem | 子系统标示符,作为语句扩展的内容 | |
Special | FILE_EXISTS | 特殊内建类型如FILE_EXISTS |
18.2.4 操作符和表达式
::variable | 全局变量,当出现在函数中时,告诉函数该变量的访问权限是全局的,不适用函数的局部访问权限 |
expr[indx] | 数组或矩阵的下标索引访问方式,indx必须是0~(N-1),N为数组长度或矩阵某一维的长度 |
func([expr[,expr]...]) | 函数调用,func为函数名,expr为函数的参数列表 |
expr.expr | 域访问符,第一个expr为纪录类型,第2个expr为其内部的一个参数名 |
(expr) | 括号,括号内的表达式优先度高 |