Ivan Jeukens
Um Estudo sobre a Utilização de
Modelos Computacionais para a
Representação de Sistemas Digitais
Dissertação apresentada à Escola
Politécnica da Universidade de São
Paulo para obtenção do título de
Mestre em Engenharia Elétrica.
São Paulo
2000
Ivan Jeukens
Um Estudo sobre a Utilização de
Modelos Computacionais para a
Representação de Sistemas Digitais
Dissertação apresentada à Escola
Politécnica da Universidade de São
Paulo para obtenção do título de Mestre
em Engenharia Elétrica.
Área de Concentração:
Microeletrônica
Orientador:
Prof. Dr. Marius Strum
São Paulo
2000
Jeukens, Ivan
Um Estudo sobre a Utilização de Modelos Computacionais para
a representação de Sistemas Digitais
São Paulo, S. P., Brasil, 2000.
p. 108
Dissertação de Mestrado. Escola Politécnica da Universidade de
São Paulo. Departamento de Engenharia Elétrica.
1. Sistemas Digitais 2. Modelos computacionais 3. Especificações
Executáveis 4. Metodologias. I. Universidade de São Paulo.
Escola Politécnica. Departamento de Engenharia Elétrica II. t.
Aos meus pais.
Agradecimentos
Ao meu orientador Prof. Marius Strum pelas inúmeras discussões que
acabaram por moldar e assegurar a qualidade desse trabalho.
Aos membros da banca de defesa pela paciência de ler esse texto.
Aos colegas da sala C2−53 Alexandre Giulietti, Oscar Guilarte, Alfredo
Coelho e João Paulo.
Ao grupo de desenvolvimento do ambiente Ptolemy por disponibilisar um
software de alta qualidade.
A CAPES pelo financiamento desse trabalho através de uma bolsa de
mestrado.
SUMÁRIO
Lista de tabelas
Lista de figuras
Resumo
¨Abstract¨
1 Introdução ............................................................................................................1
1.1 Considerações Iniciais ..............................................................................1
1.2 Trabalhos Relacionados ...........................................................................2
1.3 Estrutura do Texto ...................................................................................3
2 Fundamentos ........................................................................................................4
2.1 Introdução ................................................................................................4
2.2 O Ambiente Ptolemy ................................................................................4
2.2.1 A implementação do Ambiente Ptolemy .............................................4
2.2.1.1 O Pacote Kernel: Sintaxe da Especificação ...................................5
2.2.1.2 O Pacote Actor: Suporte à Semântica ............................................6
2.2.1.3 Outras Classes Importantes ...........................................................8
2.2.2 Utilizando o Ambiente Ptolemy ..........................................................9
2.3 Os Modelos Computacionais ..................................................................11
2.3.1 Discrete Event ...................................................................................11
2.3.2 Synchronous Reactive .......................................................................13
2.3.3 Communicating Sequential Processes ...............................................17
2.3.4 Synchronous Data Flow ....................................................................19
2.3.5 Dynamic Data Flow ..........................................................................21
2.3.6 Kahn Process Networks ....................................................................24
2.4 Outros Modelos Computacionais ............................................................24
2.5 A Ferramenta de Depuração/Profile .......................................................26
2.5.1 Dados Coletados ...............................................................................26
2.5.2 A Interface Gráfica ............................................................................27
3 Primitivas Comportamentais .............................................................................32
3.1 Introdução ..............................................................................................32
3.2 Seqüência de Expressões ........................................................................32
3.2.1 Exemplo de Sistema ..........................................................................32
3.2.2 Especificação Executável ..................................................................33
3.2.3 Análise da Execução .........................................................................34
3.2.3.1 SDF .............................................................................................34
3.2.3.2 DDF ............................................................................................34
3.2.3.3 PN ...............................................................................................34
3.2.3.4 DE ..............................................................................................35
3.2.3.5 CSP .............................................................................................35
3.2.3.6 SR ...............................................................................................35
3.3 Desvio Condicional ..........................................................................................35
3.3.1 Exemplo de Sistema ..........................................................................35
3.3.2 Especificação Executável ..................................................................36
3.3.2.1 SDF .............................................................................................36
3.3.2.2 DDF ............................................................................................38
3.3.2.3 PN ...............................................................................................39
3.3.2.4 CSP .............................................................................................39
3.3.2.5 DE ..............................................................................................40
3.3.2.6 SR ...............................................................................................41
3.3.3 Análise da Execução .........................................................................42
3.3.3.1 SDF .............................................................................................42
3.3.3.2 DDF ............................................................................................42
3.3.3.3 PN ...............................................................................................43
3.3.3.4 CSP .............................................................................................43
3.3.3.5 DE ..............................................................................................43
3.3.3.6 SR ...............................................................................................43
3.4 Iteração com Duração Fixa .....................................................................44
3.4.1 Exemplo de Sistema ..........................................................................44
3.4.2 Especificação Executável ..................................................................44
3.4.2.1 SDF .............................................................................................45
3.4.2.2 DDF ............................................................................................46
3.4.2.3 PN ...............................................................................................46
3.4.2.5 CSP .............................................................................................47
3.4.2.4 DE ..............................................................................................47
3.4.2.6 SR ...............................................................................................48
3.4.3 Análise da Execução .........................................................................48
3.4.3.1 SDF .............................................................................................48
3.4.3.2 DDF ............................................................................................48
3.4.3.3 PN ...............................................................................................49
3.4.3.4 CSP .............................................................................................50
3.4.3.5 DE ..............................................................................................50
3.4.3.6 SR ...............................................................................................50
3.5 Outras Primitivas Comportamentais .......................................................51
3.5.1 Sincronismo ......................................................................................51
3.5.2 Compartilhamento de Recursos .........................................................51
3.5.3 Concorrência .....................................................................................52
3.5.4 Preempção ........................................................................................52
3.5.5 Recursão ...........................................................................................53
3.6 Discussão ...............................................................................................53
4 Estudo de Caso ...................................................................................................56
4.1 Introdução ..............................................................................................56
4.2 O Modem ADSL ....................................................................................56
4.2.1 Introdução .........................................................................................56
4.2.2 Framming .........................................................................................57
4.2.3 Código de Redundância Cíclica .........................................................59
4.2.4 Embaralhamento ...............................................................................60
4.2.5 Correção de Erros .............................................................................60
4.2.6 Interleaver ........................................................................................60
4.2.7 Ordenamento de Tons .......................................................................60
4.2.8 Codificação de Constelações .............................................................61
4.2.9 Modulação ........................................................................................61
4.2.10 A inicialização ................................................................................61
4.3 As Especificações Executáveis ...............................................................63
4.3.1 O sistema ..........................................................................................63
4.3.2 Framming .........................................................................................65
4.3.3 A especificação dos modems ATU−C e ATU−R ...............................66
4.3.3.1 MuxSyncTx ..................................................................................67
4.3.3.2 MuxSyncRx .................................................................................69
4.3.3.3 CRCTx ........................................................................................69
4.3.3.4 CRCRx ........................................................................................70
4.3.3.5 ScramblerTx ................................................................................71
4.3.3.6 ScramblerRx ...............................................................................72
4.3.3.7 FECTx ........................................................................................72
4.3.3.8 FECRx ........................................................................................73
4.3.3.9 InterleaverTx ...............................................................................75
4.3.3.10 InterleaverRx ............................................................................75
4.3.3.11 Map ...........................................................................................76
4.3.3.12 Demap .......................................................................................79
4.3.3.13 IDFT .........................................................................................80
4.3.3.14 DFT ..........................................................................................81
4.3.3.15 Initializer ..................................................................................82
4.4 Discussão ...............................................................................................87
5 Conclusões e Trabalhos Futuros ........................................................................89
5.1 Considerações Finais ..............................................................................89
5.2 Trabalhos Futuros ..................................................................................89
Referências Bibliográficas .....................................................................................91
LISTA DE TABELAS
Tabela 3.1 Comparação dos escalonadores do MoC DE ..........................................50
LISTA DE FIGURAS
Figura 2.1 Diagrama estático de classes simplificado dos pacotes Actor e Kernel.......5
Figura 2.2 Uma topologia não hierárquica .................................................................5
Figura 2.3 Exemplo de topologia hierárquica ............................................................6
Figura 2.4 Exemplo de troca de dados .......................................................................6
Figura 2.5 Exemplo de topologia para ilustrar o uso da classe Director .....................7
Figura 2.6 Type lattice do ambiente Ptolemy .............................................................8
Figura 2.7 Um exemplo genérico de ator atômico ......................................................9
Figura 2.8 Exemplo de arquivo principal de uma especificação executável ..............10
Figura 2.9 Situação de eventos simultâneos no MoC DE .........................................11
Figura 2.10 O ator Delay no MoC DE ....................................................................12
Figura 2.11 Ilustrações das definições do MoC SR ..................................................16
Figura 2.12 Ator de atraso no MoC SR ....................................................................17
Figura 2.13 O comando CDO no ambiente Ptolemy ................................................18
Figura 2.14 Dois grafos SDF ...................................................................................19
Figura 2.15 Dois grafos SDF inválidos mas com matrizes consistentes ....................20
Figura 2.16 O ator Delay no MoC SDF ...................................................................21
Figura 2.17 Os atores Switch e Select no modelo DDF ............................................22
Figura 2.18 Código do ator Select no MoC DDF .....................................................23
Figura 2.19 A janela inicial da ferramenta PTII Analyzer ........................................27
Figura 2.20 A janela da topologia ............................................................................28
Figura 2.21 Janela inicial para o profiling ................................................................29
Figura 2.22 Tabela de dados estatísticos de atores ...................................................29
Figura 2.23 A tabela de dados dos portos .................................................................30
Figura 2.24 Gráfico da evolução do estado de um ator .............................................31
Figura 2.25 Gráfico alternativo da evolução dos estados de um ator ........................31
Figura 2.26 Exemplo de gráfico da evolução da quantidade de tokens em uma
conexão .......................................................................................................31
Figura 3.1 Descrição em linguagem C do cálculo dos pontos da curva borboleta .....33
Figura 3.2 A topologia da especificação executável para o exemplo
curva borboleta.............................................................................................33
Figura 3.3 Situação da execução em um momento inicial utilizando o escalonador
com classificação de ator deferrable .............................................................34
Figura 3.4 Situação da execução em um momento inicial utilizando o escalonador
sem classificação de ator deferrable .............................................................34
Figura 3.5 Descrição em linguagem C do cálculo do coeficiente angular
em uma reta .................................................................................................36
Figura 3.6 Topologia da primeira especificação em SDF .........................................36
Figura 3.7 Trecho do código do ator Mux ................................................................37
Figura 3.8 Topologia da segunda especificação utilizando o MoC SDF ...................37
Figura 3.9 Interface para uma função do exemplo do coeficiente angular ................37
Figura 3.10 Topologia da primeira especificação utilizando o modelo DDF ............38
Figura 3.11 Topologia da segunda especificação utilizando o MoC DDF ................39
Figura 3.12 Trecho de código do ator MuxND no MoC CSP ...................................40
Figura 3.13 Topologia da especificação no MoC DE ...............................................40
Figura 3.14 Trecho de código do ator Coef no MoC DE ..........................................41
Figura 3.15 Método fire() do ator MuxND no MoC SR ............................................41
Figura 3.16 Exemplo da evolução do estado do ator Comp2 para a especificação da
figura 3.10 no MoC DDF .............................................................................42
Figura 3.17 Exemplo da evolução do estado do ator Comp2 para a especificação da
figura 3.11 no MoC DDF .............................................................................42
Figura 3.18 Exemplo da evolução do estado do ator Comp2 para a especificação da
figura 3.11 no MoC PN ................................................................................43
Figura 3.19 Multiplicação de duas matrizes de ordem N ..........................................44
Figura 3.20 A primeira especificação para a primitiva de iteração fixa ....................44
Figura 3.21 Implementação alternativa para a geração dos índices da iteração .........45
Figura 3.22 A iteração com duração fixa no MoC SDF ...........................................45
Figura 3.23 Trecho de código dos atores CT e Data ................................................46
Figura 3.24 Código dos atores Data e CT no MoC PN ............................................47
Figura 3.25 Trecho do ator Sequencer no MoC PN ..................................................47
Figura 3.26 Trecho do ator Sequencer no MoC DE .................................................47
Figura 3.27 Evolução do estado dos atores Data (em azul) e Accum (em verde) ......48
Figura 3.28 Evolução do estado dos atores Data e Accum com a alteração do
parâmetro do método waitFor() ...................................................................49
Figura 3.29 Evolução do estado do ator Data quando o tamanho inicial
das filas é 1 ..................................................................................................49
Figura 3.30 Evolução do estado do ator Data quando o tamanho inicial
das filas é 8 ..................................................................................................49
Figura 3.31 Discrete Cosine Transform : exemplo para a primitiva de sincronismo .51
Figura 3.32 Um exemplo para a primitiva de preempção .........................................52
Figura 3.33 Máquina de Mealy para o código da figura 3.32 ...................................53
Figura 4.1 Modelo de referência de sistemas ADSL ................................................56
Figura 4.2 Diagrama funcional do transmissor do modem ATU−C ..........................57
Figura 4.3 A estrutura de frame e superframe do modem ADSL ..............................58
Figura 4.4 A estrutura de um frame de dados do buffer fast .....................................58
Figura 4.5 A estrutura de um frame de dados do buffer interleaved .........................59
Figura 4.6 Constelação quando o comprimento de b for igual à 3 ............................61
Figura 4.7 Ativação e Acknowledgment ...................................................................62
Figura 4.8 Topologia da especificação do sistema ...................................................63
Figura 4.9 Trecho de código do ator BroadBandNet no MoC DE ............................64
Figura 4.10 Trecho de código da classe MuxDataFrame .........................................65
Figura 4.11 Trecho de código da classe FastFECDataFrame ..................................66
Figura 4.12 A topologia da especificação para o modem ATU−C ............................66
Figura 4.13 Trecho de código do ator MuxSyncTx no MoC DE ...............................68
Figura 4.14 Trecho de código do ator CRCTx ..........................................................70
Figura 4.15 Trecho de código do ator CRCRx ..........................................................71
Figura 4.16 Trecho de código do ator ScramblerTx .................................................72
Figura 4.17 Trecho de código do ator FECTx ..........................................................73
Figura 4.18 Trecho de código do ator FECRx ..........................................................74
Figura 4.19 Trecho de código do ator InterleaverTx ................................................75
Figura 4.20 Trecho de código do ator InterleaverRx ................................................76
Figura 4.21 Trecho de código do ator Map ..............................................................78
Figura 4.22 Trecho de código do ator Demap ..........................................................80
Figura 4.23 Trecho de código do ator IDFT no MoC DE .........................................81
Figura 4.24 Trecho de código do ator DFT no MoC DE ..........................................82
Figura 4.25 Classe auxiliar com os símbolos de inicialização ..................................82
Figura 4.26 Método fire() do ator Timer ..................................................................83
Figura 4.27 Método _getSymbol() do ator Initializer ...............................................84
Figura 4.28 Método _sendSymbol() do ator Initializer .............................................84
Figura 4.29 Método _detectSignal() do ator Initializer ............................................85
Figura 4.30 Método _transmitSignal() do ator Initializer .........................................86
Figura 4.31 Método _fire() do ator Initializer ..........................................................87
RESUMO
Neste trabalho abordamos o problema da captura e validação da especificação
funcional de um sistema digital através de especificações executáveis. Nos
concentramos na comparação de modelos computacionais, isto é, o suporte semântico
de uma linguagem de descrição de sistemas. O ambiente Ptolemy II foi escolhido para
o desenvolvimento deste trabalho, uma vez que ele possui uma infra−estrutura para a
implementação de modelos computacionais e criação de especificações executáveis.
Comparamos seis modelos computacionais. Fundamentamos nossa metodologia na
analise de comportamentos primitivos presentes em sistemas reais. Três
comportamentos foram estudados: seqüência de expressões, desvio condicional e
iteração com duração fixa. Uma ferramenta de software foi desenvolvida para auxiliar
na comparação dos diferentes modelos computacionais através da reprodução da
execução de uma especificação. Foi possível observar que o comportamento de
seqüência de expressões foi capturado eficientemente pelos seis modelos
computacionais. Os comportamentos de desvio condicional e iteração com duração
fixa apresentaram dificuldades para a captura com alguns modelos. Finalmente,
desenvolvemos a especificação de um modem ADSL. Utilizamos o modelo
computacional que se mostrou mais eficiente para a captura dos três comportamentos.
Identificamos um novo comportamento primitivo a partir desse estudo.
ABSTRACT
In this dissertation we studied the problem of capture and validation of a
functional specification of a digital system using executable specifications. We
focused on comparing different models of computation, i.e., the semantic support of a
system description language. We have chosen the Ptolemy II framework for the
development of this work, since it has several models implemented and efficiently
supports the addition of others. We compared six different models. We based our
methodology on the analysis of primitive behaviors present in real systems. Three
different behaviors we studied: sequence of expressions, conditional execution and
fixed length iteration. We developed a software tool for helping the comparison of
different models of computation by reproducing an execution. It was possible to
efficiently capture the sequence of expressions with all six models. The other two
primitive behaviors presented some difficulties under certain models. Finally, we
developed the specification of an ADSL modem. An executable specification using
the most efficient model for capturing the three behaviors was employed. We were
able to identify a new primitive behavior from this case study.
1
Capítulo 1
Introdução
1.1 Considerações Iniciais
O desenvolvimento das tecnologias de microeletrônica vem possibilitando a criação de circuitos
integrados cada vez mais complexos. Conforme foi previsto pela lei de Moore [BRE98], a capacidade de
integração dobra a cada um ou dois anos. Os microprocessadores de alto desempenho são um bom
exemplo dessa evolução: já existem processadores com mais de 20 milhões de transistores operando a
freqüências acima de 1 GHz. Também já é uma realidade o projeto de circuitos de aplicação específica
(ASIC) com milhões de transistores. Essa abundância de recursos possibilita a implementação de
sistemas cada vez mais complexos, que podem ser realizados utilizando vários componentes discretos ou
em apenas uma única pastilha. Este último caso é denominado de System−on−Chip (SOC).
Embora o desenvolvimento da tecnologia de circuitos integrados seja fundamental para a
implementação de sistemas cada vez mais versáteis e completos, ele também traz problemas. A principal
dificuldade é lidar com a complexidade do projeto. Por exemplo, foi avaliado que durante o período de
20 anos, o número de transistores de um CI aumentou à uma taxa de 58% ao ano, enquanto que no
mesmo período, a produtividade de um projetista, medida em número de transistores projetados por dia,
aumentou à uma taxa de 21% [GAJ00]. Isso significa que não mais a capacidade de manufatura é o
gargalo, mas sim o desenvolvimento do CI, ou seja, as ferramentas e metodologias CAD.
A solução que a comunidade científica e industrial considera como sendo a mais promissora é
elevar o nível de abstração na qual o projetista trabalha, pois assim a quantidade de detalhes é reduzida.
Entretanto, um nível de abstração mais alto significa que linguagens de especificação, ferramentas de
projeto, metodologias, etc, devem ser adequadas. Essa tarefa é bastante complexa pois envolve a
mudança para um novo paradigma de projeto. Em [GAJ00], Gajski et al. identificam três metodologias
sendo empregadas atualmente para o desenvolvimento de sistemas em alto nível: projeto baseado em
plataformas (platform based design), integração de propriedade intelectual (IP Assembly), e síntese a
partir de especificações (Synthesis from Specifications). Essas três soluções diferem em custo, esforço de
projeto, flexibilidade e qualidade.
O projeto baseado em plataforma [CHA99] fundamenta−se no uso de uma arquitetura pré−
definida, otimizada para um tipo de aplicação. A metodologia empregada deve permitir a criação de uma
especificação do sistema, para então mapeá−la na arquitetura.
O projeto utilizando propriedade intelectual (IP) é baseado na reutilização de blocos pré−
projetados e pré−caracterizados. A metodologia incorpora as tarefas de seleção de componentes e criação
da arquitetura (envolve a interface entre blocos), além das tarefas presentes no projeto baseado em
plataforma. O desafio para a solução utilizando blocos IPs é a criação de uma base de dados de
componentes, além de formatos de troca de dados. Já existem esforços da comunidade de ferramentas
CAD para atacar esses problemas [SYSC].
A síntese a partir de especificações significa implementar as diferentes funções da especificação
diretamente em hardware e software. Essa solução é a mais flexível das três. A metodologia para projeto
é bastante similar às outras duas soluções, com o problema adicional de particionar a especificação e
criar métodos eficientes para explorar as inúmeras soluções arquiteturais.
Embora existam três diferentes soluções sendo estudadas, todas elas requerem a criação de uma
especificação executável para capturar o sistema nos diferentes níveis de abstração, desde sua
funcionalidade até a implementação em uma arquitetura específica [GAJ00]. Conjuntamente com uma
especificação para o ambiente no qual o sistema será implantado, é possível analisar o comportamento
do sistema sendo desenvolvido através dos resultados da execução, tal qual em uma simulação
tradicional. Esse tipo de técnica já vem sendo empregada na industria para o projeto de ASICs [SYSC],
onde a linguagem de programação C é utilizada para capturar o algoritmo a ser implementado. Só após
extensas análises e otimizações é que tal descrição em linguagem C é refinada para uma descrição em
linguagem VHDL (comportamental/RTL).
2
Toda linguagem de programação ou de descrição de hardware possui uma definição sintática e
semântica. A semântica da linguagem define o significado de cada comando, ou seja, como aquele
comando produz alguma alteração no estado do sistema ou ambiente e como ele está relacionado à outros
comandos. A semântica de uma linguagem é definida com base em um modelo computacional (MoC),
ou seja , um conjunto de definições e regras que possibilitam a representação formal e não ambígua de
um algoritmo. Um exemplo clássico de modelo computacional é a máquina RAM [AHO74] associada à
programas seqüenciais. O item 3.2 deste texto apresenta uma lista de alguns modelos computacionais.
A escolha de um, ou um conjunto, de MoCs para o desenvolvimento de uma especificação
executável depende de fatores como o domínio de aplicação, características semânticas do modelo e
interação com outros modelos computacionais. Essa escolha irá afetar as tarefas de captura/validação do
comportamento do sistema bem como posterior tarefa de sintetizar sua arquitetura.
Nesse trabalho, iremos estudar a eficiência de alguns MoCs para capturar e validar a
funcionalidade de um sistema. A motivação para nosso trabalho é:
1. o fato do projeto de sistemas eletrônicos através de metodologias top−down partirem da
captura de sua especificação através de uma linguagem SLDL;
2. linguagens SLDL são sempre baseadas em um ou um conjunto de modelos computacionais o
que pode limitar a possibilidade de capturar as especificações;
3. na existência de uma grande variedade de modelos computacionais.
Portanto, é necessário que exista algum auxílio para o projetista, na forma de ferramentas,
critérios, metodologias, tal que este possa identificar os modelos computacionais mais adequados para
capturar as especificações de sua aplicação.
1.2 Trabalhos Relacionados
O problema da escolha de um método de descrição para sistemas surgiu com o desenvolvimento
das técnicas de co−projeto hardware/software (hardware/software codesign), pois uma única linguagem
não era adequada para a captura do comportamento dos módulos em hardware e software. Isto ocorre
devido as diferenças intrínsecas entre hardware e software. No nível sistêmico esse problema torna−se
mais evidente, uma vez que é necessário capturar comportamentos heterogêneos como hardware
analógico, hardware digital dominado pelo fluxo de dados, hardware digital dominado pelo fluxo de
controle, dispositivos mecânicos, software com restrições temporais, etc.
Três soluções foram estudadas: desenvolver uma nova linguagem, estender uma linguagem
existente ou utilizar várias linguagens conjuntamente. Dentre inúmeros trabalhos que exemplificam tais
soluções, podemos enumerar os seguintes:
  [BEC92]: a linguagem de programação C e a linguagem de descrição de hardware Verilog são
utilizadas conjuntamente para cosimular um sistema hardware/software;
  [COS99]: inicialmente a linguagem SDL é utilizara para capturar a parte eletrônica de uma
sistema mecatrônico e a linguagem Matlab para capturar o comportamento dos componentes
mecânicos. Em seguida, o código em linguagem SDL é refinado para uma descrição utilizando
linguagem C e VHDL;
  [LAV99]: a semântica da linguagem C é estendida com comandos da linguagem Esterel. Esse
trabalho concentra−se em sistemas dominados pelo fluxo de controle, mas que necessitam de
uma linguagem eficiente para descrever as partes dominadas pelo fluxo de dados;
  [MAR98]: a linguagem de programação Java foi estendida com algumas classes, tornando−a
adequada para capturar sistemas reativos;
  [REE95]: a linguagem Haskell é utilizada conjuntamente com um ambiente gráfico para
capturar sistemas dominados pelo fluxo de controle;
3
  [SYSC]: a linguagem C++ é estendida através de um conjunto de classes para permitir a
descrição de circuitos síncronos;
  [GAJ00]: uma nova linguagem foi desenvolvida visando a captura e refinamento de sistemas
digitais;
  [DAV99]: a linguagem Java é utilizada conjuntamente com um grupo de classes que permitem
a implementação de diversos modelos computacionais.
Nenhum dos trabalhos mencionados procura analisar quando cada linguagem ou característica
semântica deve ser empregada. Em geral, uma classe específica de sistema é escolhida e a linguagem
mais apropriada é empregada. O único trabalho que encontramos que aborda a comparação de modelos
computacionais é [LEE98]. Entretanto, a comparação é feita de maneira descritiva e teórica.
1.3 Estrutura do Texto
Esta dissertação apresenta os seguintes capítulos:
  Capítulo 2: descreveremos os fundamentos teóricos e práticos necessários para o
desenvolvimento desse trabalho. Descreveremos uma ferramenta de software que
desenvolvemos;
  Capítulo 3: apresentaremos nossa metodologia para o estudo do problema da escolha de
modelos computacionais;
  Capítulo 4: apresentaremos o estudo de caso de um sistema real que desenvolvemos para
aplicar alguns dos resultados obtidos no capítulo 3;
  Capítulo 5: contém o resumo final do trabalho e propostas para trabalhos futuros.
4
Capítulo 2
Fundamentos
2.1 Introdução
Nesse capítulo faremos uma breve descrição dos fundamentos utilizados nesse trabalho e
introduziremos termos utilizados ao longo dessa dissertação. Inicialmente, o ambiente Ptolemy será
descrito. Seus componentes internos serão apresentados juntamente com um exemplo de como uma
especificação executável é criada. Em seguida, descreveremos os modelos computacionais que
estudamos e enumeraremos outros importantes modelos encontrados na literatura. Ao final do capítulo
apresentaremos uma ferramenta que desenvolvemos para a análise da execução de especificações.
2.2 O Ambiente Ptolemy
O ambiente Ptolemy II [DAV99] é a segunda geração de um projeto sendo desenvolvido na
Universidade da Califórnia em Berkeley desde 1990. Inicialmente voltado para o desenvolvimento de
sistemas (hardware e software) para processamento digital de sinais, atualmente seu principal objetivo é
dar suporte ao desenvolvimento de sistemas embutidos (embedded systems), principalmente os
componentes de software do sistema. O fundamento central do ambiente Ptolemy1
é a utilização conjunta
de diferentes modelos computacionais para a captura das especificações do sistema. A heterogeneidade
observada em sistemas embutidos é a justificativa para essa abordagem, isto é, um sistema embutido
pode utilizar várias tecnologias distintas como hardware analógico, hardware digital, componentes
ópticos, componentes mecânicos, software de tempo real, etc. Dessa forma, um único MoC não é
eficiente para a captura do sistema.
2.2.1 A Implementação do Ambiente Ptolemy
A linguagem de programação Java foi escolhida pelo grupo de desenvolvimento do ambiente
Ptolemy. Essa também é a linguagem de programação utilizada para criar uma especificação executável,
ou seja, um especificação no ambiente Ptolemy é um conjunto de classes criadas pelo usuário mais um
conjunto de classes disponíveis no ambiente. A figura 2.12
apresenta resumidamente um diagrama
estático de classes pertencentes aos dois pacotes centrais do ambiente: Kernel e Actor. Outros pacotes
auxiliares também estão disponíveis, além de pacotes que implementam modelos computacionais.
1 Quando utilizamos o termo Ptolemy, estamos nos referindo à versão II do ambiente, e não a versão Classic (anterior). Caso seja necessário,
faremos explicitamente a distinção entre as versões.
2 Por conveniência, as figuras mostradas no item 2.2 foram extraídas da documentação do ambiente Ptolemy[DAV99]. Para uma descrição
completa do ambiente, utilize tal referência.
5
Figura 2.1 − Diagrama estático de classes simplificado dos pacotes Actor e Kernel.
2.2.1.1 O Pacote Kernel: Sintaxe da Especificação
O pacote Kernel é composto por um conjunto de classes para implementar e manipular grafos
com nós hierárquicos, também chamados de topologias. Uma topologia não hierárquica é constituída por
entidades (Entity) e relações (Relation). A figura 2.2 apresenta um exemplo de topologia3
.
Figura 2.2 − Uma topologia não hierárquica.
Cada entidade pode possuir vários portos (Port) e uma relação pode conectar vários portos entre
si. O uso de portos e relações torna uma topologia diferente de um grafo matemático. Uma relação é uma
conexão entre múltiplos elementos. Já em grafos matemáticos, arcos são conexões entre dois elementos.
Um grafo matemático poderia ser representado através de entidades designando os nós do grafos e cada
uma possuindo exatamente um porto. No caso de um grafo direcionado, cada entidade possuiria dois
portos, uma para arcos de entrada e outro para arcos de saída.
3 Ao contrário da figura 2.2, nas representações gráficas de topologias apresentadas nos capítulos seguintes, utilizaremos retângulos como
representação gráfica para atores e setas direcionadas para representar conexões entre dois ou mais atores. O terminal com a seta indica o
ator de destino e o outro terminal o ator de origem. Dessa forma, não iremos mostrar de forma explícita as relações.
Nameable
<<Interface>>
<<Interface>>
Executable
<<Interface>>
Actor
NamedObj
Entity
ComponentEntity
AtomicActor
Manager
Port Relation
Workspace
CompositeEntity
CompositeActor
Director
ComponentPort
ComponentRelation
0..n 0..1
0..n
1
0..n
Container 0..n
0..nLink
Link
Container 0..1
0..n
0..1
0..1
1
0..2
Container
0..n 0..1
1
0..1
0..n
Implementa
Deriva
Pertence ao pacote Kernel
Pertence ao pacote Actor
Attribute
Entidade
Porto
Entidade
Porto
Entidade
Porto
Relação
Link Link
Link
6
Como mostrado na figura 2.1, cada uma das três principais classes do pacote Kernel possui uma
classe derivada com o prefixo Component. A finalidade dessas classes é permitir a criação de topologias
hierárquicas. Além dessas três classes, a classe CompositeEntity é utilizada para conter objetos do tipo
ComponentEntity e ComponentRelation. Dessa forma, uma CompositeEntity contém uma sub−topologia.
Entidades que não contenham sub−grafos são denominadas de atômicas. A figura 2.3 apresenta um
exemplo de topologia hierárquica, onde E0 e E1 são entidades do tipo CompositeEntity.
Figura 2.3 − Exemplo de topologia hierárquica.
Os portos P3 e P4 da figura 2.3 são denominados de transparentes (transparent), enquanto que
os demais são denominados de opacos (opaque). Esse conceito também se aplica a entidades, como será
mostrado mais adiante.
Um porto é opaco quando a entidade que o contém é opaca, e é transparente quando a entidade
que o contém é transparente. Quando um porto for opaco, conexões com uma possível sub−topologia são
invisíveis. Já um porto transparente permite que algoritmos percorram todas as conexões associadas a
ele. Por exemplo, existe um método na classe ComponentPort denominado deepConnectedPortList().
Esse método retorna uma lista de todos os portos conectados, independentemente do nível de hierarquia.
No caso do porto P1 da figura 2.3, esse procedimento retornaria os portos P2, P5 e P6. Portos
transparentes não são retornados. Quando o procedimento atinge o porto P4, através da relação R2, o
procedimento continua pesquisando, através das relações R3 e R4.
2.2.1.2 O Pacote Actor: Suporte à Semântica
As classes do pacote Kernel definem uma topologia onde as entidades não possuem semântica.
O pacote Actor é composto por um conjunto de classes para adicionar semântica à topologia e permitir a
implementação de um modelo computacional específico, derivando−se das principais classes do pacote
actor. Além das classes mostradas na figura 2.1, a classe IOPort e a interface Receiver também possuem
papel importante.
Duas características são necessárias para a implementação de uma semântica: troca de dados
entre os atores da topologia e o controle da execução.
Troca de Dados
A figura 2.4 ilustra o processo básico de troca de dados.
Figura 2.4 − Exemplo de troca de dados
Dois portos são utilizados na figura 2.4: um de saída (P1) e um de entrada (P2). Eles são
E0
E1
E2
E3
E4
E5
P1
P2
P3
P4 P5
P6
R1
R2
R3
R4
send(0, t0)
send(1, t1) 2 2
2
get(0), get(1)
token t0, t1
receiver.put(0)
receiver.put(1)
P1 P2
7
instâncias da classe IOPort. Essa classe disponibiliza vários métodos, entre eles o método send(),
utilizado por um porto de saída para enviar dados aos portos conectados, e o método get(), utilizado por
um porto de entrada para receber dados. No ambiente Ptolemy, os dados são transmitidos encapsulados
em tokens. Uma vez criado, o valor de um token é imutável4
.
A função do método send() é transferir os dados para um elemento denominado de receptor
(receiver). Cada conexão com um porto de entrada possui associado um receptor. Na figura 2.4, temos
duas conexões associadas. Cada conexão também é denominada de canal. Quando um porto possui mais
de um canal, como na figura 2.4, deve−se especificar no método send() e get() por qual canal o token
deve ser enviado ou recebido. Isso é feito através do primeiro argumento desses métodos. Uma vez que
os dados estejam presentes no respectivo receptor, o ator de destino pode executar o método get() e obter
os dados.
No pacote Actor, o receptor não é uma classe concreta, mas sim uma interface. O receptor é
implementado como parte do modelo computacional, pois diferentes modelos possuem semânticas
distintas de comunicação entre elementos. Por exemplo, atores em um modelo data flow comunicam−se
através de filas. As filas são implementadas em um receptor. Já no modelo Discrete Event, o receptor
deve enviar os dados para uma fila global.
Controle da Execução
A classe Director do pacote Actor tem o principal papel no controle da execução. A
implementação de um modelo computacional deve derivar essa classe a fim de implementar métodos
como fire(), prefire() e postfire() definidos na interface Executable. Em um Director, esses métodos são
responsáveis pelo controle da execução dos blocos do sistema. Para tal, é introduzido o conceito de
iteração. A cada iteração da execução, o método prefire() é o primeiro a ser chamado, em seguida o
método fire(), seguido pelo método postfire(). O comportamento exato de cada método é definido pela
implementação de um modelo computacional. Em geral, o escalonador5
de um MoC é implementado nos
métodos fire() e postfire() do respectivo Director. Um Director deve sempre estar associado a um único
ator Composite. Essa associação torna o ator opaco. A utilização de um Director é ilustrada na figura
2.5.
Figura 2.5 − Exemplo de topologia para ilustrar o uso da classe Director.
O Director D1 está associado ao ator E0, que representa o nível mais alto da hierarquia (ator
toplevel). Nesse nível, existem três atores: E1, E2, E3. O bloco E1 é um ator atômico. O bloco E3 é um
ator hierárquico transparente, já que não existe nenhum Director associado à ele. Quem governa a
execução da sub−topologia do ator E3 é o Director D1. Já o bloco E2 é um ator hierárquico opaco, pois
o Director D2 está associado à ele. Para o Director D1, o bloco E2 é um ator atômico. Ao chamar o
método fire() de E2, o controle é passado para o Director D2. Dessa forma, a ferramenta implementa um
mecanismo transparente para a co−simulação de sistemas utilizando MoCs distintos. A figura também
apresenta um Manager. Este deve ser único no sistema e associado ao ator toplevel. Sua função é iniciar,
interromper e coordenar a execução de todo o sistema.
2.2.1.3 Outras Classes Importantes
4 Como será descrito adiante no texto, o tipo ObjectToken é uma exceção à essa regra.
5 Escalonador é um algoritmo utilizado para determinar uma ordem de execução dos atores. O escalonador pode ser seqüencial (apenas um
ator executa a cada momento) ou paralelo (mais de um ator executa a cada momento).
E0 D1: local director
E1
P1 P2 P5
E4
P6
E2 D2: local director
E5
P7P4P3
E3
8
Além dos pacotes descritos acima e os que implementam diversos modelos computacionais, o
ambiente Ptolemy apresenta um conjunto de classes auxiliares, como o pacote Plot, uma aplicação para
visualizar gráficos bidimensionais, pacotes de funções matemáticas e um pacote para implementação de
um sistema de tipos de dados (type system).
Tipos de Dados no Ambiente Ptolemy
A classe Token e suas derivadas compõem o coração do pacote de dados do ambiente Ptolemy.
Como mencionado anteriormente, todos os dados transmitidos entre atores são encapsulados em tokens.
A classe Token apresenta apenas a definição de alguns métodos utilitários, como operações aritméticas.
Entretanto, o ambiente possui um sistema de tipos com várias subclasses para diferentes tipos de dados.
A figura 2.6 apresenta o type lattice do ambiente. Os tipos Numerical e Scalar são classes abstratas.
Figura 2.6 − Type lattice do ambiente Ptolemy.
Em geral, os portos de entrada/saída e os parâmetros6
de um ator podem ser criados com um tipo
associado. Esse tipo pode ser especificado pelo usuário de forma exata, utilizando o método
setTypeEquals(), ou de forma relativa. Neste caso, três métodos estão disponíveis: setTypeAtLeast(),
especifica que o tipo do objeto em questão deve ser maior ou igual ao do parâmetro do método;
setTypeAtMost(), indica que o tipo do objeto deve ser menor ou igual ao do parâmetro do método:
setTypeSameAs(), indica que o tipo do objeto deve ser igual ao do parâmetro do método.
No início da execução, o ambiente (mais precisamente, uma instância do objeto Manager) irá
verificar se existe a consistência de tipos, ou seja, se um porto de entrada conectado à um porto de saída
apresentam o mesmo tipo, ou se existe a possibilidade de conversão sem perdas (baseado no lattice da
figura 2.6). Caso alguns tipos não estejam especificados ou foram especificados de forma relativa, haverá
a resolução de tipos.
2.2.2 Utilizando o Ambiente Ptolemy
6 Parâmetro é um objeto que também pode armazenar um token, e dessa forma também é tipado. O parâmetro pode ser utilizado para
especificar valores de configurações de um objeto, tornando−o mais flexível.
General
String
Matrix
Numerical
Object BooleanMatrix
Boolean
FixMatrix
Fix
Scalar
LongMatrix ComplexMatrix
DoubleMatrix
IntMatrix
Array
Long Complex
Double
Int
NaT
9
O item 2.2.1 ilustrou de forma suscinta os principais componentes internos do ambiente
Ptolemy. Utilizando essa infra−estrutura, podemos criar uma especificação executável. Para isso, é
necessário criar os atores da especificação, interconectá−los e associar à especificação um ou mais
modelos computacionais. A figura 2.7 apresenta o esqueleto básico de um ator atômico.
1− public class AtorExemplo extends TypedAtomicActor {
2−
3− public TypedIOPort entrada;
4− public TypedIOPort saida;
5−
6− public Parameter par;
7−
8− public AtorExemplo(TypedAtomicActor container, String name)
9− throws NameDuplicationException, IllegalActionException {
10−
11− super(container, name);
12−
13− entrada = new TypedIOPort(this, ¨entrada¨, true, false);
14− entrada.setTypeEquals(BaseType.INT);
15−
16− saida = new TypedIOPort(this, ¨saida¨, false, true);
17− saida.setTypeEquals(BaseType.DOUBLE);
18−
19− par = new Parameter(this, ¨parametro1¨, new IntToken(1));
20−
21− }
22−
23− public void initialize() throws IllegalActionException {
24−
25− IntToken it = (IntToken) par.getToken();
26− _var = it.intValue();
27−
28− }
29−
30− public boolean prefire() throws IllegalActionException {
31−
32− ....
33− return super.prefire();
34− }
35−
36− public void fire() throws IllegalActionException {
37−
38− IntToken it = (IntToken) entrada.get(0);
39−
40− saida.broadcast(DoubleToken.convert(it));
41− }
42−
43− public boolean postfire() throws IllegalActionException {
44−
45− ....
46− return super.postfire();
47− }
48−
49− public void wrapup() throws IllegalActionException {
50−
51− super.wrapup();
52−
53− ....
54− }
55−
56− private int _var;
57− }
Figura 2.7 − Um exemplo genérico de ator atômico.
Na linha 1, o ator é criado derivando a classe TypedAtomicActor, ou seja, é um ator atômico com
portos tipados. Em certos modelos computacionais, novas classes de atores atômicos são criadas
estendendo a classe TypedAtomicActor. Da linha 3 à linha 6, dois portos e um parâmetro são declarados
como variáveis públicas. Da linha 8 à linha 21 está implementado o construtor da classe. Duas exceções
implementadas pelo ambiente Ptolemy são declaradas na linha 9. Na linha 13 o porto de entrada (o
terceiro parâmetro do construtor é true) é criado . Em seguida, o tipo do porto é determinado como sendo
um inteiro. O porto de saída (neste caso, o quarto parâmetro do construtor deve ser true) é criado de
maneira similar nas linhas 16 e 17. Na linha 19, o parâmetro do ator é criado e inicializado como tendo
tipo inteiro com valor inicial igual à 1 (terceiro parâmetro).
10
Observando a figura 2.1, note que a classe AtomicActor (pai da classe TypedAtomicActor)
implementa a interface Executable. Nessa interface estão declarados, entre outros, os métodos
initialize(), prefire(), postfire(), fire() e wrapup(). Conforme mencionado no item 2.2.1.2, esses métodos
são usados pela classe Director e suas derivadas para o controle da execução. No caso de um ator
atômico, esses métodos são definidos pelo usuário para implementar o comportamento desejado. A
função de cada método é:
  initialize() (linha 23 à 28): chamado uma única vez durante a execução, antes de todos os
outros métodos. No exemplo da figura 2.7, esse método é utilizado para obter e armazenar o
valor do parâmetro do ator em uma variável interna7
(linhas 25 e 26);
  prefire() (linha 30 à 34): primeiro a ser chamado, uma única vez, durante uma iteração da
execução. Caso um valor false seja retornado, isso indica que o ator não está apto para ser
executado;
  fire() (linha 36 à 41): pode ser chamado várias vezes em uma mesma iteração, após o método
prefire(). Na linha 38, um token é lido do porto de entrada, utilizando o método get(). Na linha
40, o valor do token é convertido de inteiro para real, e enviado utilizando o método
broadcast(). Esse método envia o dado para todos os portos de entrada conectados à ele;
  postfire() (linha 43 à 47): similar ao método prefire(). Ele é o último método a ser chamado em
uma iteração;
  wrapup() (linha 49 à 54): chamado uma única vez ao final da execução.
Uma vez que todos os atores de uma especificação são criados, é necessário interconectá−los e
criar os objetos que irão controlar a execução. A figura 2.8 apresenta o esqueleto básico de um trecho de
código para tal função.
1− public class Sistema {
2−
3− public static void main(String args[]) throws
4− IllegalStateException, IllegalActionException,
5− NameDuplicationException {
6−
7− TypedCompositeActor toplevel = new TypedCompositeActor();
8− toplevel.setName("teste1");
9−
10− Manager exec = new Manager("exec");
11− toplevel.setManager(exec);
12−
13− Director local = new Director(toplevel, "Local");
14−
15− AtorExemplo a1 = new AtorExemplo(toplevel, ¨a1¨);
16− a1.par.setToken(new IntToken(10));
17−
18− AtorExemplo a2 = new AtorExemplo(toplevel, ¨a2¨);
19− AtorExemplo a3 = new AtorExemplo(toplevel, ¨a3¨);
20−
21− toplevel.connect(a1.saida, a2.entrada);
22−
23− TypedIORelation r1 = new TypedIORelation(toplevel, ¨r1¨);
24− a2.saida.link(r1);
25− a3.entrada.link(r1);
26−
27− exec.run();
28− }
29− }
Figura 2.8− Exemplo de arquivo principal de uma especificação executável.
Na linha 7, o ator de nível mais alto (toplevel) é criado. Nenhum outro ator Composite é
utilizado, dessa forma, o exemplo da figura 2.8 não é hierárquico. Na linha 10, uma instância de objeto
Manager é criada e associada ao ator hierárquico toplevel (linha 11). Na linha 13, um Director é criado e
associado ao ator hierárquico. Esse ator, um objeto Manager e um objeto Director, sempre estarão
presentes em qualquer especificação executável. Três instâncias do ator da figura 2.7 são utilizadas. Na
linha 16, o valor padrão do parâmetro par do AtorExemplo exemplo é modificado. Na linha 21, é feita a
conexão entre dois portos, através do método connect() do ator hierárquico. Outra forma de criar uma
7 Adotamos como notação adicionar um caractere _ ao início de um nome de variável quando esta for private.
11
conexão é exemplificada da linha 23 à linha 25, através da criação explícita de um objeto Relation. O
comando da linha 27 é utilizado para iniciar a execução.
2.3 Os Modelos Computacionais
Descreveremos nesse item a semântica dos seis modelos computacionais (MoC) que utilizamos e
características referentes à implementação desses modelos no ambiente Ptolemy. Iremos ressaltar dois
pontos importantes de um MoC: o escalonador e o modelo de tempo. Em seguida, apresentaremos uma
lista não exaustiva de outros modelos. É importante notar que um mesmo MoC pode ser implementado
de várias maneiras diferentes. Todos os resultados desse trabalho são referentes as implementações
adotadas no ambiente Ptolemy.
2.3.1. Discrete Event
O modelo computacional Discrete Event [MUL99] (DE) é baseado no processamento de eventos
produzidos pelos atores. Um evento é um par composto por um token e um valor numérico real. Esse
valor numérico é denominado de timestamp e sua função é modelar um valor temporal, ou seja, o
modelo DE possui uma noção explícita de tempo. O tempo nesse modelo é compartilhado por todos os
atores da especificação. Quando um ator produz um evento, este é enviado para uma fila de eventos
controlada pelo escalonador, e não para o ator de destino. Os eventos são mantidos ordenados de forma
crescente na fila, com base no valor do timestamp. A cada iteração, o escalonador remove o próximo
evento da fila e envia−o ao ator de destino. Esse ator é então ativado, isto é, o escalonador executa os
métodos prefire(), fire() e postfire(). É possível criar uma especificação com atores que não manipulam o
valor do timestamp. Esse tipo de ator é denominado de atraso zero (zero delay). A execução de uma
especificação no modelo DE é interrompida quando não há mais nenhum evento na fila ou quando um
valor de tempo especificado para o fim da execução é alcançado.
Uma situação de conflito que pode ocorrer nesse modelo é a presença de eventos simultâneos, ou
seja, eventos com um mesmo timestamp. A figura 2.9 ilustra esse situação.
Figura 2.9 − Situação de eventos simultâneos no MoC DE.
Na situação da figura 2.9, o ator A produziu dois eventos com um mesmo timestamp T. Um
evento é para o ator B, e o outro para o ator C. Nessa situação, qual deve ser o próximo ator a ser
ativado?
A solução implementada no ambiente Ptolemy associa a cada ator um valor inteiro não negativo
e único (prioridade). Esse valor é obtido através de uma ordem topológica de um grafo, onde os nós são
os atores e os arcos representam as conexões que não apresentam atraso. Como mencionado em
[MUL99], essa solução torna a execução da especificação determinística. É possível que o grafo gerado
contenha ciclos. Isso significa que a especificação contém um ciclo sem atraso, situação que impede a
execução da especificação, pois introduz um loop com geração infinita de eventos. Neste caso, o
ambiente gera uma mensagem indicando para o usuário do problema. Os principais passos do algoritmo
de escalonamento do MoC DE são:
1. caso a fila de eventos esteja vazia ou o instante de tempo final tenha sido alcançado, vá para o
passo 9;
2. obtenha o próximo evento da fila;
Ramp
A
Ramp
B
Ramp
C
T
T
12
3. atualize o instante de tempo atual para o valor do timestamp do evento obtido no passo 2;
4. determine o ator de destino do evento obtido e introduza o token no respectivo porto de
destino;
5. caso não exista nenhum outro evento simultâneo para o ator do passo 4, vá para o passo 7;
6. obtenha todos os eventos simultâneos para o ator do passo 4 e introduza−os nos respectivos
portos do ator;
7. ative o ator até que todos os tokens em seus portos tenham sido consumidos;
8. vá para o passo 1;
9. fim.
Quando mais que um evento com o mesmo timestamp estiver disponível para um ator, todos são
removidos da fila de eventos (passos 4, 5, 6). Nessa condição, caso mais que um evento seja para o
mesmo porto, uma fila é utilizada no porto de entrada/saída, respeitando a ordem desses eventos na fila
do escalonador.
É possível criar atores de atraso zero utilizando as classes TypedAtomicActor e
TypedAtomicPort, mencionadas no item 2.2. Entretanto, para utilizar as características específicas do
MoC DE, as classes DEIOPort e DEActor estão disponíveis. A figura 2.10 apresenta um trecho de
código de um ator utilizando tais classes.
1− ....
2− import ptolemy.domains.de.kernel.*;
3− import ptolemy.domains.de.lib.DETransformer;
4− ....
5−
6− public class Delay extends DETransformer {
7−
8− public Delay(TypedCompositeActor container, String name) throws
9− NameDuplicationException, IllegalActionException {
10− super(container, name);
11
12− delay = new Parameter(this, "delay", new DoubleToken(1.0));
13− delay.setTypeEquals(BaseType.DOUBLE);
14
15− input.delayTo(output);
16− }
17−
18− public Parameter delay;
19−
20− ....
21−
22− public void fire() throws IllegalActionException {
23− _currentInput = input.get(0);
24− }
25−
26− public boolean postfire() throws IllegalActionException {
27− output.broadcast(_currentInput,
28− ((DoubleToken)delay.getToken()).doubleValue());
29− return super.postfire();
30− }
31−
32− private Token _currentInput;
33− }
Figura 2.10 − O ator Delay no MoC DE.
O ator da figura 2.10 implementa um atraso por um tempo especificado através de um parâmetro
(linha 18). O comando da linha 15 indica que existe um caminho com atraso entre um porto de entrada e
um porto de saída. Essa informação é utilizada durante a criação do grafo utilizado para determinar o
valor de prioridade de cada ator. Nas linhas 27 e 28 o evento é criado, através de uma versão específica
do método broadcast(). Nessa versão, o segundo parâmetro indica o valor do atraso.
2.3.2 Synchronous Reactive
O modelo computacional Synchronous Reactive [EDW94][EDW97] (SR) é baseado na hipótese
13
de sincronismo: o sistema é capaz de produzir uma resposta à estímulos externos de forma infinitamente
rápida. Cada reação do sistema é instantânea e atômica, dessa forma, a evolução do tempo é dividida em
uma série de instantes discretos. A hipótese de sincronismo também é utilizada por uma classe de
linguagens de programação, tais como Esterel, Lustre, Signal, denominada de síncrona
[BER98][HAL93][BEN91], além de circuitos digitais síncronos.
Antes de descrevermos com maior detalhe a semântica do modelo SR, é necessário mencionar
algumas definições, proposições e teoremas importantes. As demonstrações para esses teoremas e
proposições podem ser encontradas em [EDW97].
Definição 1:
Um conjunto parcialmente ordenado (poset) é um conjunto S com uma relação parcial de ordem
¡ que, para quaisquer x, y, e z pertencentes à S, satisfaz:
  x
¡ x (reflexiva);
  x
¡ y e y
¡ x implica que x = y (antissimétrica);
  x
¡ y e y
¡ z implica que x
¡ z (transitiva);
Definição 2:
Um limite superior de um conjunto T é um elemento u tal que t
¡ u para todos t ¢ T. O menor
limite superior de um conjunto T, representado por
£
T, é um elemento l tal que l
¡ u para todos limites
superiores u.
Proposição 1:
O menor limite superior de um conjunto, se existir, é único.
Definição 3:
Uma cadeia é um conjunto totalmente ordenado C, isto é, para todo x, y ¢ C, ou x
¡ y ou y
¡
x.
Definição 4:
Um poset, onde toda cadeia em S possui um menor limite superior em S, é denominado de
conjunto parcialmente ordenado completo (CPO).
Proposição 2:
O menor limite superior de uma cadeia finita sempre existe e é seu maior elemento.
Corolário 1:
Um poset apenas com cadeias finitas é um CPO.
Definição 5:
O limite inferior de um poset, representado por ¤ , é um membro de S tal que ¤ ¡ s para todo s
¢ S. Um poset com um limite inferior é denominado de poset pontual.
Proposição 3:
14
Se D1 e D2 são CPOs, então D1 ¥ D2 é um CPO sob a ordem
(x1, x2)
¡ (y1, y2) se e somente se x1
¡ y1 e x2
¡ y2
e se x1
= (x1
1, x1
2) x2
= (x2
1, x2
2), ....
¦ {x1
,x2
,...} = (
£
{x1
1, x2
1, ...},
£
{x1
2, x2
2, ...}).
Definição 6:
Uma função f:D § E entre posets D e E é monotônica se para todo x,y ¢ D tal que x
¡ y, f(x)
¡ f(y).
Definição 7:
Uma função f:D § E entre CPOs D e E é contínua se para todas as cadeias C ⊆ D, f(
£
C) =£
{f(c) ¨ c ¢ C}.
Proposição 4:
Uma função contínua é monotônica.
Proposição 5:
Uma função monotônica cujo domínio é um CPO com apenas cadeias finitas é contínua.
Proposição 6:
A composição de duas funções contínuas também é contínua.
Proposição 7:
A composição de duas funções monotônicas também é monotônica.
Proposição 8:
Seja D, E e F CPOs. Se f:D § E e g:D § F contínuas, então f¥ g também é contínua.
Definição 8:
Seja D um poset, f:D § D uma função e x © D:
  se f(x)
¡ x, então x é um ponto prefixo;
  se f(x) = x, então x é também um ponto fixo;
  se x é um ponto prefixo e x
¡ p para todo ponto prefixo p, então x é um menor ponto
prefixo;
  se x é um ponto fixo e x
¡ y para todo ponto fixo y, então x é um menor ponto fixo;
Teorema 1:
15
Seja f:D § D uma função contínua sobre um CPO pontual D. Então:
fix(f) ≡
£
{¤ f(¤ ), f(f(¤ )), ...., fk
(¤ ), ...}
existe e é tanto um único menor ponto fixo como um único menor ponto prefixo de f.
Definição 9:
Seja I = I1 ¥ .... ¥ In e O = O1 ¥ .... ¥ Om vetores de CPOs pontuais. Denomina−se bloco uma
função vetorial contínua de I sobre O.
Definição 10:
Seja b : I § O um bloco, J = J1 ¥ .... ¥ Ja um vetor de CPOs pontuais e w1,....,wn uma
seqüência tal que wk ¢ {1, .... , a}. Se Jwk ⊆ Ik, então o bloco conectado à J com as conexões w1, ...., wn
é a função c : J § O tal que
c(j1, ...., ja) = b(jw1, ...., jwn)
onde (j1, ...., ja) ¢ J.
Definição 11:
Seja b1 : I § O, b2 : I § O, ...., bn : I § O um conjunto de blocos, I = I1 ¥ .... ¥ In um vetor
de CPOs pontuais, O = O1 ¥ .... ¥ Os e J = I ¥ O. O sistema aberto composto por esses blocos é a
função d: J § O tal que
d(j) = c1(j) ¥ c2(j) ¥ .... ¥ cs(j)
onde ck é o bloco bk conectado ao vetor J, e j ¢ J.
O modelo computacional SR enxerga o sistema como sendo uma função. Seja d um sistema
aberto, a função SR é a menor função e:I § O que satisfaça
e(i) = d(i, e(i)), (1)
onde I e O são vetores de CPOs pontuais.
A figura 2.11 apresenta uma ilustração das definições 10, 11 e de uma função SR.
16
Figura 2.11 − Ilustrações das definições do MoC SR: (a) O sistema; (b) O sistema aberto correspondente; (c) O
bloco correspondente.
A equação (1) é um ponto fixo, onde a função e é o parâmetro. Dessa forma, podemos escrever:
e = B(e),
onde B:(I § O) § (I § O) é uma função que transforma uma função em outra função.
Utilizando o teorema 1, Edwards demonstra que a função B apresenta um único menor ponto fixo. Para
tal, ele demonstra que o domínio de B é um CPO pontual e que B é contínua. Isso implica que o MoC
SR é determinístico.
O valor de cada sinal no MoC SR pode estar em um de três estados: indefinido, definido e
ausente (nenhum evento no instante) ou definido e presente (evento com um valor no instante). No início
de cada instante, todos os sinais, a menos das entradas do sistema, estão no estado indefinido. Uma vez
que um sinal passa do estado indefinido para um definido, esse sinal não pode mais mudar de estado ou
valor. Essa regra é necessária para assegurar que o ator implementa uma função monotônica (e pela
proposição 5, contínua). Os três estados formam uma CPO, com o estado indefinido sendo o limite
inferior.
A implementação do MoC SR no ambiente Ptolemy consiste de um escalonador que resolva o
cálculo de um menor ponto fixo para o sistema. Edwards descreve um método capaz de determinar em
tempo de compilação uma ordem de ativamento de atores que encontra o menor ponto fixo. Entretanto, o
método é relativamente complexo para ser descrito brevemente nessa dissertação.
O escalonador que utilizamos baseia−se no teorema 1 para encontrar o menor ponto fixo:
1. execute todos os atores e verifique se algum sinal passou do estado indefinido para o
definido;
2. se não houver modificação no estado dos sinais, então o ponto fixo foi encontrado. Do
contrário, repita o passo 1.
Em [EDW94], Edwards prova que esse escalonador irá encontrar o menor ponto fixo em um
Ramp
F1
F2
F3
F4
F4
F1
F2
F3
1
2
3
4
5
1
2
3
4
5
O
O
J
I
d
I Oe
(c)
(b)
(a)
17
número finito de iterações.
No MoC SR, um ator atômico é dividido em dois tipos: Strict e Non−Strict. Atores Strict são
aqueles que requerem que todos os sinais de entrada estejam definidos para ocorrer o ativamento. Atores
Non−Strict não possuem essa restrição, e portanto, são mais flexíveis. Uma das razões para a existência
de atores desse tipo é que em caminhos de realimentação, caso apenas atores Strict fossem utilizados, o
sistema entraria em deadlock. A figura 2.12 apresenta um exemplo de ator Non−Strict que implementa
um atraso por um instante.
1− ....
2− public class SRPre extends TypedAtomicActor {
3−
4− public SRIOPort input;
5− public SRIOPort output;
6−
7− public SRPre(TypedCompositeActor container, String name)
8− throws IllegalActionException, NameDuplicationException {
9− super(container, name);
10−
11− input = new SRIOPort(this, input1, true, false);
12− input.setTypeEquals(BaseType.INT);
13−
14− output = new SRIOPort(this, output, false, true);
15− output.setTypeEquals(BaseType.INT);
16− output.setMultiport(true);
17−
18− ((SRDirector) getDirector()).setStrict(this, false);
19− }
20−
21− public void initialize() throws IllegalActionException {
22− super.initialize();
23− _state = 0;
24− }
25−
26− public void fire() throws IllegalActionException {
27− if(!output.known(0)) {
28− output.send(0, new IntToken(_state));
29− }
30− }
31−
32− public boolean postfire() throws IllegalActionException {
33− if(input.present(0)) {
34− _state = ((IntToken) input.get(0)).intValue();
35− }
36− return true;
37− }
38−
39− private int _state;
40− }
Figura 2.12 − Ator de atraso no MoC SR.
A linha 18 utiliza o método setStrict() para determinar que o ator é do tipo Non−Strict. Na linha
27, é determinado se o sinal de saída ainda não está definido. Caso positivo, o valor atual da variável
interna é enviado. No MoC SR, o método fire() pode ser chamado inúmeras vezes a cada instante. Dessa
forma, ações que o usuário deseje realizar uma única vez por instante devem ser efetuadas no método
postfire(). No caso do ator da figura 2.12, a atualização do estado (linha 34) é feita nesse método.
2.3.3 Communicating Sequential Processes
O modelo computacional Communicating Sequential Processes (CSP) [HOA78] [HOA85]
[SMY98] é fundamentado no uso de processos concorrentes, cada ator associado à um único processo. O
escalonamento dos processos é feito pelo ambiente de execução da linguagem Java. Nesse modelo não
há armazenamento de dados entre processos, a transferência de dados é feita através do protocolo de
rendezvous. Nesse protocolo, um ator A executa o método send() ou broadcast() para enviar um dado à
um ator B. O ator A permanece bloqueado enquanto o ator B não consome o token. O mesmo acontece
quando um ator tenta consumir um token quando este ainda não está disponível.
O MoC CSP permite o rendezvous não−determinístico através de comandos de comunicação
18
condicional (guarded). Esses comandos apresentam a seguinte forma:
guard; comunicação = lista de comandos;
O guard é uma expressão que retorna um valor booleano. Não é permitido que seu cálculo
produza alterações no estado interno do processo. Caso o valor retornado seja falso, o comando é
encerrado. Caso contrário, o comando de comunicação especificado é iniciado e quando ele for
completado, a lista de comandos é executada.
Dois tipos de comando de comunicação condicional estão disponíveis: CIF e CDO. O comando
CIF apresenta a seguinte forma:
CIF {
guard1; comunicação1 = lista de comandos1;
[]
guard2; comunicação2 = lista de comandos2;
[]
....
}
Para cada caminho do comando CIF, os respectivos guards são verificados. Essa verificação é
feita concorrentemente, ou seja, é irrelevante a ordem dos guards. Caso um ou mais guards sejam
verdadeiros, os respectivos comandos de comunicação são iniciados. Caso nenhum comando de
comunicação esteja pronto, todo o comando CIF irá bloquear até que uma comunicação esteja pronta.
Caso mais que uma comunicação esteja pronta, a escolha de uma é feita de maneira não−determinística.
Uma vez que a comunicação é completada, a lista de comandos associada é executada e o comando CIF
é encerrado. O comando CIF é ignorado quando todos os guards forem falsos.
O comando CDO é similar ao comando CIF. Entretanto, ao acabar a execução de uma das listas
de comandos, o comando CDO é reiniciado. Esse comando é encerrado apenas quando todos os guards
forem falsos.
A implementação do MoC CSP no ambiente Ptolemy apresenta alguns métodos adicionais para
implementar os comandos CIF e CDO. A figura 2.13 ilustra o comando CDO.
1− boolean continueCDO = true;
2−
3− while(continueCDO) {
4−
5− ConditionalBranch [] branches = new ConditionalBranch[3];
6−
7− branches[0] = new ConditionalReceive(true, input, 0, 0);
8− branches[1] = new ConditionalReceive(true, input, 0, 1);
9−
10− token = new Token();
11− branches[2] = new ConditionalSend(true, output, 0, 2, token);
12−
13− int result = chooseBranch(branches);
14−
15− if(result == 0) {
16− ....
17− Token t = branches[0].getToken();
18− ....
19− }
20− else
21− if(result == 1) {
22− ....
23− Token t = branches[1].getToken();
24− ....
25− }
26− else
27− if(result == 2) {
28− ....
29− }
30− else
31− if(result == −1) {
32− continueCDO = false;
33− }
34− }
19
Figura 2.13 − O comando CDO no ambiente Ptolemy.
A primeira tarefa é a criação dos diferentes caminhos do comando utilizando os objetos
ConditionalReceive e ConditionalSend. Isso é feito da linha 5 à linha 11. No exemplo da figura 2.13,
dois caminhos possuem comandos de comunicação para recepção de dados (linha 7 e 8) e um caminho é
para envio de um dado (linha 11). O primeiro parâmetro dos construtores é o guard do comando, que
neste exemplo é sempre verdadeiro. O quarto parâmetro é um valor inteiro único associado ao caminho
em questão.
A segunda tarefa é a escolha de um dos caminhos, utilizando o método chooseBranch(). Esse
método irá retornar o número inteiro associado com o caminho escolhido. Finalmente, a lista de
comandos para o caminho ativado é executada (linhas 15 à 33).
Embora originalmente esse modelo não apresente uma noção de tempo, a implementação no
ambiente Ptolemy introduziu essa característica. A exemplo do MoC DE, esse MoC também utiliza o
tempo centralizado. Cada processo (ator) pode se suspender por uma duração de tempo determinada.
Quando esse instante for alcançado, o escalonador ativa novamente o processo. O tempo é avançado
quando todos os processos se suspenderam ou quando todos os processos estão bloqueados devido ao
rendezvous e pelo menos um processo se suspendeu. Nestas situações, o tempo atual é avançado o
suficiente para ativar algum processo que se suspendeu.
2.3.4 Synchronous Data Flow
O modelo computacional Synchronous Data Flow (SDF) [LEE87a][LEE87b pertence à classe
dos modelos data flow [DAV82][DEN80][KAR66]. O modelo data flow foi inicialmente desenvolvido
para descrever e analisar programas paralelos. Nesse modelo, o sistema é abstraído por um grafo onde os
nós são atores e os arcos indicam dependência de dados entre os atores. Em cada arco do grafo existe
uma fila unidirecional utilizada para a troca de dados. É introduzido o conceito de ativamento de um
ator (nó): um nó executa apenas quando todos os dados necessários estiverem disponíveis (o ator está
pronto), e sua execução é atômica. Não existe o conceito de fluxo de controle em grafos data flow.
No modelo SDF, além de respeitar a semântica data flow, deve ser especificado, para cada ator,
quantos tokens são consumidos (no caso de um porto de entrada) ou produzidos (no caso de um porto de
saída) para todos os seus portos. Esses valores (também chamados de taxas de amostragem) são fixos e
conhecidos em tempo de compilação. Um grafo SDF possui um escalonamento (seqüencial ou paralelo)
estático, ou seja, antes da execução é determinado quantas vezes cada ator deve ser ativado e qual é a
ordem de ativamento. A figura 2.14 apresenta dois grafos SDF. Descreveremos o modelo SDF mais
formalmente seguindo [LEE87b]. Iremos considerar o caso de escalonamento seqüencial. O caso paralelo
está descrito em [LEE87a].
Figura 2.14 − Dois grafos SDF. Os números próximos aos nós são as taxas de amostragem.
Um grafo SDF pode ser representado por uma matriz de incidência: as linhas representam os
arcos e as colunas os nós. Cada posição da matriz é uma taxa de amostragem: positiva quando o ator
produz o token na respectiva conexão, negativa quando ele consome. Essa matriz é denominada de
matriz de topologia. As matrizes de topologia para os grafos da figura 2.14 (a) e (b) são respectivamente:
1
2
3
1
2
3
1
2
11
1
1
1
2
3
1
2
3
1
2
21
1
1
(a) (b)
1 −1 0
2 0 −1
0 1 −1
1 −1 0
2 0 −1
0 2 −1
(a) (b)
Γ Γa b
= =
20
Durante a execução, a quantidade de dados em cada fila de comunicação irá variar. Em um
determinado momento n, a quantidade de dados em cada fila é dada por um vetor coluna b(n). Para um
escalonamento seqüencial, o vetor coluna v(n) indica qual ator é ativado em um instante n. Podemos
descrever a variação de dados em cada fila pela equação
b(n + 1) = b(n) + Γv(n).
O valor do vetor b quando n = 0 indica em quais conexões existem dados antes do início da
execução.
Para se determinar um escalonamento de um grafo SDF, é necessário verificar se a matriz de
topologia é consistente, ou seja, se as taxas de amostragem especificadas são válidas. Foi demonstrado
que o rank da matriz de topologia, igual ao número de nós menos um, é uma condição necessária para a
consistência. Por exemplo, o rank da matriz Γa é igual a três, enquanto que o rank da matriz Γb é igual à
dois. Dessa forma, o grafo da figura 2.14 (a) é inválido e o da figura (b) é válido.
Quando a matriz de topologia for válida, existe um vetor coluna q que satisfaz:
Γq = 0.
Para a matriz Γb, q = J[1 1 2]T
, para qualquer J positivo. O vetor q determina quantas vezes cada
nó deve ser ativado para a obtenção de um escalonamento periódico, ou seja, um escalonamento que
quando completado, faz com que o valor do vetor b permaneça inalterado. Dessa forma, o grafo SDF
pode ser executado infinitamente com capacidade limitada e conhecida de memória (filas).
Mesmo quando a matriz de topologia é consistente, é possível que a especificação do grafo seja
inválida. Isso ocorre quando existirem ciclos no grafo sem a quantidade adequada de dados no momento
inicial da execução. A figura 2.15 ilustra essa situação.
Figura 2.15 − Dois grafos SDF inválidos mas com matrizes consistentes.
O losango da figura 2.15 (b) representa um ator de atraso, ou seja, um ator que no instante n
envia o token recebido no instante n − 1. Para isso, esse ator produz tokens antes do início da execução
(a entrada no vetor b com n = 0 para o porto de saída desse ator é maior que zero). Ambos os grafos da
figura 2.15 não possuem condições iniciais válidas: a grafo da figura (a) não possui nenhum atraso, o da
figura (b) apresenta um atraso de apenas um token, o que é insuficiente. A figura 2.16 apresenta a
implementação de um ator de atraso no ambiente Ptolemy.
1− public class Delay extends Transformer {
2− public Delay(TypedCompositeActor container, String name)
3− throws IllegalActionException, NameDuplicationException {
4−
5− super(container, name);
6− new Parameter(output, TokenInitProduction,
7− new IntToken(1));
8−
9− output.setTypeAtLeast(input);
10− }
11−
12− public void fire() throws IllegalActionException {
1
1
1
1
1
2
1
2
D
(a) (b)
21
13− Token message = input.get(0);
14− output.broadcast(message);
15− }
16−
17− public void initialize() throws IllegalActionException {
18− output.broadcast(new Token());
19− }
20− }
Figura 2.16 − O ator Delay no MoC SDF.
A linha 6 modifica o parâmetro TokenInitProduction que especifica a quantidade de tokens a
serem produzidos antes do início da execução. Outra forma de fazer tal especificação é através do
método setTokenInitProduction() da classe SDFIOPort. Métodos similares existem para especificar a
produção e consumo de tokens a cada ativamento do ator.
Quando o grafo SDF for consistente e com a quantidade necessária de atrasos, o seguinte
algoritmo pode ser usado para a obtenção de um escalonamento seqüencial8
:
1. encontre o menor vetor q;
2. obtenha uma lista L de todos os atores da especificação;
3. percorra a lista L e para cada ator pronto, escalone−o;
4. caso cada ator α tenha sido escalonado qα vezes, FIM;
5. caso nenhum ator da lista L esteja pronto, ocorreu um deadlock (a especificação é inválida);
6. do contrário, vá para 3.
O algoritmo encontra um escalonamento simulando a execução do sistema. Para encontrar o
vetor q do passo 1, o seguinte método é utilizado:
1. escolha aleatoriamente um ator e assuma que ele é ativado uma única vez, ou seja, qa = 1;
2. para um ator B adjacente à A, calcule o valor de qb = (qa . pa)/cb, onde pa é a quantidade de
tokens produzidos pelo ator A na conexão e cb a quantidade de tokens consumidos por B. O
valor obtido pode ser fracional mas sempre racional;
3. repita o passo 2 recursivamente para um ator adjacente a B;
4. uma vez obtidos todos os valores de q, encontre o menor denominador comum desses
valores a fim de torná−los inteiros.
É fácil perceber que o método descrito irá encontrar o vetor q em tempo linear ao número de nós
e arcos do grafo.
No modelo SDF, não é necessário implementar explicitamente filas para a comunicação entre os
atores. O fato desse MoC ser escalonado estaticamente permite associar uma posição de memória
específica para cada dado a ser consumido ou escrito por um ator, em cada ativamento do mesmo.
Entretanto, a semântica de comunicação por filas é respeitada.
O modelo SDF não apresenta uma noção de tempo.
2.3.5 Dynamic Data Flow
Assim como o modelo SDF, o modelo computacional Dynamic Data Flow [LEE95] (DDF)
respeita a semântica data flow, e também não possui uma noção de tempo. Ao contrário do modelo
SDF9
, não existe a necessidade de especificar as taxas de consumo e produção de tokens de cada porto,
embora isso seja permitido. Esses valores podem variar durante a execução. Dois exemplos de atores
DDF são mostrados na figura 2.17.
8 Outros algoritmos para escalonamento seqüencial de grafos SDF estão descritos em [BHA94].
9 O modelo SDF é um subconjunto do modelo DDF.
22
Figura 2.17 − Os atores Switch e Select no modelo DDF.
O ator Switch consome um token do porto de dados e baseado no valor do token do porto de
controle, envia o token de dado para um dos dois portos de saída. Dessa forma, apenas um porto de saída
produzirá dados a cada ativamento do ator Switch. O ator Select consome um token de dados, baseado no
valor do token de controle.
Podemos observar que no caso do ator Select, mais de uma condição de presença de dados pode
ativá−lo. Esse fato é conhecido como as regras de ativamento de um ator (firing rules). No caso do
modelo SDF, só existe uma regra para cada ator. No caso do modelo DDF, várias regras podem ser
especificadas para um mesmo ator. Por exemplo, o ator Select possui duas regras:
R1 = {[*], ¤ , [0]}
R2 = {¤ , [*], [1]}
A primeira posição das regras representa a entrada Dado1, a segunda a entrada Dado2 e a
terceira a entrada Controle. O símbolo ¤ indica que a regra pode ser satisfeita independentemente da
respectiva entrada. O símbolo * indica que é necessário um token, independente de seu valor. Os
números dentro de colchetes indicam o valor necessário do token.
O modelo DDF é determinístico (assim como o SDF), isto é, dado um mesmo conjunto de
entradas, as saídas sempre terão o mesmo valor, independentemente da ordem de ativamento dos atores.
Entretanto, para que uma especificação utilizando o MoC DDF seja válida (determinística), todos os
atores devem implementar funções contínuas. Isso pode ser obtido utilizando regras de ativamento
seqüenciais. Um algoritmo para determinar quando as regras de ativamento são seqüenciais é:
1. encontre um porto de entrada j tal que [*]
¡ Ri,j para todas as regras i = 1, ..., N, isto é,
um porto de entrada tal que todas as regras necessitem de pelo menos um token neste
porto. Caso nenhum porto seja encontrada, as regras não são seqüenciais;
2. para o porto j encontrado no passo 1, divida as regras em subconjuntos de acordo com o
primeiro valor de Ri,j para todas as regras. Caso Ri,j = [*, ....], então Ri,j deve aparecer em
todos os subconjuntos;
3. remova o primeiro elemento de Ri,j para todas as regras i;
4. caso todos os subconjuntos sejam vazios, então as regras de ativamento são seqüencias.
Caso contrário, repita os passos para cada subconjunto não vazio.
Para as regras do ator Select, o primeiro passo irá identificar j = 3. Dois subconjuntos são
formados, um para cada regra. As duas novas regras são R1 = {[*], ¤¤ } e R2 = {¤ , [*], ¤ }. O
procedimento encerra trivialmente para cada um dos subconjuntos.
No ambiente Ptolemy, as regras de ativamento de um ator não são enumeradas explicitamente.
Para garantir a validade do ator, a semântica de leitura bloqueante é utilizada, ou seja, ao tentar consumir
um ou mais tokens, o ator fica bloqueado enquanto os dados não estiverem disponíveis. Note que o
algoritmo para determinar se um conjunto de regras é seqüencial pode ser implementado através do uso
de leitura bloqueante. A figura 2.18 exemplifica essa situação através do código do ator Select.
1− public class Select extends DDFAtomicActor {
2−
3− public DDFIOPort control;
4− public DDFIOPort data1;
5− public DDFIOPort data2;
Switch Select
Dado
Controle Controle
Dado1
Dado2
Saída1
Saída2
Saída
23
6− public DDFIOPort output;
7−
8− public Select(TypedCompositeActor container, String name) throws
9− IllegalActionException, NameDuplicationException {
10−
11− super(container, name);
12−
13− control = new DDFIOPort(this, control, true, false);
14− control.setTokenConsumptionRate(1);
15− control.setTypeEquals(BaseType.DOUBLE);
16−
17− data1 = new DDFIOPort(this, data1, true, false);
18− data1.setTokenConsumptionRate(0);
19−
20− data2 = new DDFIOPort(this, data2, true, false);
21− data2.setTokenConsumptionRate(0);
22−
23− output = new DDFIOPort(this, output, false, true);
24− output.setTokenProductionRate(1);
25− }
26−
27− public void fire() throws IllegalActionException {
28− int i;
29−
30− if(_readyToGo == false) {
31− DoubleToken t = (DoubleToken) control.get(0);
32− _ctrl = (double) t.doubleValue();
33− }
34−
35− if(_ctrl == 0.0) {
36− if(data1.hasToken(0)) {
37− _readyToGo = false;
38− waitFor(control, 1);
39− output.broadcast(data1.get(0));
40− }
41− else {
42− _readyToGo = true;
43− waitFor(data1, 1);
44− }
45− }
46− else {
47− if(data2.hasToken(0)) {
48− _readyToGo = false;
49− waitFor(control, 1);
50− output.broadcast(data2.get(0));
51− }
52− else {
53− _readyToGo = true;
54− waitFor(data2, 1);
55− }
56− }
57− }
60−
61− public void initialize() throws IllegalActionException {
62− super.initialize();
63−
64− _readyToGo = false;
65− waitFor(control, 1);
66− _ctrl = 0.0;
67− }
68−
69− private boolean _readyToGo;
70− private double _ctrl;
71− }
Figura 2.18 − Código do ator Select no MoC DDF.
As linhas 18 e 21 indicam que os portos de entrada data1 e data2 não possuem taxas de
consumo constante. A leitura bloqueante é implementada através da utilização do método waitFor(). O
primeiro parâmetro desse método é o porto de entrada e o segundo parâmetro indica quantos tokens
devem ser lidos para o ativamento. Inicialmente, o ator Select deve ler o token de controle (linhas 65, 38
e 49) para em seguida ler o dado. Caso esse não esteja disponível, a leitura bloqueante é utilizada
novamente para os portos de dados (linhas 43 e 54). Note a utilização de uma variável interna
(_readyToGo) para indicar o estado em que o ator se encontra e assim determinar de qual porto o
próximo token deve ser consumido.
24
Ao contrário do modelo SDF, o escalonador do MoC DDF não é estático. Dois tipos de
escalonadores dinâmicos existem [PAR95]: data driven e demand driven. Escalonadores tipo data
driven ativam um ator assim que uma de suas regras de ativamento seja válida. Já escalonadores tipo
demand driven ativam um ator baseado na demanda de tokens de um ator sucessor. O ambiente Ptolemy
utiliza um escalonador tipo data driven, classificando os atores prontos para serem ativados como
deferrable ou não. Um ator é deferrable quando alguns de seus sucessores já possuem tokens suficientes
na conexão com o ator. Um ator deferrable só é ativado quando não existirem atores classificados como
não deferrable. Nesse caso, um dos atores deferrable é escolhido aleatoriamente para ser ativado. A
classificação de ator deferrable visa evitar o acúmulo desnecessário de tokens em uma fila.
Outra característica do MoC DDF é que, ao contrário do MoC SDF, não é possível determinar a
quantidade necessária de armazenamento de cada fila de comunicação. É possível que uma execução
venha a ser prematuramente interrompida devido ao estouro de espaço de memória.
2.3.6 Kahn Process Networks
Assim como o MoC CSP, uma especificação executável utilizando o modelo computacional
Kahn Process Networks [KA74][KAH77][GOE98][PAR95] é composta por uma rede de processos, onde
cada ator da especificação é associado à um processo. Tal como nos modelos data flow, cada conexão
entre atores contém uma fila. O modelo PN é determinístico quando cada processo implementa uma
função contínua. Assim como no MoC DDF, isso é implementado através de leitura bloqueante: quando
um ator tenta consumir um token de uma fila vazia, o respectivo processo é bloqueado, sendo reativado
quando o token estiver disponível. O MoC PN não permite o teste da presença de um token, pois isso
tornaria o modelo não−determinístico. Também não é permitido que um ator espere por tokens em mais
de um porto ao mesmo tempo.
Tal como no MoC DDF, não é possível determinar a capacidade necessária de cada fila. A
solução adotada no ambiente Ptolemy utiliza a escrita bloqueante, ou seja, quando um ator tenta produzir
um token e a respectiva fila está cheia, o processo do ator é bloqueado. Quando houver espaço na fila, o
processo é reativado.
É possível que uma execução venha a entrar em deadlock, ou seja, quando todos os atores
estiverem bloqueados. Quando não existe nenhum ator bloqueado devido à situação de filas cheias, diz−
se que o deadlock é real. Nessa situação a execução chegou ao fim. Caso contrário, temos um deadlock
artificial. Neste caso, o ambiente Ptolemy determina uma fila cheia e aumenta seu tamanho, reativando o
respectivo processo e prosseguindo com a execução.
A implementação desse MoC no ambiente Ptolemy possui uma noção de tempo, similar ao do
modelo CSP, ou seja, um processo pode se suspender até um determinado instante de tempo.
2.4 Outros Modelos Computacionais
Vários outros modelos computacionais foram desenvolvidos, além dos seis modelos
computacionais que utilizamos nesse trabalho. O próprio ambiente Ptolemy implementa outros três
modelos. Enumeramos alguns modelos importantes e descritos na literatura:
  Cyclo−Static Data Flow [BIL96]: modelo data flow que estende o MoC SDF permitindo que
um ator tenha mais que uma regra de ativamento. Entretanto, as regras devem apresentar taxas
de consumo e produção constantes e a ordem de utilização de cada regra é definida antes da
execução. Esse modelo também apresenta um escalonamento estático;
  Boolean Data Flow [BUC93]: modelo data flow que estende o MoC SDF permitindo a
variação das taxas de consumo e produção durante a execução. Entretanto, diferentemente do
MoC DDF, essa variação deve sempre estar condicionada à um valor booleano obtido de outro
porto (por exemplo, os atores da figura 2.17). Em alguns casos, é possível encontrar um
escalonamento estático;
25
  Máquinas de Estado Finito [GAJ00]: modelo computacional clássico baseado em autômatos
finitos;
  Redes de Petri [PET81]: modelo de estados explícitos onde a especificação é composta por dois
tipos de elementos: place e transição. Cada place pode conter um elemento denominado de
token. Um place pode ser conectado à uma transição (place de entrada da transição) e uma
transição pode ser conectada à um place (place de saída da transição). Quando todos os place
de entrada de uma transição tiverem pelo menos um token, diz−se que a transição está pronta
para ser ativada. O ativamento consiste em remover um token de todos os place de entrada e
adicionar um token à todos os places de saída. A concorrência é possível pois mais de uma
transição pode estar pronta para ser ativada em um mesmo instante. Neste caso, a escolha é
feita de forma não determinística;
  StateChars [HAR87]: modelo que adicionada à máquinas de estado finito o conceito de
hierarquia e concorrência. A hierarquia é representada através de estados que contenham outras
máquinas de estado. Quando ocorre a transição para um estado hierárquico, a máquina de
estado interna é ativada. A concorrência é modelada através da possibilidade de que mais de
um estado estar ativado simultaneamente. Nesse modelo, é permitido transições de estados
pertencentes à diferentes níveis de hierarquia;
  *Charts [GIR99]: modelo implementado no ambiente Ptolemy que adiciona os conceitos de
hierarquia e concorrência às máquinas de estados finitos. Diferentemente do modelos
StateCharts, o modelo *Charts não define estados concorrentes. A concorrência é obtida
quando vários atores Composite, que implementam subsistemas em *Charts, são incluídos em
uma topologia governada por um modelo com concorrência. Não é permitido transições entre
estados de diferentes hierarquias;
  Codesign Finite State Machine [LAV00]: modelo baseado em uma rede de máquinas de estado.
A comunicação é feita através de broadcast de sinais: uma máquina produz um evento em um
sinal e todas as outras máquinas que dependem desse sinal podem obter o novo evento. A
hipótese de sincronismo é assumida para todas as máquinas de estado mas não aplicada à
interação das máquinas, ou seja, uma vez que um novo evento é produzido, a resposta de outra
máquina à esse novo evento não é infinitamente rápida. Dessa forma, é possível que eventos
não sejam capturados por máquinas com reação lenta, uma vez que não existe o
armazenamento de eventos;
  Control−Data Flow Graph [MIC94]: modelo utilizado para representar um algoritmo
seqüencial através de um grafo direcionado. Existem dois tipos básicos de nós: nós de controle
e basic blocks. Um nó tipo basic block contém uma seqüência de comandos. Nós de controle
determinam os diferentes caminhos possíveis para o fluxo de controle;
  Continuous Time [LIU98]: modelo implementado no ambiente Ptolemy fundamentado na
utilização de equações diferenciais para representar um sistema. Esse modelo possui uma noção
de tempo contínuo. O escalonador desse modelo é composto por um algoritmo que obtenha
resultados aproximados para o sistema de equações em alguns instantes de tempo. Esse modelo
é mais apropriado para representar sistemas analógicos e mecânicos;
  Distributed Discrete Event [DAV99]: modelo implementado no ambiente Ptolemy que procura
solucionar os problemas relacionados ao emprego de uma noção global de tempo, tal como no
modelo DE. O modelo mantém uma noção local de tempo em cada conexão entre atores. Cada
ator é associado à um processo e este pode avançar seu valor de tempo para o mínimo entre os
valores de suas conexões de entrada.
26
2.5 A Ferramenta de Depuração/Profile
Conforme foi ilustrado no item 2.2, uma especificação executável no ambiente Ptolemy é
composta por classes na linguagem Java, escritas pelo usuário, e por classes disponíveis pelo ambiente.
Essas classes passam por um processo de compilação e o código resultante pode ser executado. A análise
do comportamento da execução e dos resultados obtidos fica a cargo do usuário, isto é, o usuário deve
adicionar trechos de códigos à especificação para coletar e apresentar informações relevantes à execução.
Até o presente momento, o ambiente Ptolemy provê auxilio para esta tarefa apenas através de atores que
utilizam o aplicativo PtPlot. Este aplicativo é capaz de ilustrar dados através de vários tipos de gráficos
bidimensionais.
Uma alternativa disponível para a análise da execução seria o uso de uma ferramenta de
depuração para a linguagem Java. Essa solução é recomendável para encontrar erros no código fonte de
cada ator, uma vez que cada comando do código pode ser analisado. Entretanto, esse tipo de ferramenta
não é adequada para a análise de características mais genéricas referentes à interação entre diferentes
atores sob um modelo computacional. Essas informações podem ser úteis para o usuário entender mais
detalhadamente os resultados da execução da especificação e para encontrar erros lógicos não associados
ao código de um ator isoladamente.
Sob a luz do estudo apresentado nessa dissertação, uma ferramenta para a análise da execução de
uma especificação torna−se útil, pois tal ferramenta auxilia identificar as diferenças encontradas ao
utilizar modelos computacionais distintos na representação de uma mesma primitiva computacional.
Dessa forma, uma ferramenta denominada de PTII Analyzer foi desenvolvida com esse objetivo. Sua
principal função é reproduzir uma execução, ilustrando a troca de tokens entre diferentes atores.
Informações estatísticas também podem ser extraídas a partir dos dados coletados.
2.5.1 Dados coletados
A ferramenta desenvolvida baseia−se em dados coletados durante a execução de uma
especificação. Para tal, acrescentamos trechos de código em classes específicas do ambiente Ptolemy.
Dois tipos de conjunto de dados são armazenados: a produção ou o consumo de um token e a mudança
de estado de um ator.
Para cada evento de consumo ou produção de um token, armazenamos o seguinte conjunto de
informações:
  tipo do evento: consumo ou produção;
  identificador do token: um valor inteiro único para cada token durante a execução;
  ator de origem;
  porto de origem;
  ator de destino;
  porto de destino;
  valor do tempo no momento do evento;
  instante.
O instante é um valor inteiro único para cada conjunto de informações e utilizado a fim de
ordenar os dados coletados permitindo ilustrar a reprodução da execução.
O segundo tipo de conjunto de dados armazenado é referente a mudança de estado de um ator.
Definimos três possíveis estados: bloqueado, pronto ou executando. Em alguns MoCs, subdividimos o
estado bloqueado em bloqueado no consumo ou na produção de um token. Quando ocorre uma mudança
de estado, armazenamos o seguinte conjunto de dados:
  ator;
  novo estado;
  valor do tempo no momento do evento;
  instante.
27
Definimos que um ator está no estado executando quando este está consumindo ou produzindo
tokens. A determinação dos outros estados depende da semântica do modelo computacional que controla
o ator. Para os seis modelos computacionais utilizados, definimos:
SDF:
  bloqueado: não há dados suficientes nos portos de entrada;
  pronto: há dados suficientes nos portos de entrada.
DDF: igual ao caso do MoC SDF.
PN:
  bloqueado: quando a respectiva fila estiver vazia (bloqueado no consumo) ou quando a
respectiva fila estiver cheia (bloqueado na produção);
  pronto: quando o ator estava executando mas foi escalonado.
DE:
  bloqueado: este estado não existe nesse MoC;
  pronto: quando o ator não está executando.
CSP:
  bloqueado: quando o ator está esperando para iniciar ou concluir o rendezvous;
  pronto: quando o ator estava executando mas foi escalonado.
SR:
Quando o ator é Strict:
  bloqueado: não há a presença de eventos exigida;
  pronto: há a presença dos eventos exigidos.
Quando o ator é Non−Strict:
  bloqueado: este estado não existe nesse caso;
  pronto: quando o ator não está executando.
2.5.2 A Interface Gráfica
O principal componente da ferramenta PTII Analyzer é sua interface gráfica, pois apresentar os
dados coletados da execução na forma de uma tabela não seria útil ao usuário. A ferramenta permite dois
tipos de análise: depuração e profiling. A depuração é feita reproduzindo−se graficamente a interação
entre os atores. O profiling é feito através de estatísticas calculadas a partir dos dados e apresentados ao
usuário na forma de gráficos e tabelas. A figura 2.19 apresenta a janela com o menu principal da
ferramenta.
Figura 2.19 − A janela inicial da ferramenta PTII Analyzer.
Dados coletadosUm mesmo token Barra de scroll de instantes
28
A janela inicial da ferramenta contém uma tabela para mostrar os dados coletados. Quando o
usuário seleciona o campo de identificação de um token, todos os demais campos da tabela referentes ao
mesmo token são destacados. Inicialmente a primeira entrada da tabela é selecionada (instante 1). A
janela inicial possui uma barra de scroll que permite selecionar a próxima entrada da tabela, ou a
anterior. Aliado à janela que exibe a topologia da especificação, o usuário pode utilizar essa barra de
scroll para reproduzir a execução. A janela da topologia é mostrada quando o usuário carrega o layout
gráfico da topologia. A figura 2.20 apresenta essa janela.
Figura 2.20 − A janela da topologia.
Se os dados da execução foram carregados, a janela da topologia irá ilustrar o instante da entrada
selecionada na tabela. Os atores são coloridos de acordo com o estado do instante selecionado: verde se
estiver executando, amarelo se estiver pronto, vermelho se estiver bloqueado, roxo se estiver bloqueado
na produção e rosa se estiver bloqueado no consumo. Os portos são coloridos quando o respectivo
evento for selecionado na tabela: vermelho quando é uma produção e azul quando for um consumo.
Também é possível inspecionar o estado de cada receiver até o instante selecionado. No canto direito
inferior da figura 2.20, é mostrada uma janela que apresenta tal informação.
A tarefa de depuração é feita com o auxilio da janela principal e da janela de topologia. A tarefa
de profiling utiliza outras janelas. A figura 2.21 apresenta a janela inicial para essa tarefa.
Ator Porto
Informações sobre o
nível de hierarquia
Botões de edição da figura
Estado do receiver no instante atualAtores da especificação
29
Figura 2.21 − Janela inicial para o profiling.
A janela da figura 2.21 apresenta na seu lado esquerdo uma árvore representando a hierarquia
dos atores e portos associados. Selecionando um ator opaco ou porto, o sumário das informações
estatísticas para o respectivo objeto é apresentado. Também é possível restringir a análise à um
subconjunto dos dados determinado−se um intervalo de instantes. O valor padrão inclui todos os dados.
Através do menu dessa janela o usuário pode visualizar os dados em tabelas e gráficos. A figura 2.22
apresenta a tabela de dados referentes à atores.
Figura 2.22 − Tabela de dados estatísticos de atores.
A tabela da figura 2.22 apresenta nove dados para cada ator: as porcentagens de instantes em que
o ator esteve em cada um dos três estados e o intervalo mínimo e máximo que um ator esteve em cada
um dos três estados consecutivamente. A figura 2.23 apresenta a tabela de dados referente aos portos.
Sumário das estatísticas
Hierarquia de atores e
portos da especificação
Inclui os dados do ator
ou porto nos gráficos
30
Figura 2.23 − A tabela de dados dos portos.
A ferramenta utiliza duas tabelas para apresentar os dados estatísticos referentes aos portos: a
primeira é para cada porto isoladamente e a segunda é referente à conexão entre dois portos. Para cada
porto é calculado quantos dados foram produzidos, consumidos e os intervalos mínimos e máximos entre
cada consumo e produção de tokens. Para cada conexão é registrado o valor máximo e a média de tokens
acumulados durante o intervalo de instantes especificado.
A janela inicial de profiling (figura 2.21) possui um botão para incluir os dados de um ator ou
porto nos gráficos (os dados das tabelas são incluídos automaticamente). A figura 2.24 mostra o gráfico
da evolução do estado de um ator com relação aos instantes.
Estatísticas de cada porto
Estatísticas de cada conexao
31
Figura 2.24 − Gráfico da evolução do estado de um ator.
A janela da figura 2.24 utiliza o aplicativo PtPlot presente no ambiente Ptolemy para mostrar o
gráfico. O valor 1.0 representa o estado executando, o valor 0.0 representa o estado pronto e o valor −1.0
o estado bloqueado. A ferramenta dispõe de um gráfico alternativo para a visualização da evolução do
estado de um ator, conforme a figura 2.25 demonstra.
Figura 2.25 − Gráfico alternativo da evolução dos estados de um ator.
Com o gráfico da figura 2.25 é mais fácil comparar a evolução de vários atores simultaneamente.
A evolução da quantidade de tokens em cada conexão também pode ser estudada através de um
gráfico, exemplificado na figura 2.26.
Figura 2.26 − Exemplo de gráfico da evolução da quantidade de tokens em uma conexão.
Atores
Atores
Conexões
32
Capítulo 3
Primitivas Comportamentais
3.1 Introdução
A metodologia que empregamos para determinar a eficiência de cada MoC é baseada no conceito
de primitiva comportamental. Definimos uma primitiva comportamental como um tipo de
comportamento básico utilizado pelo projetista para capturar um trecho do sistema. Uma primitiva
comportamental deve possuir duas características: não ser ambígua e ser genérica. Criamos o conceito de
primitiva comportamental a partir da analogia com o conceito de base em um espaço vetorial: uma
especificação executável pode ser decomposta em um conjunto de primitivas da mesma forma como os
pontos de um espaço vetorial podem ser encontrados a partir de bases. Entretanto, ao contrário de bases,
primitivas comportamentais não são necessariamente independentes. Para capturar diferentes primitivas
comportamentais utilizamos ¨toy benchmarks¨, que são exemplos de pequenos sistemas que demonstram
fortemente a presença da primitiva.
Podemos citar como exemplo de primitivas comportamentais construções de linguagens de
programação imperativa tais como seqüência de expressões, iteração, desvios condicionais, chamada de
procedimentos, etc. Comportamentos tais como recursão, sincronização, preempção e concorrência
também são primitivas.
Neste capítulo apresentaremos o estudo das primitivas comportamentais que enumeramos.
Obtivemos essa lista de primitivas a partir de nossa experiência de criação de especificações executáveis.
Cada primitiva é capturada através de um toy benchmark. Em seguida, uma ou mais especificações para
cada um dos seis modelos computacionais utilizados são descritas e comentadas. Em seguida,
observações referentes à análise da execução são apresentadas. Ao final do capítulo, será apresentado um
resumo das conclusões referentes à adequação de cada modelo computacional na captura das primitivas
comportamentais.
3.2 Seqüência de Expressões
Uma seqüência de expressões (basic block) é um trecho de código caracterizado por ter fluxo de
controle seqüencial sem repetições, desvios condicionais e chamadas de procedimentos. Essa primitiva
foi escolhida pois sistemas costumam apresentar este tipo de comportamento.
3.2.1 Exemplo de Sistema
O cálculo dos pontos de uma curva borboleta10
será usado como exemplo para capturar essa
primitiva. Um procedimento em linguagem C que implementa esse cálculo é mostrado na figura 3.1.
butterfly(double input, double *x, double *y) {
double t1, t2, t3;
t1 = input * 1/12.0; // Scale1
t1 = sin(t1); // Sine
t2 = t1 * t1; // Mult1
t2 = t2 * t2; // Mult2
t3 = t2 * t1; // Mult3
t1 = input * 4.0; // Scale2
t1 = cos(t1); // Cos2
t1 = −2.0 * t1; // Scale3
10 Esse exemplo foi extraído do conjunto de demonstrações da versão anterior (Classic) do ambiente Ptolemy.
33
t2 = cos(input); // Cos1
t2 = exp(t2); // Exp
t3 = t3 + t2 + t1; // Add
*x = t3 * cos(input); // PtoR
*y = t3 * sin(input); // PtoR
}
Figura 3.1 − Descrição em linguagem C do cálculo dos pontos da curva borboleta.
A entrada para o sistema é um valor inteiro e o sistema produz um ponto da curva com base no
valor de entrada.
3.2.2 Especificação Executável
Uma das primeiras decisões a serem tomadas na criação de uma especificação executável no
ambiente Ptolemy é a escolha de quais atores devem ser utilizados. Isso implica em definir qual é a
funcionalidade de cada um e qual será a interação entre eles. Ao final, o conjunto de atores deve capturar
o comportamento desejado.
No caso do sistema da figura 3.1, tivemos que determinar a associação entre uma expressão do
procedimento com um ator na especificação, isto é, determinar a granularidade da função implementada
por cada ator. Embora uma solução fosse criar apenas um ator que implemente o procedimento, essa
solução trivial não é interessante para o estudo da primitiva, uma vez que implementando−a através de
um único ator não iria expor a primitiva às características do modelo computacional. Desta forma,
optamos por utilizar uma granularidade a mais fina possível: cada expressão do procedimento é
implementada por um ator. A única exceção são as últimas duas expressões do procedimento que são
implementados por um único ator. A figura 3.2 apresenta a topologia obtida. O comentário ao lado de
cada expressão do procedimento da figura 3.1 indica qual ator implementa a respectiva expressão. Os
atores Ramp e Plotter são usados respectivamente para gerar valores e mostrar a curva resultante.
Figura 3.2 − A topologia da especificação executável para o exemplo curva borboleta.
Uma característica da especificação da figura 3.2 é que apenas atores domain polymorphic
[DAV99] foram utilizados, devido à simplicidade da funcionalidade de cada ator. Desta forma, a mesma
especificação pode ser utilizada com diferentes modelos computacionais. A única modificação necessária
à especificação foi sob o modelo DE, uma vez que esse modelo computacional é baseado no
processamento de eventos. Nesse caso um ator que gera eventos periodicamente (Clock) foi usado para
iniciar um ciclo de execução.
É importante notar que a especificação da figura 3.2 não captura o procedimento da figura 3.1,
mas sim o cálculo da curva borboleta, isto porque o modelo computacional do procedimento é uma
máquina RAM e outros modelos foram utilizados na especificação da figura 3.2. Por exemplo, mais de
um ator pode ser executado simultaneamente utilizando um modelo data flow, o que não ocorre com o
procedimento da figura 3.1.
Ramp
Ramp
Ramp
Scale1
Ramp
Scale2
Ramp
Cos1
Ramp
Sine
Ramp
Cos2
Ramp
Exp
Ramp
Mult1
Ramp
Scale3
Ramp
Mult2
Ramp
Mult3
Ramp
Add
Ramp
PtoR
Ramp
Plotter
22
34
3.2.3 Análise da Execução11
3.2.3.1 SDF
A análise da execução de uma especificação usando o modelo SDF é sempre previsível,
conforme foi descrito no capítulo 2. A especificação obtida para a primitiva é um grafo homogêneo
(todas as taxas de amostragem são iguais à 1), pois a comunicação entre atores corresponde a escrita e
leitura de variáveis temporárias (t1, t2, t3) do procedimento mostrado na figura 3.1. Desta forma, cada
ator foi ativado uma vez a cada iteração.
3.2.3.2 DDF
A seqüência de disparos de atores foi similar ao obtido utilizando o MoC SDF, devido ao
escalonador utilizar o conceito de ator deferrable. O ator Ramp ilustra essa situação: uma vez que ele
dispara, é classificado como deferrable até que o ator PtoR dispare. Utilizando um escalonador sem a
classificação de ator deferrable, o ator Ramp e seus sucessores são disparados sucessivamente,
permitindo a existência de paralelismo temporal. Nesse caso, o número máximo de tokens foi observado
na conexão Ramp/PtoR, variando entre 7 e 8 tokens. Esse limite está relacionado ao caminho de dados
mais longo do grafo a partir do ator Ramp até o ator PtoR. As figuras 3.3 e 3.4 ilustram momentos de
uma execução utilizando ou não um escalonador puramente data driven. O número ao lado de alguns
atores representa a quantidade de tokens nas respectivas filas de entrada.
Figura 3.3 − Situação da execução em um momento inicial utilizando o escalonador com classificação de ator
deferrable.
Figura 3.4 − Situação da execução em um momento inicial utilizando o escalonador sem classificação de ator
deferrable.
3.2.3.3 PN
Como no caso do MoC DDF, a conexão Ramp/PtoR foi o empecilho para a presença de
paralelismo temporal. Caso não inicializada com um valor diferente, a capacidade inicial de cada fila é 1,
só sendo modificada na ocorrência de um deadlock artificial. Modificando esse valor inicial de 1 para 5,
a porcentagem de instantes em que o ator Ramp esteve bloqueado caiu de 43 para 8.5. Aumentando esse
11 É importante ressaltar que as análises desse item são referentes às implementações dos modelos computacionais no ambiente Ptolemy.
Ramp
Ramp
Ramp
Scale1
Ramp
Scale2
Ramp
Cos1
Ramp
Sine
Ramp
Cos2
Ramp
Exp
Ramp
Mult1
Ramp
Scale3
Ramp
Mult2
Ramp
Mult3
Ramp
Add
Ramp
PtoR
Ramp
Plotter
1
2
2
1
1
1
3
Executando
Pronto
Bloqueado
Ramp
Ramp
Ramp
Scale1
Ramp
Scale2
Ramp
Cos1
Ramp
Sine
Ramp
Cos2
Ramp
Exp
Ramp
Mult1
Ramp
Scale3
Ramp
Mult2
Ramp
Mult3
Ramp
Add
Ramp
PtoR
Ramp
Plotter
1
1
1
Executando
Pronto
Bloqueado
35
valor suficientemente obtivemos uma execução similar ao MoC DDF com um escalonador puramente
data driven.
3.2.3.4 DE
Os atores foram ativados seguindo uma ordem topológica. Esse ordem foi gerada conforme
descrito no capítulo 2. Apenas o ator Clock, utilizado para disparar o ator Ramp, gera eventos com
atraso.
3.2.3.5 CSP
Comparando com o MoC PN, a porcentagem de instantes que um ator esteve bloqueado
aumentou, com conseqüente redução do número de instantes em que o mesmo ator esteve pronto. A
semântica de comunicação rendezvous explica essa observação. O ator Scale3 ilustra tal situação: após
iniciar o envio do token, ele é bloqueado esperando pelo ator Add obter o token. Esse ator também deve
consumir tokens enviados pelos atores Mult3 e Exp. Desta forma, o ator Scale3 fica bloqueado durante
um período que depende de como o ator Add foi implementado. Essa situação não ocorre com o MoC
PN, uma vez que a comunicação é feita através de filas unidirecionais.
Observamos que esse exemplo possui uma situação onde um deadlock pode ocorrer, decorrente
de como um ator foi implementado e de como a topologia foi construída (vide figura 3.2). O ator Ramp
envia um token para quatro outros atores: Scale1, Scale2, Cos1 e PtoR. O ambiente Ptolemy irá enviar tal
token através de cada conexão. A ordem em que essas conexões são acessadas irá depender de como a
topologia foi criada. Se a conexão entre o ator Ramp e o ator PtoR foi criada antes da conexão para o ator
Cos1, a execução entra em deadlock. Isso porque o actor Ramp estaria bloqueado para produção (write
blocked) na conexão com o ator PtoR, o actor PtoR estaria bloqueado para consumo (read blocked) na
conexão com o actor Add e o esse ator nunca seria executado uma vez que o caminho a partir do ator
Cos1 não produziria nenhum token.
3.2.3.6 SR
Assim como o modelo SDF, a análise da execução é previsível. A especificação utilizada é
composta apenas por atores strict e não possui nenhum ciclo. Desta forma, cada ator foi disparado uma
vez, respeitando uma ordem topológica.
3.3 Desvio Condicional
A primitiva comportamental desvio condicional é caracterizada pela execução ou não de um
determinado bloco de atores com base no valor de uma expressão condicional. Um desvio condicional
pode ser composto por mais de um bloco (branch) de atores, cada bloco associado à uma expressão
condicional. As condições são verificadas de forma seqüencial, isto é, o resultado final irá depender da
ordem em que forem especificadas. Uma vez que alguma condição seja satisfeita, o bloco associado é
executado, sem calcular as condições seguintes. Essa primitiva também foi extraída de linguagens de
programação imperativa.
3.3.1 Exemplo de Sistema
O cálculo do coeficiente angular de uma reta foi o toy benchmark escolhido. A figura 3.5
apresenta a implementação desse exemplo em linguagem C.
double coef(int x1, int y1, int x2, int y2) {
if(x1 == x2) { // Cond1
return 1.0; // Cte1
}
else
if(y1 == y2) { // Cond2
36
return 0.0; // Cte2
}
else {
return (double) (y1 − y2)/(x1 − x2); // Coef
}
}
Figura 3.5 − Descrição em linguagem C do cálculo do coeficiente angular de uma reta.
Nesse exemplo, duas igualdades são as condições a serem computadas e três blocos de atores são
utilizados. A entrada para o sistema é o valor de dois pontos ( (x1,y1) e (x2,y2) ) da reta e o sistema
produz como saída o valor do coeficiente angular.
3.3.2 Especificação Executável
A criação de uma especificação executável que capture a primitiva comportamental desvio
condicional foi mais complexa com relação à primitiva anterior. Ao total, cinco diferentes especificações
foram criadas. Descreveremos separadamente as soluções obtidas para cada modelo computacional
estudado.
3.3.2.1 SDF
O modelo SDF apresenta uma regra que dificulta a captura da primitiva: cada ator deve consumir
e produzir um número fixo de tokens, sendo esses valores especificados a priori. Desta forma, a cada
iteração, um ator deve ser ativado pelo menos uma vez. Ao contrário, para capturar adequadamente a
primitiva, apenas um dos possíveis blocos de atores deve ser executado para cada conjunto de entradas.
A topologia da primeira especificação desenvolvida para capturar a primitiva utilizando o MoC
SDF é ilustrada na figura 3.6.
Figura 3.6 − Topologia da primeira especificação em SDF.
A topologia é composta pelos seguintes atores:
 Cte1 e Cte2: dois atores que produzem um valor constante (e especificado por um parâmetro)
toda vez que são ativados;
 Coef: calcula a expressão do coeficiente angular;
 Comp1 e Comp2: produzem um valor igual à 1 no porto de saída caso o valor das entradas
sejam iguais, do contrário produz um valor 0;
 Mux: recebe os resultados de todos os outros atores e produz o resultado final do desvio.
A especificação criada pôde ser capturada no modelo SDF, pois todos os atores consomem e/ou
produzem um token em cada conexão. Parte do comportamento da primitiva foi capturado pelos atores
Comp1
Comp2
Cte1
Cte2
Coef Mux
X1
X2
Y1
Y2
37
Comp1 e Comp2 e parte pelo ator Mux. Embora fosse possível implementar o ator Mux utilizando um
comando tipo if−then−else presente na linguagem Java, isso foi evitado utilizando uma matriz
associando o par de valores condicionais a um porto de entrada. A figura 3.7 mostra o corpo desse ator.
1− ...
2− IntToken t1 = (IntToken) v1.get(0);
3− IntToken t2 = (IntToken) v2.get(0);
4− DoubleToken t3 = (DoubleToken) v3.get(0);
5−
6− _table[1][0] = t1.intValue();
7− _table[1][1] = _table[1][0];
8−
9− _table[0][1] = t2.intValue();
10− _table[0][0] = t3.doubleValue();
11−
12− int index1 = ((IntToken) ctrl1.get(0)).intValue();
13− int index2 = ((IntToken) ctrl2.get(0)).intValue();
14−
15− output.broadcast(
16− new DoubleToken(_table[index1][index2]));
17− ...
Figura 3.7 − Trecho do código do ator Mux.
De maneira geral, a estratégia utilizada na especificação da figura 3.6 é valida para capturar a
primitiva no MoC SDF. Entretanto, essa solução não é totalmente satisfatória, pois todos os atores
implementando os blocos e condições são sempre ativados e somente em seguida uma decisão é tomada.
Além desse desvantagem, o exemplo do coeficiente angular apresenta uma característica
particular: o terceiro caminho só pode ser executado quando a condição (x1 == x2) for falsa. Caso
contrário, uma divisão por zero irá ocorrer, gerando um erro fatal. Uma possível solução seria adicionar
como entrada do ator Coef o valor da primeira condição calculada pelo ator Comp1, e utilizar um
comando if−then−else dentro do ator.
Para obter uma especificação válida para o cálculo do coeficiente angular, implementamos outra
solução utilizando o conceito de função high−order [HUD89], ou seja, uma função que tem como
parâmetros funções e/ou que retorna como resultado outras funções. A topologia da especificação obtida
é mostrada na figura 3.8.
Figura 3.8 − Topologia da segunda especificação utilizando o MoC SDF.
O atores são:
 Comp1 e Comp2: mesma função que na especificação da figura 3.7;
 Map: recebe o valor de ambas as condições e determina qual deve ser a função a ser executada.
Uma vez determinada, a função é inserida em um token do tipo objeto (ObjectToken) e enviada
ao ator Apply.
 Apply: lê os valores das entradas (parâmetros da função) e executa a função recebida.
Como duas funções diferentes devem ser executadas (geração de constantes e cálculo da
expressão do coeficiente), utilizamos um objeto do tipo Interface da linguagem Java para transmitir a
função do ator Map ao ator Apply. A figura 3.9 apresenta o código desse objeto:
package ptij.domains.sdf.demo.ifelse2;
public interface Function {
public double apply(int x1, int x2, int y1, int y2);
}
Figura 3.9 − Interface para uma função do exemplo do coeficiente angular
Comp1
Comp2
X1
X2
Y1
Y2
Map Apply
X1
X2 Y1
Y2
38
Cada diferente função implementa a Interface da figura 3.9, definindo o código da função apply.
Como a expressão para o cálculo do coeficiente necessita dos valores de ambos os pontos, estes estão
incluídos como parâmetros da função apply. A implementação da geração de um valor constante ignora
esses parâmetros. Isto também é válido para o ator Apply, que deve ter como entradas a união dos
parâmetros de todas as funções que podem ser executadas.
A necessidade de adicionar entradas, mesmo que desnecessárias para algumas funções, é um
ponto negativo da especificação da figura 3.8, isto porque dados serão recebidos desnecessariamente.
3.3.2.2 DDF
O MoC DDF permite a variação em tempo de execução da quantidade de tokens recebidos e
enviados por um ator. Desta forma, desenvolvemos novas especificações utilizando tal característica. A
figura 3.10 mostra a topologia da primeira especificação executável.
Figura 3.10 − Topologia da primeira especificação utilizando o modelo DDF.
Os atores Comp1, Comp2, Cte1, Cte2 e Coef implementam as mesmas funções que nas
especificações anteriores. A única modificação necessária foi a inclusão de um porto de entrada (Trigger)
aos atores Cte1 e Cte2. A finalidade deste porto é controlar o ativamento dos respectivos atores. Dois
novos atores foram desenvolvidos: Switch e Select (vide item 2.3.5). O ator Switch é responsável por
enviar os dados da entrada do sistema aos respectivos atores, com base no valor das condições. O ator
Select determina qual é o porto de entrada que contém o próximo token, a partir do valor das condições.
O ponto favorável da especificação da figura 3.10 é que um bloco do sistema só é executado
quando a expressão condicional associada à ele for verdadeira. Dois pontos negativos podem ser
observados: todos os valores de entrada dos blocos do sistema devem passar pelo ator Switch e ambas as
condições são sempre computadas. Com o intuito de remover esses problemas, a especificação ilustrada
na figura 3.11 foi criada.
Comp1
Comp2
X1
X2
Y1
Y2
Switch
Ramp
Coef
Cte2
Cte1
Select
Trigger
Trigger
39
Figura 3.11 − Topologia da segunda especificação utilizando o MoC DDF.
A especificação da figura 3.11 contém os seguintes atores:
 Cte0, Cte1 e Cte2: igual à especificação da figura 3.10;
 Coef: como nas outras especificações;
 Comp1 e Comp2: como nas outras especificações;
 D: ator que implementa um demultiplexador. Com base em um token de controle (na figura
3.11, é a entrada mostrada no lado superior ou inferior do retângulo) determina para qual porto
de saída o dado de entrada deve ser enviado.
 M: similar ao ator D, mas implementando uma função de multiplexador, ou seja, com base no
token de controle, determina qual entrada deve ser a próxima a ser lida.
A especificação da figura 3.11 calcula uma condição apenas quando as anteriores falharam. Os
dados Y1 e Y2 são enviados ao ator Comp2 apenas quando a condição C1 é 0 (falsa), desta forma,
controlando o ativamento do ator. No caso dos atores Cte1 e Cte2, o resultado dos atores condicionais
roteia um token de controle (gerado pelo ator Cte0). No caso do ator Coef, os atores condicionais
eliminam ou não os dados necessários pelo ator. Essa eliminação é necessária (também no caso do ator
Comp2) devido às filas unidirecionais utilizadas em cada conexão: caso um ator não fosse ativado, e os
dados não fossem descartados, ele iria receber na próxima execução dados antigos.
3.3.2.3 PN
Todas as especificações mostradas até o momento são válidas para o MoC PN. Nenhuma outra
foi desenvolvida utilizando características específicas do mesmo.
3.3.2.4 CSP
As especificações mostradas anteriormente também podem ser usadas com o MoC CSP. Embora
esse MoC não utilize filas entre atores para a comunicação dos dados, todas as especificações transmitem
Comp1
X1
X2
D
0
1Cte0
D
0
1
D
0
1
Comp2
D
0
1
Cte1
Cte2
M
0
1
M
0
1
Ramp
CoefD
0
1
D
0
1
Y1
Y2
C1
D
0
1
D
0
1
D
0
1
D
0
1
D
0
1
D
0
1
X1
X2
Y1
Y2
C1
C1
C1
Trigger
Trigger
40
apenas um token em cada conexão.
Neste MoC, fizemos uma tentativa de alteração da especificação da figura 3.11, a fim de torná−
la mais eficiente. Os dois atores multiplexer (M) foram substituídos por um ator com comportamento não
determinístico (MuxND da figura 3.13). A figura 3.12 mostra parte do código desse ator.
1− ConditionalBranch [] branches = new ConditionalBranch[3];
2−
3− branches[0] = new ConditionalReceive(true, a, 0, 0);
4− branches[1] = new ConditionalReceive(true, b, 0, 1);
5− branches[2] = new ConditionalReceive(true, c, 0, 2);
6−
7− int result = chooseBranch(branches);
8− if(result == 0) {
9− output.broadcast(branches[0].getToken());
10− }
11− else
12− if(result == 1) {
13− output.broadcast(branches[1].getToken());
14− }
15− else
16− if(result == 2) {
17− output.broadcast(branches[2].getToken());
18− }
Figura 3.12 − Trecho de código do ator MuxND no MoC CSP.
Para implementar o comportamento do ator MuxND, utilizamos o comando de comunicação
guarded. Da linha 1 à linha 5 do código da figura 3.12, as três possibilidades são criadas, uma para cada
porto de entrada do ator (a, b, c). A expressão condicional de cada comando de comunicação é sempre
verdadeira, desta forma, evitando uma seqüência fixa de verificação dos portos de entrada.
A inclusão do ator MuxND pretendia eliminar a necessidade de comunicar os valores
condicionais aos atores multiplexer da especificação. Esses atores são utilizados para combinar os
resultados dos blocos de atores do desvio condicional, enviando o resultado para a saída do sistema.
Como apenas um dos três possíveis caminhos pelos atores do corpo do desvio condicional (Cte1, Cte2 e
Coef) é utilizado para um dado conjunto de entradas, não seria necessário enviar os valores condicionais
aos atores multiplexer.
O problema com a modificação adotada é que ela torna o comportamento da especificação não
determinístico. Isso ocorre pois não é possível garantir que a propagação dos resultados de um conjunto
de entrada irá terminar antes da propagação de outro conjunto posterior devido ao escalonamento
(desconhecido e aleatório) dos diferentes processos. Na figura 3.11, uma vez que o primeiro bloco de
atores D tenha lido os valores C1, X1, X2, Y1, Y2, outro conjunto de entradas pode ser propagado, isto
porque o ator Comp1 e os atores que geram os valores (X, Y) estão desbloqueados (terminaram o
rendezvous) . Caso o próximo grupo de entradas seja propagado pelo ator Cte1, não é possível garantir a
ordem final dos resultados.
3.3.2.5 DE
Todas as especificações anteriores são válidas nesse MoC. Entretanto, utilizando as
características particulares desse MoC, desenvolvemos uma nova especificação. A figura 3.13 apresenta
a topologia desta especificação.
Figura 3.13 − Topologia da especificação no MoC DE.
Comp1
Comp2
Cte1
Cte2
Coef
MuxND
X1
X2 Y1
Y2
X1
X2
Y1
Y2
Trigger
41
Os atores do corpo do desvio, Cte1, Cte2 e Coef permanecem com a mesma função. Apenas o
ator Coef foi modificado: quando um evento está presente em um dos portos de entrada, ele é
armazenado em uma variável interna. A figura 3.14 mostra um trecho de código deste ator.
1− ...
2− if(x1.hasToken(0)) {
3− _x1 = ((IntToken)x1.get(0)).intValue();
4− }
5− ...
6− if(trigger.hasToken(0)) {
7− trigger.get(0);
8− output.broadcast(new DoubleToken( (_y1 − _y2)/(_x1 − _x2) ));
9− }
10− ...
Figura 3.14 − Trecho de código do ator Coef no MoC DE.
Quando um evento está presente no porto Trigger (figura 3.13), um novo valor de saída é
computado com base nos valores armazenados dos dados.
Os atores que calculam os valores condicionais do desvio, Comp1 e Comp2, possuem na nova
especificação três portos de entrada (o porto Trigger do ator Comp1 é ignorado) e dois de saída. Além
dos dois valores de dados a serem comparados, um novo porto de entrada, utilizado para ativar o cálculo
da comparação (mesma técnica do trecho de código da figura 3.14), foi incluído. Dois portos de saída
são utilizados, um para cada condição. Apenas um dos dois portos terá um evento para cada par de
valores de entrada.
O ator MuxND foi utilizado nessa especificação: ao receber um evento em qualquer porto de
entrada, o mesmo é enviado para o porto de saída. Caso existam eventos simultâneos, a ordem será
conforme os portos de entrada são verificados.
3.3.2.6 SR
Utilizando atores strict, é possível implementar as especificações das figuras 3.6, 3.8, 3.10, 3.11.
A especificação da figura 3.13 também pode ser utilizada. Nesse caso, o ator MuxND passa a ser um ator
non−strict, isto é, tão logo um evento em um dos portos de entrada esteja presente, o evento no porto de
saída nesse instante será o valor lido. A figura 3.15 mostra o método fire() desse ator.
1− public void fire() throws IllegalActionException {
2− if(a.present(0)  !output.known(0)) {
3− output.broadcast(a.get(0));
4− }
5−
6− if(b.present(0)  !output.known(0)) {
7− output.broadcast(b.get(0));
8− }
9−
10− if(c.present(0)  !output.known(0)) {
11− output.broadcast(c.get(0));
12− }
13− }
Figura 3.15 − Método fire() do ator MuxND no MoC SR.
É importante notar que o ator MuxND, implementado como mostra a figura 3.15, não é um ator
monotônico12
, portanto, sua utilização no MoC SR é inválida. Foi possível executar a modelo no
ambiente Ptolemy, pois a implementação que fizemos do MoC SR não efetua nenhum tipo de verificação
de consistência. Entretanto, podemos (o usuário) garantir que mesmo com esse ator, a especificação
captura o comportamento de forma adequada (determinística). Isso se deve à mutua exclusão presente na
primitiva de desvio condicional: é garantido que em um determinado instante, apenas um dos sinais (a,
b, c) estará presente, tornando o código da figura 3.15 válido.
12 Na literatura, esse ator (e variantes) é denominado nondeterminate merge [PAN92][LEE95], e foi demonstrado que seu comportamento não
é monotônico [BRO81].
42
3.3.3 Análise da Execução
3.3.3.1 SDF
Como todas as especificações para a primitiva de desvio condicional são homogêneas, a
execução ativou cada ator respeitando o escalonamento, que nesse caso é uma ordem topológica do
grafo.
3.3.3.2 DDF
As figuras 3.16 e 3.17 apresentam um exemplo da evolução do estado do ator Comp2 para as
especificações das figuras 3.10 e 3.11 respectivamente. O eixo da abcissas indica um instante da
execução e o das ordenadas o estado do ator. Nota−se o ativamento constante e repetido do ator para
quatro conjuntos de valores de entrada no caso da especificação da figura 3.10. Já para a especificação da
figura 3.11, após o segundo conjunto de dados, o ator permanece um longo período bloqueado (sem
dados). Nesse período, dois conjuntos de dados foram aplicados e que não necessitavam do cálculo da
segunda expressão condicional.
Figura 3.16 − Exemplo da evolução do estado do ator Comp2 para a especificação da figura 3.10 no MoC DDF.
Figura 3.17 − Exemplo da evolução do estado do ator Comp2 para a especificação da figura 3.11 no MoC DDF.
Embora a especificação da figura 3.11 seja mais bem elaborada, notamos que a especificação da
figura 3.10 teve um tempo de execução menor (100ms em média) que a especificação da figura 3.11
(220ms em média). Isso pode ser explicado pelo maior número de atores na segunda especificação, com
43
conseqüente aumento do overhead imposto pelo escalonamento dinâmico. Outro ponto relevante à
diferença no tempo de execução é a simplicidade do código de cada ator, principalmente do código dos
atores Comp1 e Comp2. Caso tais atores envolvessem funções mais complexas, a especificação da figura
3.11 passaria a ser mais vantajosa.
3.3.3.3 PN
Conforme foi mencionado no capítulo 2, uma característica que diferencia esse MoC com
relação ao MoC DDF é a utilização de processos executando de forma contínua e ininterrupta ao invés de
ativamentos atômicos de atores. Isso pode ser observado comparando−se a figura 3.18 com a figura 3.17:
no caso da figura 3.18, a execução foi fragmentada em vários instantes. O máximo intervalo contínuo em
que o ator esteve executando foi por 6 instantes, mas a maioria foi por 1 instante.
Figura 3.18 − Exemplo da evolução do estado do ator Comp2 para a especificação da figura 3.11 no MoC PN.
Nenhuma observação adicional pôde ser feita quando o parâmetro do tamanho inicial das filas de
comunicação foi alterado.
3.3.3.4 CSP
Observamos novamente o problema de deadlock no MoC CSP. O ator D da figura 3.11 tinha
sido originalmente implementado de forma a obter o primeiro token de controle e em seguida o dado a
ser roteado. No caso dos modelos data flow e no modelo PN, isso não acarretou problemas. No modelo
CSP, ocorreu um deadlock, pois o ator que gerava os valores (X,Y) estava bloqueado tentando enviar o
valor X1 para um ator D, este esperando o valor de controle gerado pelo ator Comp1, que não podia
produzir um resultado enquanto não obtivesse o valor X2. Modificamos a implementação do ator D, e a
especificação não apresentou mais deadlocks.
3.3.3.5 DE
Como no exemplo da primitiva seqüência de expressões, apenas o Clock gera eventos com
atraso. Nenhuma característica particular pôde ser observada, apenas verificamos o comportamento
adequado da especificação da figura 3.13.
3.3.3.6 SR
Nenhuma característica relevante foi observada. O escalonamento é trivial (ordem topológica) e
a especificação é válida. Como foi destacado no item 3.3.2.6, observou−se que os sinais a, b e c do ator
MuxND nunca possuem eventos simultaneamente.
44
3.4 Iteração com Duração Fixa
A primitiva comportamental de iteração com duração fixa é a repetição sucessiva de um bloco de
atores. O número de repetições é determinado no momento da criação da especificação. Essa primitiva é
baseada em comandos de repetição (laços).
3.4.1 Exemplo de Sistema
Escolhemos como exemplo de sistema a multiplicação de duas matrizes de ordem N. A figura
3.19 mostra o trecho de código utilizado.
1− for(int i = 0;i  N;i++) {
2− for(int j = 0;j  N;j++) {
3− for(int k = 0;k  N;k++) {
4− res[i][j] += data[i][k] * cT[k][j];
5− }
6− }
7− }
Figura 3.19 − Multiplicação de duas matrizes de ordem N.
O exemplo utiliza três iterações encadeadas (linha 1 até 3), cada uma com um contador (i, j, k)
diferente. Desta forma, o corpo da iteração (linha 4) é repetido N3
vezes. Utilizamos no estudo da
primitiva matrizes de ordem 8.
3.4.2 Especificação Executável
A primeira especificação que desenvolvemos pôde ser utilizada com todos os seis modelos
computacionais, pois a exemplo da especificação da figura 3.2, utilizamos somente atores que consomem
e produzem um token por cada porto de entrada/saída. A figura 3.20 apresenta a topologia desta
especificação.
Figura 3.20 − A primeira especificação para a primitiva de iteração fixa.
A função de cada ator da especificação é:
 Ramp: gera uma seqüência crescente de valores inteiros, com incremento igual à 1;
 Modulo: calcula o módulo do valor de entrada por um valor especificado como parâmetro;
 Divide1 e Divide2: calcula o módulo da divisão do valor de entrada por um valor
especificado como parâmetro. O valor do módulo é o mesmo do divisor utilizado;
 Ct e Data: são atores que armazenam uma matriz (especificada como um parâmetro). Dado
um valor de linha (R) e coluna (C), produz o valor da posição correspondente na matriz;
 Mult: multiplica o valor de dois tokens;
 AccumAdd: calcula a soma de N (parâmetro) números. A cada valor de entrada, gera o
resultado parcial;
 Res: utilizado para armazenar um novo valor em uma matriz.
i
j
kRamp
Divide1
Modulo
Divide2
AccumAdd ResMult
R
R
C
C
CT
Data
45
A geração dos contadores da iteração da figura 3.16 é implementada pelos atores Ramp, Divide1,
Divide2 e Modulo. Os outros atores representam o corpo da iteração. Cada iteração é iniciada com o
disparo do ator Ramp. Como o exemplo utiliza três laços encadeados, o valor do contador j deve
permanecer o mesmo enquanto o contador k vai de 0 até N. O mesmo deve ocorrer com o contador i em
relação ao contador j. Essa é a razão da utilização dos atores Divide1 e Divide2. O primeiro é
configurado com valor N = 8 e o segundo com valor N = 64.
Outra possível implementação para a geração dos contadores da figura 3.20 é mostrada na figura
3.21.
Figura 3.21 − Implementação alternativa para a geração dos índices da iteração.
Neste caso, cada índice é gerado utilizando−se dois atores: um ator de atraso (D) e o ator
IndexGen. O ator IndexGen é responsável por calcular os novos valores dos contadores com base nos
valores anteriores. O valor inicial para cada ator de atraso é 0. A função desse ator é armazenar o valor
anterior do contador, ou seja, a topologia da figura 3.21 está implementando o conceito de estado de
maneira explícita.
O principal problema com a especificação da figura 3.20 é a produção desnecessária do valor dos
contadores (por exemplo, o contador i é desnecessariamente repetido por 63 vezes), ou seja, atores são
desnecessariamente ativados. Utilizamos características específicas dos diferentes modelos
computacionais para aprimorar a especificação. Descreveremos os resultados obtidos.
3.4.2.1 SDF
O modelo computacional SDF permite uma iteração implícita de atores através da especificação
de valores de consumo e produção de tokens variados. Considere o exemplo: um ator A produz dados
para um ator B. Caso seja especificado que o ator A produz um token a cada ativação e que o ator B
consome dez, então o ator A será ativado dez vezes mais que o ator B.
A figura 3.22 mostra a especificação executável utilizando o modelo SDF.
Figura 3.22 − A iteração com duração fixa no MoC SDF.
Com relação à figura 3.20, apenas a geração dos contadores foi modificada. Ela é feita através de
três atores Sequencer. Esse tipo de ator gera uma seqüência crescente de inteiros até um valor
especificado (parâmetro) e então reinicia a seqüência a partir de 0. Os números próximos a cada conexão
são as taxas de amostragem dos respectivos portos. Por exemplo, foi especificado que para cada token
IndexGen
i
k
j
D
D
D
1
Sequencer2
Sequencer1
Sequencer3
AccumAdd ResMult
R
R
C
C
CT
Data
1
1
8
1
1
64
1
1
81 1 8
8
1
8
64
46
que o ator Data consome proveniente do ator Sequencer1, 64 tokens devem ser consumidos provenientes
do ator Sequencer1. Nessa especificação, não há produção redundante de tokens.
3.4.2.2 DDF
Implementamos outra solução permitindo que alguns valores de consumo/produção não fossem
especificados, como no caso dos atores CT e Data. Neles, a utilização da leitura bloqueante através do
comando waitFor() pode ser empregada como forma de garantir o consumo da quantidade correta de
tokens em cada porto. A figura 3.23 apresenta um trecho de código de um desses atores para ilustrar essa
solução.
1− ....
2− public void fire() throws IllegalActionException {
3−
4− if(_state == 0) {
5− if(_rowCount == 1) {
6− _Port0Token = (IntToken) row.get(0);
7− waitFor(col, 1);
8− }
9− else {
10− _Port0Token = (IntToken) col.get(0);
11− waitFor(row, 1);
12− }
14− _state = 1;
15− }
16− else {
17− IntToken it;
18− if(_rowCount == 1) {
19− it = (IntToken) col.get(0);
20− data.broadcast(new IntToken(
21− _data[_Port0Token.intValue()][it.intValue()]));
22−
23− _state++;
24− if(_state == _colCount) {
25− waitFor(row, 1);
26− _state = 0;
27− }
28− else {
29− waitFor(col, 1);
30− }
31− }
32− else {
33− it = (IntToken) row.get(0);
34− data.broadcast(new IntToken(
35− _data[it.intValue()][_Port0Token.intValue()]));
36−
37− _state++;
38− if(_state == _rowCount) {
39− waitFor(col, 1);
40− _state = 0;
41− }
42− else {
43− waitFor(row, 1);
44− }
45− }
46− }
47− }
48− ....
Figura 3.23 − Trecho de código dos atores CT e Data.
As variáveis _rowCount e _colCount armazenam a quantidade de tokens a serem consumidos
nos portos row e col, respectivamente. Uma dessas variáveis deve ter o valor igual à 1, enquanto que a
outra pode apresentar qualquer valor maior ou igual a 1. A variável _state é utilizada para determinar de
qual porto o próximo token deve ser lido.
3.4.2.3 PN
A especificação empregada no MoC DDF também foi utilizada nesse MoC. Como não é um
47
modelo data flow, nenhuma taxa de amostragem é especificada. Desta forma, o código dos atores CT e
Data foram adaptados. A figura 3.24 mostra essa modificação.
1− if(_rowCount == 1) {
2− r = (IntToken) row.get(0);
3− for(int i = 0;i  _colCount;i++) {
4− c = (IntToken) col.get(0);
5− data.broadcast(new IntToken(_data[r.intValue()][c.intValue()]));
6− }
7− }
8− else {
9− c = (IntToken) col.get(0);
10− for(int i = 0;i  _rowCount;i++) {
11− r = (IntToken) row.get(0);
12− data.broadcast(new IntToken(_data[r.intValue()][c.intValue()]));
13− }
14− }
Figura 3.24 − Código dos atores Data e CT no MoC PN.
3.4.2.4 CSP
A especificação empregada no MoC PN também é válida nesse modelo. Nenhuma modificação
foi necessária.
3.4.2.5 DE
A topologia da especificação utilizada nesse modelo foi basicamente a mesma da figura 3.22,
com apenas a inclusão de um ator tipo Clock, como nos exemplos das primitivas anteriores, conectado à
cada ator Sequencer. Entretanto, o código dos atores Sequencer, MatrixHolder, AccumAdd e Res teve
que ser alterado. Isso se deve à semântica de comunicação no MoC DE, diferente dos MoCs anteriores.
Utilizaremos o ator Sequencer como exemplo. A figura 3.25 mostra o código desse ator no MoC PN e a
figura 3.26 no modelo DE.
1− ....
2− public void fire() throws IllegalActionException {
3− output.broadcast(new IntToken(_state));
4− _state = (_state + 1) % _limit;
5− }
6− ....
Figura 3.25 − Trecho do ator Sequencer no MoC PN.
1− ....
2− public void fire() throws IllegalActionException {
3−
4− if(input.hasToken(0)) {
5− input.get(0);
6− _counter++;
7− if(_counter == _limit) {
8− output.broadcast(new IntToken(_state));
9− _state = (_state + 1) % _to;
10− _counter = 0;
11− }
12− }
13− }
14− ....
Figura 3.26 − Trecho do ator Sequencer no MoC DE.
No código da figura 3.25, o comando na linha 3 cria um novo token com o valor atual da
seqüência e envia tal token para os atores conectados. A atualização do valor da seqüência é feita na linha
4. Note a utilização de uma variável de estado interna. Esse código é válido no MoC PN devido à
utilização de filas nas conexões, o que armazena os valores produzidos, e a semântica de escrita
bloqueante. Não é possível utilizar esse trecho no MoC DE, pois a cada execução do ator um novo dado
é gerado.
No código da figura 3.26, o comando na linha 4 verifica se há um token no porto input. Este
48
porto é utilizado para ativar o ator. O valor lido não é relevante (linha 5). Um contador (linha 6) é
utilizado para determinar quando um novo dado deve ser produzido. Esse limite (linha 7) é determinado
pelo usuário. Desta forma, é possível que diferentes atores Sequencer gerem dados a taxas diferentes.
Note que uma outra alternativa para a implementação de um contador seria como na figura 3.21, ao invés
de utilizar uma variável interna.
É interessante notar que o trecho da figura 3.26 é valido nos modelos PN, CSP e DDF, pois
nesses casos é possível que um ator não produza nenhum dado em determinados momentos.
Um aprimoramento que pode ser feito é que ao invés de conectar o ator Clock a todos os atores
Sequencer, fazê−lo apenas com o ator Sequencer1 e modificar tais atores para que quando a seqüência
atingir o valor máximo, gerar um evento em um novo porto de saída. Desta forma, conectando os atores
Sequencer de maneira encadeada, teremos uma especificação válida e que não escalona nenhum ator de
forma desnecessária.
3.4.2.6 SR
A especificação utilizada é igual à do modelo DE, a menos do ator Clock. As modificações feitas
nos atores Sequencer, MatrixHolder, AccumAdd e Res para o modelo DE também foram necessárias.
Esses modificações utilizaram os comandos próprios desse MoC, como o comando makeAbsent() para
tornar um valor de saída ausente em um determinado instante.
3.4.3 Análise da Execução
3.4.3.1 SDF
A única observação feita durante a execução das duas especificações é que a segunda (figura
3.22) utilizou filas de capacidade maior. Por exemplo, a conexão Sequencer1 para o ator Data necessita
de uma fila com 64 posições. Como esse MoC é escalonado de forma estática, isso já era previsível. O
aumento do tamanho das filas está ligado à duração de cada iteração e ao nível de encadeamento de uma
iteração.
3.4.3.2 DDF
Conforme descrito no item 3.3.2.2, utilizamos a leitura bloqueante através do comando
waitFor() para controlar a capacidade máxima necessária das filas de comunicação. A figura 3.27
apresenta a evolução do estado do ator Data e do ator Accum.
Figura 3.27 − Evolução do estado dos atores Data (em azul) e Accum (em verde).
É possível observar que após o ator Data obter o valor do contador i (primeiro pico) , ele obtêm
um valor do contador k e produz um novo valor de saída, para cada ativação. Quando oito valores são
criados, o ator Accum é ativado.
Utilizando essa especificação, a capacidade máxima necessária da fila entre os atores Sequencer1
e Data caiu de 64 para 1. Em contrapartida, o ator foi escalonado por um maior número de vezes, o que
49
pode acarretar um overhead adicional. Entretanto, é possível explorar outras configurações dos
parâmetros capacidade máxima das filas/número de ativamentos do ator. Isso pode ser feito
modificando−se o segundo parâmetro do comando waitFor(). A figura 3.28 apresenta um exemplo onde
o valor do segundo parâmetro foi aumentado de 1 para 4 (linhas 29 e 43 do código da figura 3.23).
Figura 3.28 − Evolução do estado dos atores Data e Accum com a alteração do parâmetro do método waitFor().
Observando−se a figura 3.28, o ator Data foi escalonado três vezes, ao invés de nove.
Entretanto, a capacidade máxima da fila entre os atores Sequencer1 e Data subiu de 1 para 4.
3.4.3.3 PN
Variamos o valor do tamanho inicial de cada fila de comunicação. Aumentado esse valor, a
porcentagem de instantes em que cada ator esteve em cada um dos três possíveis estados permaneceu
muito similar. Notamos que a partir de um determinado instante, vários atores estiveram bloqueados
devido à falta de espaço na fila de comunicação. O aumento do valor do tamanho inicial de cada fila
apenas retardou o instante em que esses atores começaram a ficar bloqueados. A figura 3.29 ilustra a
evolução do estado do ator Data quando o tamanho inicial das filas é 1, e a figura 3.30 quando o
tamanho inicial é 8.
Figura 3.29 − Evolução do estado do ator Data quando o tamanho inicial das filas é 1.
Figura 3.30 − Evolução do estado do ator Data quando o tamanho inicial das filas é 8.
3.4.3.4 CSP
50
Notamos novamente que a porcentagem de instantes em que os atores estiveram bloqueados
aumentou, com relação ao MoC PN. Nenhum deadlock foi detectado.
3.4.3.5 DE
O escalonamento obtido foi trivial (ordem topológica), uma vez que as especificações utilizadas
são sem atraso. Essa situação também ocorreu nas especificações para as outras duas primitivas.
Observamos uma situação que possibilita otimizar o escalonador. Quando existirem vários
eventos com um mesmo timestamp, a ordem dos eventos é decidida com base no valor de prioridade,
calculado através da ordenação topológica do grafo da especificação. Isto significa que nesta situação, a
ordem de ativação é conhecida antes da execução. Desenvolvemos então o seguinte escalonador :
1. caso a fila de eventos esteja vazia ou o tempo final foi superado, vá para o passo 9;
2. remova o próximo evento da fila;
3. atualize o valor de tempo atual para o do evento obtido no passo 2;
4. determine para qual ator o evento é destinado e envie o respectivo token;
5. remova da fila todos os eventos que tenha o timestamp igual ao tempo atual, enviando os
respectivos tokens aos atores correspondentes;
6. respeitando uma ordem topológica, ative cada ator que tenha algum token em um porto de
entrada até que ele tenha consumido dos os tokens. Envie os eventos produzidos com
timestamp maior que o tempo atual para a fila de eventos. Envie os eventos produzidos sem
atraso para os respectivos atores;
7. caso existam atores com tokens nos portos de entrada, vá para o passo 6;
8. vá para o passo 1.
9. fim.
O escalonador modificado ativa os atores em um dado timestamp de forma até que nenhum ator
esteja produzindo mais eventos. Os eventos para um tempo futuro são introduzidos na fila de eventos,
enquanto que os eventos sem atraso são enviados diretamente para os portos dos atores. A validade desse
escalonador é garantida pelo respeito à ordenação topológica. A principal vantagem dessa modificação é
reduzir o número de eventos da fila central do escalonador. A tabela 3.1 apresenta alguns dados
comparativos.
Tabela 3.1 − Comparação dos escalonadores do MoC DE.
Atores
com
atraso
Atores
sem
Atraso
Eventos na Fila
Escalonador
Original
Eventos na fila
Escalonador
Modificado
Tempo de Execução
(ms)
Escalonador Original
Tempo de Execução
(ms)
Escalonador Modificado
6 28 27131 8662 5272 4666
1 14 20021 1001 4468 3150
3 3 9884 1945 5317 4620
A tabela 3.1 mostra uma redução considerável do tráfego de eventos na fila do escalonador.
Consequentemente, há uma redução no tempo de execução, pois algum overhead relacionado à
manipulação da fila é removido.
É interessante notar que o escalonador modificado é bastante similar ao escalonador do MoC SR.
Isso ocorre pois podemos relacionar um instante do MoC SR com um valor de timestamp do MoC DE.
3.4.36 SR
Nenhuma característica adicional foi observada. O escalonamento é trivial.
3.5 Outras Primitivas Comportamentais
51
Além das três primitivas descritas e analisadas nos itens anteriores, enumeramos outras
primitivas comportamentais relevantes à captura de sistemas. Iremos descrever o comportamento de cada
primitiva e apresentar um toy benchmark.
3.5.1 Sincronismo
A primitiva comportamental de sincronismo corresponde à especificação em que determinado
bloco de atores deve ser ativado antes ou depois de outro bloco, ou seja, impor uma restrição na ordem
da ativação de atores em determinadas situações. Essa primitiva é amplamente encontrada em
especificações de sistemas concorrentes. Em linguagens de programação seqüenciais, ou em modelos
computacionais não concorrentes, essa primitiva é utilizada de forma implícita, pois ao criar a
especificação, a ordem da execução entre os comandos (atores) é fixada. A figura 3.31 apresenta uma
implementação do algoritmo Discrete Cosine Transform (DCT) que pode ser utilizado para estudar a
primitiva. A entrada do sistema é uma matriz (data, linha 6) e a saída outra matriz (outv, linha 16).
1− for(i = 0; i  _N; i++) {
2− for(j = 0; j  _N; j++) {
3− temp[i][j] = 0.0;
4− for(k = 0; k  _N; k++) {
5− temp[i][j] += (((int)(data[i][k]) − 128) * _cT[k][j]);
6− }
7− }
8− }
9−
10− for(i = 0; i  _N; i++) {
11− for(j = 0; j  _N; j++) {
12− temp1 = 0.0;
13− for(k = 0; k  _N; k++) {
14− temp1 += (_c[i][k] * temp[k][j]);
15− }
16− outv[i][j] = (int) Math.round(temp1);
17− }
18− }
Figura 3.31 − Discrete Cosine Transform: exemplo para a primitiva de sincronismo.
O algoritmo DCT é composto por duas multiplicações de matrizes de ordem N (linhas 1 à 8 e 10
à 18), ou seja, por dois blocos de atores que implementam a primitiva do item 3.3. A primitiva de
sincronismo se manifesta no exemplo da figura 3.31, pois a segunda multiplicação utiliza o resultado da
primeira, ou seja, existe uma dependência de dados entre a primeira e a segunda multiplicação.
A captura do exemplo da figura 3.31 implica em sincronizar as duas multiplicações. Nesse
exemplo, isso pode ser feito de maneira implícita gerando os contadores da segunda multiplicação
atrelados aos contadores da primeira multiplicação, ou de forma explícita através de novos atores para
controlar as ativações/geração de eventos.
3.5.2 Compartilhamento de Recursos
Um dos fatores que limita a implementação de um sistema é a disponibilidade de um recurso,
como por exemplo área no caso de um circuito integrado ou a quantidade de memória em um sistema
microprocessado. Desta forma, o compartilhamento de recursos é amplamente utilizado e deve ser
capturado por uma especificação executável.
Um exemplo para capturar essa primitiva é o problema dos Dinning Philosophers13
[HOA78].
Neste exemplo, o recurso a ser compartilhado são os garfos disponíveis. É um exemplo clássico de
programação concorrente e foi criado inicialmente para o estudo de propriedades como liveness e
fairness. Além disso, esse exemplo foi utilizado para o estudo de primitivas de sincronismo. Podemos
considerar essa primitiva como sendo uma outra forma de sincronismo (neste caso, não temos
13 Um fator para essa escolha é que, além da relevância e adequação do problema, uma especificação utilizando o MoC CSP que implementa
uma solução está disponível no ambiente Ptolemy II.
52
dependências entre os processos disputando os recursos).
3.5.3 Concorrência
A concorrência é uma característica básica de qualquer sistema embutido. Todas as
especificações que foram criadas nesse trabalho utilizam concorrência de forma implícita, uma vez que
todos os MoCs empregados são concorrentes. Entretanto, é bastante importante testar a captura de um
algoritmo concorrente, ou seja, utilizar essa primitiva de maneira explícita. Podemos mencionar o
cálculo da integral de uma função f(x) no intervalo [a,b] utilizando a regra do trapézio14
, dada pela
fórmula:
n
Σ (f(xi) + f(xi − 1)) * h/2
i = 1
Nessa fórmula, o intervalo [a,b] é dividido em n intervalos de tamanho h. A concorrência está no
fato que o cálculo do valor nos n intervalos pode ser feita de forma independente, propiciando um
paralelismo trivial.
3.5.4 Preempção
A primitiva comportamental de preempção é a interrupção de um cálculo ou tarefa para o início
de outra tarefa, quando uma determinada condição for verdadeira. Esse tipo de primitiva é fundamental
para a captura de sistemas a serem implementados em hardware [GUP97]. Um exemplo simples desse
tipo de primitiva é a reinicialização do sistema. A figura 3.32 apresenta um trecho de código15
na
linguagem Esterel para exemplificar essa situação.
1− module ABRO:
2− input A, B, R;
3− output O;
4− loop
5− [ await A || await B ];
6− emit O;
7− each R
8− end module
Figura 3.32 − Um exemplo para a primitiva de preempção.
A linha 1 declara um novo módulo do sistema (ator), e os portos de entrada e saída são
declarados nas linhas 2 e 3. O corpo do sistema é constituído por um laço infinito (linhas 4 a 7) que
espera em paralelo por dois eventos (linha 5), um no porto A e outro no porto B, para em seguida gerar
um novo evento no porto O (linha 6). Em seguida, o sistema espera por um sinal no porto R. Caso,
durante a espera dos eventos nos portos A e B, ocorra um evento no porto R, o laço é reinicializado. A
figura 3.33 apresenta uma máquina de estados de Mealy que descreve o comportamento do código da
figura 3.32.
14 Esse exemplo foi retirado de [ARV82].
15 Esse exemplo foi extraído de [BER98].
53
Figura 3.33 − Máquina de Mealy para o código da figura 3.32.
3.5.5 Recursão
A recursão é a especificação de uma função com base nela mesma. Embora seja sempre possível
transformar um processo recursivo em uma iteração, essa primitiva é relevante pois torna a especificação
de uma função mais concisa e simples. Dentre inúmeros exemplos, temos o cálculo da sequência de
Fibonacci como um possível toy benchamark. Outro possível exemplo, este mais significativo, é o
algoritmo da transformada discreta de Fourier, que pode ser descrito de forma recursiva [LEE95].
3.6 Discussão
O principal objetivo desse trabalho é avaliar a eficiência de diferentes modelos computacionais
para representar e validar um sistema digital. Para alcançar tal objetivo, fundamentamos nossa
metodologia na definição de primitivas comportamentais e no estudo da eficiência dos diferentes MoCs
para representá−las.
A partir das características semânticas de cada modelo computacional e do estudo das três
primitivas comportamentais descritas nas seções 3.2, 3.3 e 3.4, podemos obter as seguintes conclusões:
SDF:
Esse MoC foi desenvolvido visando a captura eficiente de algoritmos para processamento digital
de sinais. Esses algoritmos são caracterizados pela transformação de um fluxo (stream) de dados através
de várias operações matemáticas. Isso justifica a escolha da semântica data flow. As três primeiras
primitivas que analisamos comprovaram essa observação.
No caso da primeira e da terceira primitivas, uma descrição eficiente pôde ser efetuada, pois
ambas as primitivas envolvem a transformação de uma seqüência de dados. A captura da primeira
primitiva foi extremamente simples, pois se trata de um grafo homogêneo. A terceira primitiva demanda
o uso de diferentes taxas de amostragem, todos elas constantes. Entretanto, a terceira primitiva expôs
uma situação relevante: como as taxas de amostragem devem ser valores inteiros, é possível que uma
especificação apresente taxas com valores altos, como no caso da figura 3.22. Isso implica diretamente
no tamanho necessário para cada fila de comunicação, isto é, na quantidade necessária de memória. A
alternativa seria utilizar uma especificação que produza valores repetidos nos portos com uma taxa de
consumo menor, como foi feito na especificação da figura 3.20.
A fraqueza do modelo SDF foi demostrada na captura da segunda primitiva, que envolvia o
ativamento de um ator condicionado ao resultado de outro ator. Neste caso, não foi possível criar uma
especificação satisfatória. Utilizamos o recurso de função high−order para obter uma especificação
válida. Isso já era previsível, pois o MoC SDF necessita que um ator seja sempre ativado pelo menos
uma vez a cada iteração.
A partir desses resultados, podemos concluir que é favorável utilizar o MoC SDF quando:
R
R
R
AB/O
A
B
B
A
54
  desejamos capturar um sistema transformacional, em que as taxas de amostragem são
conhecidas a priori e constantes;
  é imprescindível que características da execução sejam conhecidas a priori
(escalonamento estático).
É importante notar que a utilização de composição de atores pode solucionar ou gerar problemas
para a captura de uma especificação no MoC SDF. Isto ocorre porque esse MoC não preserva suas
propriedades através da composição. A segunda primitiva pode ser utilizada como exemplo: a
composição dos atores Cte1, Cte2, Coef, e Mux em um único ator permite a criação de uma especificação
válida.
DDF:
O modelo DDF procura eliminar as limitações presentes no MoC SDF, permitindo variação das
taxas de amostragem. A primeira e a terceira primitivas foram capturadas com sucesso, com as mesmas
especificações utilizadas para o MoC SDF. Neste caso, é mais vantajoso utilizar o MoC SDF, devido ao
escalonamento estático.
A captura da segunda primitiva exigiu a utilização das características específicas do modelo.
Duas especificações válidas foram criadas, mais eficientes com relação ao MoC SDF. Entretanto,
pudemos demonstrar que a característica de leitura bloqueante, presente em modelos data flow,
atrapalhou a obtenção de uma especificação totalmente satisfatória.
Desta forma, podemos concluir que o modelo DDF deve ser usado principalmente para capturar
sistemas transformacionais que não possam ser descritos pelo modelo SDF.
PN:
O modelo PN é bastante similar ao modelo DDF. A principal diferença está no fato do modelo
DDF utilizar ativamentos atômicos e o modelo PN processos concorrentes. A vantagem da solução do
modelo DDF é que o overhead devido ao escalonamento de processos é eliminado. Em contrapartida, é
mais simples descrever o código fonte de um ator no MoC PN, pois não é necessário manter informações
referentes ao estado do ator (vide 3.4.2.2 e 3.4.2.3).
Podemos afirmar que esse MoC deve ser empregado nas mesmas situações que o MoC DDF.
Determinar qual é mais vantajoso irá depender da implementação da especificação.
CSP:
Para as três primitivas que estudamos, o modelo CSP utilizou as mesmas especificações que o
modelo PN. Na captura da segunda primitiva, foi feita uma tentativa de utilizar o rendezvous não
determinístico, mas a especificação resultante não era válida.
Não foi possível chegar à conclusões específicas para esse modelo. Devido à comunicação por
rendezvous, esse modelo apresenta as mesmas limitações que os modelos data flow. Um ponto negativo
do modelo CSP são as situações de deadlock. Embora seja possível detectar tais situações [FDR], essa
tarefa demanda algum esforço, o que não ocorre com outros modelos.
SR:
O modelo SR foi bastante eficiente na captura das três primitivas, inclusive na primitiva de
desvio condicional. Isso se deve à flexibilidade do modelo em permitir a ausência/indeterminação de
eventos em alguns sinais. O escalonamento estático é outro ponto favorável desse modelo.
Podemos enumerar três pontos desfavoráveis desse modelo. Primeiramente, embora seja possível
[EDW97], as conexões entre atores não apresentam armazenamento de dados. Desta forma, sistemas
55
transformacionais com taxas de amostragem não unitárias não podem ser capturados eficientemente por
esse modelo. O segundo ponto desfavorável desse modelo pode ser observado nas figuras 3.25 e 3.2616
: a
não utilização de leitura bloqueante torna o código fonte de um ator um pouco mais complexo. A
hipótese de sincronismo também pode ser um ponto problemático do modelo, caso a implementação do
sistema não seja capaz de satisfaze−la.
A menos das observações acima, o MoC SR pode ser empregado na captura de diversos
sistemas. Sistemas reativos, caracterizados pela operação baseada na iteração com o ambiente através de
eventos, são forte candidatos, uma vez que os modelos data flow e CSP são menos eficientes (vide
primitiva comportamental de desvio condicional).
DE:
Assim como o modelo SR, todas as primitivas foram capturadas eficientemente no modelo DE.
Esse modelo também permite a ausência/indeterminação de eventos em alguns sinais. Ao contrário do
MoC SR, sistemas transformacionais podem ser capturados satisfatoriamente por esse modelo, pois
quando múltiplos eventos com um mesmo timestamp são enviados para um porto, uma fila é utilizada.
O modelo DE é o mais flexível dos seis modelos que estudamos. É possível utilizar esse modelo
para simular a semântica dos outros cinco modelos. Desta forma, para as três primitivas que estudamos,
esse modelo se mostrou o mais adequado.
16 O código da figura 3.26 está no MoC DE mas é basicamente o mesmo para o MoC SR.
56
Capítulo 4
Estudo de Caso
4.1 Introdução
Neste capítulo apresentaremos o desenvolvimento de especificações executáveis para capturar o
comportamento de um subconjunto de um modem ADSL [ADSL]. Ao contrário dos exemplos do
capítulo 3, o sistema em questão representa uma aplicação real e o desenvolvimento foi feito a partir da
especificação em língua inglesa para a tecnologia. Inicialmente iremos apresentar um resumo dessa
especificação, mencionando apenas a funcionalidade que foi capturada. Em seguida, a criação das
especificações executáveis no ambiente Ptolemy será detalhada. Concluiremos esse capítulo com
comentários à respeito do estudo de caso.
4.2 O Modem ADSL
4.2.1 Introdução
Asymmetric Digital Subscriber Line (ADSL) é uma tecnologia de modem baseada no uso de
modulação Discrete Multitone (DMT), utilizada para atingir altas taxas de transmissão de dados em
linhas telefônicas convencionais. O princípio básico da modulação DMT é dividir a banda disponível em
um grande número de subcanais (tons). A modulação é capaz de alocar uma certa quantidade de dados de
forma que cada subcanal seja utilizado ao máximo. Caso algum subcanal não apresente uma qualidade de
transmissão razoável, ele não é utilizado. A figura 4.1 apresenta o modelo de referência para sistemas
ADSL, destacando os principais blocos.
Figura 4.1 − Modelo de referência de sistemas ADSL
Os dados provenientes da rede broadband são recebidos pelo modem ATU−C (ADSL
Transceiver Unit − Central Office) e convertidos em sinais analógicos. Os sinais analógicos são
transmitidos conjuntamente com sinais de telefone convencional (POTS), até o modem remoto (ATU−
R). O modem ATU−C também recebe e decodifica dados provenientes do modem remoto. A figura 4.2
mostra o diagrama de blocos funcionais do transmissor do modem ATU−C.
ATU−C ATU−R
SplitterSplitter
Telefone ou
Modem
Voiceband
Rede
Customer
Rede
Narrowband
Rede
Broadband
V−C T−R
U−C2 U−R2
57
Figura 4.2 − Diagrama funcional do transmissor do modem ATU−C.
Até quatro canais seriais simplex (ASx) e até três canais seriais duplex (LSx) são sincronizados à
uma taxa de transmissão de 4 kHz. Os dados provenientes dos canais seriais são multiplexados em dois
caminhos de dados diferentes: fast e interleaved. Um código de redundância cíclica (CRC),
embaralhamento e uma codificação do tipo forward error correction (FEC) devem ser aplicados aos
dados dos dois buffers separadamente. Os dados do buffer interleaved devem então ser submetidos à
uma função de intercalação. Em seguida, dados provenientes dos dois buffers devem passar por uma
função de ordenamento de tons e formar um frame. Esse frame é enviado à um módulo de codificação de
constelações. Após essa codificação, o frame deve ser modulado em um símbolo DMT para finalmente
gerar um sinal analógico pronto para a transmissão.
Devido à adição de bytes redundantes pelo módulo FEC e à função de intercalamento, os pacotes
de dados apresentam estruturas diferentes nos três pontos de referência indicados na figura 4.2. Os três
pontos de referência são:
  A (mux data frame): nesse ponto os dados já foram multiplexados, sincronizados e o CRC foi
adicionado. Mux data frames devem ser gerados à uma taxa média de 4.0 kHz;
  B (FEC output data frame): nesse ponto foi adicionado os bytes referentes à correção de erros;
  C (constellation encoder input data frame): nesse ponto um frame é a entrada para o
codificador de constelações.
O transmissor do modem ATU−R é similar ao do ATU−C, exceto pela ausência dos canais
seriais simplex (ASx). Isso implica que uma quantidade menor de dados podem ser enviados por esse
transmissor, e seu respectivo data path é mais simples. Por exemplo, o bloco IDFT do modem ATU−C
possui 256 entradas, enquanto o do modem ATU−R 64.
4.2.2 Framing
O modem ADSL utiliza a estrutura de superframe apresentada na figura 4.3. Cada superframe é
composto por 68 ADSL frames, codificados e modulados em símbolos DMT. A nível do padrão de bits,
a taxa de transmissão de um símbolo DMT é de 4000 baud (período = 250 µs). Devido ao símbolo de
sincronismo introduzido ao final de cada superframe, a taxa real de dados transmitidos é de 68/69 * 4000
baud. O símbolo de sincronização é utilizado para recuperar o posicionamento dos frames quando
ocorrerem micro−interrupções na transmissão. Do contrário, seria necessário reinicializar os modems.
Controle
Mux/Sync
CRCF
CRCI
Scram 
FEC
Scram 
FEC
Interleaver
Ordenamento
deTons
Codificador
de
Constelações
IDFT
Buffer
Paralelo/Serial
DAC e
geração do
sinal
AS0
AS1
AS2
AS3
LS0
LS1
LS2
V−C
U−C2
A
mux data frame
B
FEC data frame
C
CE input frame
Zi
i = 0 to 255
xn
n = 0 to 511
58
Figure 4.3 − A estrutura de superframe e frame do modem ADSL.
O byte denominado de fast do frame para os dados provenientes do buffer fast contém o
resultado do cálculo do CRC ou informações de controle. O frame possui um byte similar para os dados
provenientes do interleaved buffer, denominado de synchronization bytes Cada canal serial ASx ou LSx
é associado à um dos dois buffers de dados, durante a inicialização do modem.
A estrutura de superframe e frame não utiliza um padrão de bits para determinar os intervalos
entre cada frame. Essa função é feita por um prefixo cíclico introduzido no símbolo DMT pelo
modulador. Os intervalos entre superframes são determinados pelo símbolo de sincronismo, também
introduzido pelo modulador, e que não carrega dados do usuário.
Figura 4.4 − A estrutura de um frame de dados do buffer fast.
A figura 4.4 apresenta a estrutura do frame com dados extraídos do buffer fast. No ponto de
referência A (mux data frame), o frame construído a partir dos dados do buffer fast deve sempre conter
pelo menos o byte fast. Este pode ser seguido por BF((ASx) byte para cada canal ASx e por BF(LSx) para
cada canal LSx. Caso algum dos valores BF(ASx) seja maior que zero, então um byte AEX e um byte
LEX devem ser incluídos após os dados do último canal LSx. Caso algum dos valores Bf(LSx) seja
maior que zero, então apenas o byte LEX deve ser incluído. O frame no ponto de referência B é obtido
acrescentando−se ao mux data frame RF bytes, utilizados para detecção e correção de erros.
Frame 0 Frame 1 Frame 2 Frame 34 Frame 35 Frame 66 Frame 67 Sync frame
Byte fast Bytes de dados FEC
Buffer fast Buffer interleaved
Buffer de frames de dadosr (68/68 x 250 µsec )
Bytes de dados
Superframe (17 µsec)
1 byte
KF
bytes RF
bytes
NF
bytes NI
bytes
Byte
fast AS0 AS1 AS2 AS3 LS0 LS1 LS2 AEX LEX FEC bytes
NF
bytes
KF
bytes
1 byte
BF
(AS0)
bytes
BF
(AS1)
bytes
BF
(AS2)
bytes
BF
(AS3)
bytes
BF
(LS0)
bytes
BF
(LS1)
bytes
BF
(LS2)
bytes
AF
bytes
LF
bytes
RF
bytes
59
Figura 4.5 − A estrutura de um frame de dados do buffer interleaved.
A figura 4.5 ilustra a estrutura de um frame construído a partir de dados do buffer interleaved.
No ponto de referência A, o frame da figura 4.5 deve conter pelo menos o byte de sincronização. O
restante desse frame é construído de maneira similar ao caso do buffer fast, substituindo BF por BI. O
tamanho de um mux data frame é de KI bytes.
Para os dados do buffer interleaved, o codificador FEC deve obter S mux data frames e adicionar
RI FEC bytes de redundância para produzir uma palavra código. Esta palavra possui comprimento igual à
N − FECI = S x KI + RI bytes. Os frames resultantes devem conter NI = N −FECI/S bytes, onde NI é um
número inteiro. Quando S  1, para S frames em uma palavra código FEC, o frame resultante (ponto de
referência B) deve conter mais que um mux data frame em todos os casos a menos do último frame. Esse
frame deve conter os RI bytes de redundância, além de uma fração de mux data frame
4.2.3 Código de Redundância Cíclica
O principio para o código de redundância cíclica (CRC) é interpretar os dados a serem
transmitidos como uma palavra binária M. Essa palavra é então dividida por uma chave k (polinômio
gerador), conhecido pelo transmissor e receptor. O resto da divisão constitui a ¨palavra de verificação¨
para o conjunto de dados. O transmissor deve enviar os dados juntamente com a palavra de verificação
para que o receptor possa refazer a divisão e comparar com a palavra de verificação recebida.
Para um modem ADSL, o código de redundância cíclica é aplicado para os dados do buffer fast e
interleaved, independentemente. A palavras de verificação obtidas para cada superframe devem ser
enviadas no primeiro frame do próximo superframe. O bits cobertos pela palavra de verificação incluem:
  buffer fast:
  frame 0 : bytes ASx (x = 0, 1, 2, 3), bytes LSx (x = 0, 1, 2) e qualquer byte AEX e LEX;
  todos os outros frames: byte fast, bytes ASx (x = 0, 1, 2, 3), bytes LSx (x = 0, 1, 2) e
qualquer byte AEX e LEX.
  buffer interleaved
  frame 0: bytes ASx (x = 0, 1, 2, 3), bytes LSx(x = 0, 1, 2) e qualquer byte AEX e LEX;
  todos os outros frames: byte sync, bytes ASx (x = 0, 1, 2,3), bytes LSx (x = 0, 1, 2) e
qualquer byte AEX e LEX.
O polinômio gerador utilizado para modems ADSL é: : x8
+ x4
+ x3
+ x2
+ 1.
Byte
Sync AS0 AS1 AS2 AS3 LS0 LS1 LS2 AEX LEX
KI
bytes
1 byte
BI
(AS0)
bytes
BI
(AS1)
bytes
BI
(AS2)
bytes
BI
(AS3)
bytes
BI
(LS0)
bytes
BI
(LS1)
bytes
BI
(LS2)
bytes
AI
bytes
LI
bytes
Mux data frame 0 Mux data frame 1 Mux data frame
S −1
FEC
bytes
FEC data frame 0 FEC data frame 1 FEC data frame S − 1
KI
bytes KI
bytes KI
bytes RI
bytes
NI
bytes NI
bytes NI
bytes
60
4.2.4 Embaralhamento
Os dados presentes nos buffers fast e interleaved devem ser embaralhados bit a bit
separadamente utilizando a seguinte fórmula:
dn’ = dn ⊕ dn−18’⊕ dn−23’
,onde dn é a n−éssima saída do buffer fast ou interleaved e dn’ é a n−éssima saída do
correspondente embaralhador. O embaralhamento é feito no nível de bits, independentemente do símbolo
de sincronismo e da estrutura dos frames.
4.2.5 Correção de Erros
A correção de erros do tipo Forward Error Correction (FEC) é utilizada para assegurar o
desempenho da transmissão. A correção é baseada no algoritmo de codificação Reed−Solomon. O tipo
de código empregado por esse algoritmo é denominado de código de bloco cíclico, pois utiliza bits
redundantes adicionados ao final dos dados.
O algoritmo RS particiona os dados em símbolos contendo m bits. Cada símbolo é processado
como sendo uma unidade pelo codificador e pelo decodificador. O código RS pode ser descrito pelo par
(n, k), onde k é o tamanho da palavra de dados não codificada e n é o tamanho da palavra de dados com
os bits redundantes. Os (n − k) símbolos redundantes são chamados de símbolos de verificação de
paridade. O código RS deve satisfazer: n ≤ 2m
− 1 e n − k ≥ 2t, onde t é a quantidade possível de
símbolos corrigíveis.
Para o modem ADSL, m é fixo em 8 bits. O valor de n e k irá depender do número de bytes
transmitidos por cada canal serial ASx e LSx. O número de bytes de paridade por cada palavra é limitado
entre 0 e 16.
4.2.6 Interleaver
A maioria dos algoritmos utilizados para correção de erros são otimizados para situações onde os
erros aparecem randomicamente na palavra de dados. Interleaving é uma técnica utilizada para rearranjar
os bits da palavra de dados tal que os erros apareçam em posições randômicas e distribuídos por várias
palavras de dados, ao invés de poucas palavras.
Para o modem ADSL, os frames FEC devem ser sofrer um interleaving convolucional, à uma
dada profundidade. O processo de interleaving irá atrasar cada byte do frame FEC por um tempo
diferente, tal que o codificador de constelações recebe bytes pertencentes a vários frames diferentes. A
regra utilizada para o interleaving é:
Cada um dos N bytes B0, B1, Bn−1 de uma palavra código RS é atrasada por uma quantidade de
tempo que varia linearmente com a posição do byte na palavra. Desta forma, o byte Bi é atrasado por
(D−1) * i bytes, onde D é a profundidade do interleaving.
Para modems ADSL, A profundidade de interleaving deve ser uma potência de dois menor ou
igual a 64.
4.2.7 Ordenamento de Tons
Devido as características do sinal ADSL e do conversor digital/analógico, as maiores
probabilidades de erros de transmissão estão associados aos subcanais com maior número de bits. Dessa
forma, para que o sistema de codificação FEC consiga eficientemente corrigir tais erros, é mais favorável
que os tons com maior número de bits recebam dados provenientes do buffer interleaved.
61
O ordenamento de tons do modem ADSL associa os primeiros BF bytes para os tons com menor
número de bits e os restantes BI bytes são associados aos tons restantes.
4.2.8 Codificação de Constelações
Codificação de constelações é um processo que recebe uma palavra de bits e transforma−a em
um número complexo. Para um dado subcanal ADSL, o codificador de constelações seleciona um par de
número inteiros ímpares (X, Y) a partir de um mapa numérico, baseado no vetor b de bits (vb−1, vb−2, ...,
v1, vb−0).
Para uma palavra b com comprimento par, os valores inteiros X e Y do ponto da constelação é
determinado pela regra: os números inteiros ímpares X e Y são formados tomando a representação
complemento de dois dos vetores vb−1, vb−3, ..., v1, 1 e vb−2, vb−4, ...., v0, 1, respectivamente.
Quando o comprimento de b for igual a 3, a constelação da figura 4.6 é utilizada.
Figura 4.6 − Constelação quando o comprimento de b for igual à 3.
Quando o comprimento for ímpar maior que 3, o ponto da constelação é determinado da seguinte
maneira: os dois bits mais significativos de X e de Y são determinados com base nos 5 bits mais
significativos de b. Seja c = (b+1)/2, então X e Y tomam a representação complemento de dois dos
vetores Xc, Xc−1, vb−4, vb−6, ...., v3, v1, 1 e Yc, Yc−1, vb−5, vb−7, vb−9, ..., v2, v0, 1, respectivamente. A relação
entre Xc, Xc−1, Yc, Yc−1 e vb−1, vb−2, .., vb−5 é dada por uma tabela presente no padrão ADSL.
4.2.9 Modulação
Após o codificador de constelações, os dados são modulados através da transformada discreta
inversa de Fourier (IDFT). Essa operação define uma relação entre os 512 valores reais xk e 256 números
complexos Zi. Como o codificador de constelação gera apenas 255 valores complexos, a fim de obter os
512 valores reais xk, o vetor Z deve ser aumentado em 256 valores, tal que:
Zi = conj(Z512 − i) para i = 257 to 511.
4.2.10 A Inicialização
A inicialização é o processo utilizado para estabelecer uma conexão entre o par de modems
ATU−C e ATU−R. Para que a qualidade da transmissão e a quantidade de dados transmitidos seja
maximizada, o transmissor/receptor ADSL deve determinar certos parâmetros do canal de conexão e
estabelecer as características de processamento de cada modem. A inicialização é composta por quatro
partes: ativação e acknowledgment, treinamento do transmissor/receptor, análise do canal e troca de
Y
X
0
1
2
3
4
5
6
7
62
informações.
A determinação das características do canal de comunicação e o estabelecimento das
características da transmissão exigem que cada transmissor/receptor gere, e responda apropriadamente,
um conjunto de sinais pré−definidos e temporizados. Regras são definidas determinando o início e fim
de cada um desses sinais. Essas regras são descritas através da definição dos estados de inicialização do
transmissor/receptor, e através da definição dos sinais que eles irão gerar.
Devido a complexidade do procedimento de inicialização, descreveremos somente a parte
referente a ativação e acknowledgment. A seqüência de estados/sinais para a fase de ativação e
acknowledgment é apresentada na figura 4.7.
Figura 4.7 − Ativação e acknowledgment.
Os sinais para o modem ATU−C são:
  C−QUIET1: após o power−up e um auto teste opcional, o modem ATU−C deve entrar no
estado C−QUIET1. Quando o modem está no estado C−QUIET1, um comando do
controlador central do modem ou a detecção correta do sinal R−ACT−REQ faz com que
ocorra uma transição para o estado C−ACT. Para assegurar a compatibilidade entre
diferentes implementações do modem, o transmissor ATU−C deve permanecer no estado
C−QUIET1 até que o sinal R−ACT−REQ não seja mais detectado por 128 símbolos
consecutivos;
  C−ACT: o modem ATU−C deve transmitir o sinal C−Active a fim de iniciar uma conexão
com o modem ATU−R. Quatro diferentes sinais C−Active são definidos. Isso ocorre para
distinguir diferentes requisições do sistema referentes à temporização da conexão e o uso ou
não de um tom piloto. Na implementação que desenvolvemos, utilizamos o sinal C−ACT2,
definido por:
0, i ≠ 44, 0 ≤ i ≤ 256
Zi = AC−ACT2 , i = 44
AC−ACT2 deve possuir um nível de potência de transmissão igual à −3.65 dBm para os
primeiros 64 símbolos, e ser 24 dB menor para os restantes 64 símbolos. Esse sinal deve ser
transmitido por 128 símbolos consecutivos e nenhum prefixo cíclico deve ser adicionado;
  C−QUIET2: a função do estado C−QUIET2 é permitir a detecção do sinal R−ACK1 ou R−
ACK2, sem a necessidade de treinar o cancelador de echo do modem ATU−C. A duração do
estado C−QUIET2 é de 128 símbolos. Após o estado C−QUIET2, o modem ATU−C deve
entrar em um de três estados:
  C−REVEILLE: caso o ATU−C tenha detectado o sinal R−ACK, ele deverá entrar
no estado C−REIVELLE. Mesmo que o ATU−C venha a detectar o sinal R−ACK
em menos que 128 símbolos, este deverá esperar pela duração completa do sinal,
mantendo−se no estado C−QUIET2;
  C−ACT: caso o modem ATU−C falhe em detectar o sinal R−ACK, e ele não tenha
C−IDLE/
C−QUIET1/
C−TONE
C−ACT
1−2−3−4 C−QUIET2
R−ACT−REQ/
R−QUIET1
R−ACK
1−2
ATU−R
ATU−C
63
entrado no estado C−ACT por mais que duas vezes, então o ATU−C deve entrar no
estado C−ACT;
  C−QUIET1: caso o modem ATU−C não detecte o sinal R−ACK após ter entrado no
estado C−ACT pela segunda vez, ele deverá retornar ao estado C−QUIET1.
Os sinais para o modem ATU−R são:
  R−ACT−REQ: o sinal R−ACT−REQ é utilizado quando o modem ATU−R deve iniciar o
estabelecimento de uma conexão com o modem ATU−C. O sinal R−ACT−REQ é
transmitido após o power−up e um auto teste opcional. O sinal R−ACT−REQ é definido tal
como o sinal C−ACT2, mas com i = 8. O modem ATU−R deve permanecer no estado R−
ACT−REQ indefinidamente (isto é, transmitir o sinal pela duração de 128 símbolo para em
seguida permanecer quieto por 896 símbolos, e então repetir o processo) até que o sinal C−
ACT2 seja detectado com sucesso;
  R−Acknowledge: esse sinal é transmitido com um acknowledgment da detecção correta do
sinal C−ACT2. Três variações do sinal são definidas. Utilizamos nesse estudo a versão R−
ACK2. Esse sinal é definido tal qual o sinal C−ACT2, mas com i = 12 e com o nível de
potência de transmissão igual à −1.65 dBm para os primeiros 64 símbolos e 20 db menor para
os 64 símbolos restantes.
4.3 As Especificações Executáveis
4.3.1 O Sistema
A figura 4.8 apresenta a topologia do nível hierárquico mais alto da especificação. Neste nível
existem quatro atores: duas instâncias da especificação do modem (ATU−C e ATU−R) e dois blocos
representando o ambiente (rede broadband e customer). As especificações para os modems serão
descritas mais adiante neste texto.
Figura 4.8 − Topologia da especificação do sistema.
Os atores modelando o ambiente são responsáveis por enviar e receber dados para os modems
através dos sinais de comunicação serial (ASx e LSx)17
. A fim de tornar a especificação mais simples,
sem perder a representatividade, implementamos apenas dois sinais (um de cada tipo): AS0 e LS0. Como
a especificação determina que esses sinais estejam sincronizados à uma taxa de 4 kHz, o MoC utilizado
deve ter uma noção de tempo.
Escolhemos o modelo Discrete Event para a captura da especificação do sistema. As razões para
tal escolha são a flexibilidade desse MoC e a presença de uma noção de tempo. Também tentamos
utilizar o modelo Process Networks, uma vez que a implementação do modelo no ambiente Ptolemy
possui uma noção de tempo e pelas características do sistema (a grande quantidade de blocos
transformacionais). Entretanto, encontramos diversas situações em que esse modelo não se mostrou
adequado. A primeira situação problemática ocorreu quando tentamos criar os atores representando o
ambiente: devido à semântica de leitura bloqueante, não é possível criar apenas um ator que faça as duas
operações de envio e recebimento de dados. A semântica de leitura bloqueante também foi um empecilho
para a captura da especificação do modem: como não é possível verificar se um porto contém dados, os
17 Os atores modelando o ambiente são utilizados para simular e validar o comportamento do modem.
Rede
Broadband ATU−C ATU−R Rede
Customer
Tx
TxRx
Rx
AS0 AS0
LS0 LS0
DataTx DataRx
64
sinais DataTx e DataRx devem ser utilizados para indicar qual é o porto que contém dados. Esses sinais
são dispensáveis na especificação com o MoC DE. A figura 4.9 apresenta um trecho do código do ator
BroadBandNet no modelo DE.
1− public class BroadBandNet extends DEActor {
2−
3− public TypedIOPort AS0;
4− public TypedIOPort dataTx;
5− public TypedIOPort LS0;
6−
7− public Parameter AS0_Rate;
8− public Parameter LS0_Rate;
9−
10− public BroadBandNet(TypedCompositeActor container, String name) throws
11− NameDuplicationException, IllegalActionException {
12−
13− ....
14− }
15−
16− public void fire() throws IllegalActionException {
17−
18− if(_nextFire == getCurrentTime()) {
19− if(_nextFireAS0 == 0) {
20− ObjectToken ot =
21− new ObjectToken(new Integer(_data[_counter]));
22− AS0.broadcast(ot);
23− dataTx.broadcast(new IntToken(0));
24−
25− _counter = (_counter + 1) % _data.length;
26− _nextFireAS0 = 1000.0 / _AS0_Rate;
27− }
28− ....
29− _nextFire();
30− }
31−
32− ObjectToken ot;
33− int [] tmp = null;
34− while(LS0.hasToken(1)) {
35− ot = (ObjectToken) LS0.get(1);
36− tmp = (int []) ot.getValue();
37− for(int i = 0;i  tmp.length;i++) {
38− _LS0Data.insertElementAt(new Byte((byte)tmp[i]),_LS0Counter);
39− _LS0Counter++;
40− }
41− }
42− }
43− ....
44−
45− private void _nextFire() throws IllegalActionException {
46−
47− double min = 1000.0;
48− boolean sched = false;
49−
50− if(_nextFireAS0  min) min = _nextFireAS0;
51− if(_nextFireLS0  min) min = _nextFireLS0;
52−
53− if(_nextFireAS0 − min == 0) {
54− fireAt(getCurrentTime() + _nextFireAS0);
55− _nextFire = getCurrentTime() + _nextFireAS0;
56− sched = true;
57− }
58− _nextFireAS0 −= min;
59−
60− if((sched == false)  (_nextFireLS0 − min == 0)) {
61− fireAt(getCurrentTime() + _nextFireLS0);
62− _nextFire = getCurrentTime() + _nextFireLS0;
63− }
64− _nextFireLS0 −= min;
65− }
66− ....
Figura 4.9 − Trecho de código do ator BroadBandNet no MoC DE.
Dois parâmetros (linhas 7 e 8) são utilizados para indicar a taxa de transmissão de cada sinal
serial. Da linha 18 à linha 30 está mostrado o código para a transmissão de dados através do sinal AS0
(igual para o sinal LS0). A variável _nextFireAS0 contém o valor do próximo momento em que um novo
65
dado deve ser enviado através do sinal AS0. Essa variável é atualizada pelo método _nextFire() (linha 45
à 65). Esse método é responsável por escalonar o ativamento futuro do ator, baseado no valor dos
parâmetros AS0_Rate e LS0_Rate, utilizando o método fireAt() (linhas 54 e 61).
O trecho de código da linha 32 à linha 41 é utilizado para receber um dado proveniente do sinal
LS0. Note que o ator BroadBandNet é ativado por duas condições (e que podem ser simultâneas):
quando um novo token é recebido ou quando o instante de tempo indicado em uma das chamadas do
método fireAt() for alcançado. Não é possível implementar tal comportamento com o modelo PN, bem
como em qualquer outro modelo data flow.
O ator para a rede customer apresenta uma implementação similar à da figura 4.9. A diferença
desse ator é que apenas o sinal LS0 é utilizado para transmitir dados e ambos os sinais são verificados
para determinar a presença de dados.
4.3.2 Framing
Como mencionado no capítulo 2, o ambiente Ptolemy permite a associação de um tipo de dado à
um porto de entrada/saída. Na especificação executável para o modem ADSL, utilizamos amplamente o
tipo ObjectToken por dois motivos: é possível criar uma especificação mais genérica e próxima da
especificação em linguagem natural, e devido ao menor tempo de execução da especificação.
Duas classes auxiliares implementado a estrutura de framming, descritas no item 4.2.2, foram
criadas: uma para o Mux Data Frame e outra para o Fast Data Frame. Não existe uma classe específica
para o Interleaved Data Frame, pois os dados são misturados após o bloco de Interleaver, sendo visto
apenas como um vetor de bytes. O trecho de código da figura 4.10 apresenta a classe MuxDataFrame.
1− public class MuxDataFrame {
2−
3− public MuxDataFrame(int nbAS0, int nbAS1, int nbAS2, int nbAS3,
4− int nbLS0, int nbLS1, int nbLS2) {
5−
6− _fastByte = 0;
7− _AS0 = new int[nbAS0];
8− .....
9− _AEXcond = (nbAS0 != 0) || (nbAS1 != 0) ||
10− (nbAS2 != 0) || (nbAS3 != 0);
11− _AEX = 0;
12−
13− _LEXcond = _AEXcond || (nbLS0 != 0) ||
14− (nbLS1 != 0) || (nbLS2 != 0);
15− _LEX = 0;
16− }
17−
18− public void setAS0Byte(int b, int nb) {
19− _AS0[nb] = b;
20− }
21−
22− public int getAS0Byte(int nb) {
23− return _AS0[nb];
24− }
25−
26− public int [] getAS0() {
27− return _AS0;
28− }
29− ....
30− protected int _fastByte;
31− protected int _AS0[];
32− ....
33− protected int _AEX;
34− protected int _LEX;
35− protected boolean _AEXcond;
36− protected boolean _LEXcond;
37− }
Figura 4.10 − Trecho de código da classe MuxDataFrame.
Da linha 30 à linha 36 estão declaradas as variáveis representando os campos de um mux data
frame. Essas variáveis são inicializadas no construtor (linha 3 à 16), que possui parâmetros indicando a
quantidade de bytes por cada canal serial ASx e LSx. As condições para a utilização dos bytes AEX e
66
LEX são calculadas nas linhas 9, 10, 13 e 14. A classe MuxDataFrame possui uma série de métodos
(linha 18 à linha 27 para o caso do sinal AS0) para acessar e modificar as variáveis internas.
A classe auxiliar para o Fast Data Frame está apresentada na figura 4.11. Trata−se de uma
classes derivada da classe MuxDataFrame (linha 1), adicionando um campo (linha 18) para armazenar o
valor da paridade do frame, além dos métodos utilizados para manipular o novo campo (linhas 10 à 16).
1− public class FastFECDataFrame extends MuxDataFrame {
2−
3− public FastFECDataFrame(int nbAS0, int nbAS1, int nbAS2, int nbAS3,
4− int nbLS0, int nbLS1, int nbLS2, int npar) {
5−
6− super(nbAS0, nbAS1, nbAS2, nbAS3, nbLS0, nbLS1, nbLS2);
7− _parity = new int[npar];
8− }
9− ....
10− public void setParityByte(int b, int nb) {
11− _parity[nb] = b;
12− }
13−
14− public int getParityByte(int nb) {
15− return _parity[nb];
16− }
17− ....
18− private int _parity[];
Figura 4.11 − Trecho de código da classe FastFECDataFrame.
4.3.3 A especificação dos Modems ATU−R e ATU−C
A especificação do sistema da figura 4.8 possui duas instâncias da especificação executável do
modem ADSL: uma para o ATU−C e outra para o ATU−R. Ambos os modems são similares: a
diferença está no número de sinais seriais e consequentemente, a configuração do data path. Durante o
restante desse capítulo, todos os exemplos serão dados com base no modem ATU−C.
A figura 4.12 apresenta a topologia para o modem ATU−C utilizando o MoC DE. Ela é
composta por atores implementando o data path (MuxSyncTx/Rx, CRCTx/Rx, ScramblerTx/Rx,
FECTx/Rx, InterleaverTx/Rx, Map, Demap, IDFT, DFT), um ator implementando a máquina de estados
para a inicialização do modem, e um ator gerador de eventos. Descreveremos a seguir a implementação
de cada um desses atores.
Figura 4.12 − A topologia da especificação para o modem ATU−C. As setas em azul indicam a transmissão de
dados e as em vermelho sinais de controle.
4.3.3.1 MuxSyncTx
AS0
LS0
DataTx
MuxSyncTx CRCTx ScramblerTx FECTx
InterleaverTx
Map IDFT
InitializerTimer
Reset
Start
Start
Stop
Event
Mode
Tx
MuxSyncRx CRCRx ScramblerRx FECRx
InterleaverRx
DeMap DFT
LS0 Rx
Datapath
67
O ator MuxSyncTx é responsável por obter os bytes provenientes dos sinais seriais e construir
mux data frames para o caminho de dados utilizando o buffer fast e interleaved. Ele corresponde ao
bloco Mux/Sync da figura 4.2, embora o sincronismo não tenha sido implementado na especificação
executável. O porto de entrada Start é utilizado para indicar quando o ator deve iniciar o processamento
dos dados provenientes do ambiente. A figura 4.13 apresenta um trecho do código desse ator.
1− ....
2− public Parameter AS0Bytes;
3− public Parameter LS0Bytes;
4−
5− public Parameter AS0Map;
6− public Parameter LS0Map;
7−
8− public Parameter S;
9−
10− ....
11−
12− public void fire() throws IllegalActionException {
13−
14− if(start.hasToken(0)) {
15− BooleanToken bt = (BooleanToken) start.get(0);
16− _mode = bt.booleanValue();
17− }
18−
19− if(_mode) {
20− sendData();
21− }
22− else {
23− if(dataTx.hasToken(0)) dataTx.get(0);
24− if(AS0.hasToken(0)) AS0.get(0);
25− if(LS0.hasToken(0)) LS0.get(0);
26− }
27− }
28− ....
29− private void _sendData() throws IllegalActionException {
30−
31− if(!dataTx.hasToken(0)) return;
32−
33− IntToken it = (IntToken) dataTx.get(0);
34− switch(it.intValue()) {
35− case 0: {
36− _ot = (ObjectToken) AS0.get(0);
37− Integer b = (Integer) _ot.getValue();
38− ....
39− if(_AS0Wrote == _AS0Bytes) {
40− _AS0Wrote = 0;
41− if(_AS0Map) {
42− _currentAS0Frame = _getNextInterFrame(_currentAS0Frame);
43− else {
44− _currentAS0Frame = _getNextFastFrame(_currentAS0Frame);
45− }
46− }
47− _currentAS0Frame.setAS0Byte(b.intValue(), _AS0Wrote);
48− _AS0Wrote++;
49− } break;
50− ....
51− }
52− _checkFullFrames();
53− }
54−
55− private MuxDataFrame _getNextFastFrame(MuxDataFrame frame) {
56−
57− int i = _fastFrames.firstIndexOf(frame);
58− if(i == _fastFrames.size() − 1) {
59− _mf = _newFastFrame();
60− _fastFrames.insertLast(_mf);
61− }
62− else {
63− _mf = (MuxDataFrame) _fastFrames.at(i + 1);
64− }
65− return _mf;
66− }
67− ....
68− private void _checkFullFrames() throws IllegalActionException {
69−
68
70− while(true) {
71− if(_sentFrame == 0) {
72− _mf = (MuxDataFrame) _fastFrames.at(0);
73− if(_currentAS0Frame != _mf  _currentLS0Frame != _mf) {
74− _fastFrames.removeAt(0);
75− _ot = new ObjectToken(_mf);
76− output.broadcast(_ot);
77− _sentFrame = 1;
78−
79− if(_AS0Map  _LS0Map) {
80− _mf = _newFastFrame();
81− _fastFrames.insertLast(_mf);
82− }
83− }
84− else {
85− return;
86− }
87− }
88− else {
89− ....
90− }
91− else {
92− return;
93− }
94− }
95− if(_sentFrame  _S) _sentFrame = 0;
96− }
97− }
98−
99− private MuxDataFrame _newFastFrame() {
100− _mf = new MuxDataFrame(_AS0Map ? 0 : _AS0Bytes, 0, 0, 0,
101− _LS0Map ? 0 : _LS0Bytes, 0, 0);
102− return _mf;
103− }
104− ....
105− private boolean _mode;
106− private LinkedList _fastFrames;
107− private LinkedList _interFrames;
108− private MuxDataFrame _currentAS0Frame;
109− private int _AS0Wrote;
110− ....
111− private int _sentFrame;
112− private ObjectToken _ot;
113− private MuxDataFrame _mf;
114− ....
Figura 4.13 − Trecho de código do ator MuxSyncTx no MoC DE.
Cinco parâmetros são declarados da linha 2 à linha 8: ?0Bytes indica o número de bytes de cada
sinal serial em um mux data frame; ?S0Map determina para qual buffer de dados o respectivo sinal serial
está associado; S é o número de mux data frame por cada interleaved data frame. Esses parâmetros
também são utilizados por vários outros atores do data path (dos atores MuxSyncTx/Rx até os atores
Map/Demap).
O corpo do método fire() (linha 12 à 27) determina se os dados devem ser processados ou não,
dependendo de eventos no porto Start. Esses eventos são gerados pela máquina de estados de
inicialização. Durante o começo da execução (etapa de inicialização) a máquina de estados instrui o ator
para descartar os dados recebidos. Uma vez que a inicialização tenha sido concluída, um evento é
enviado ao porto Start, indicando o início da transmissão de dados.
O método _sendData() (linha 29 à 53) implementa a funcionalidade do ator. Inicialmente, a
existência de dados nos canais seriais é verificada (linha 31). Caso existam dados, primeiramente eles
são lidos (linhas 36 e 37). Em seguida, é verificado se o campo do frame para o buffer de dados do
respectivo sinal serial já está cheio (linha 39 para o sinal AS0). Caso negativo, os dados são armazenados
no mux data frame disponível (linha 69−70 para o sinal AS0, LS0 não é mostrado). Caso positivo, um
novo mux data frame é criado (linhas 40 à 45). Finalmente, o método determina se existe algum mux
data frame que esteja completo (linha 52), isto é, todos os campos referentes aos sinais seriais já estão
preenchidos. Isso é implementado pelo método _checkFullFrames().
4.3.3.2 MuxSyncRx
69
Esse ator é bastante simples. Sua única função é obter mux data frames provenientes do ator
CRCRx e enviar os dados aos respectivos sinais seriais.
4.3.3.3 CRCTx
Esse ator obtêm mux data frames e aplica o algoritmo de CRC descrito no item 4.2.3. É
necessário determinar os limites de um superframe, tal que o ator seja capaz se armazenar o valor de
CRC calculado no começo do próximo superframe. A figura 4.14 apresenta um trecho do código desse
ator.
1− ....
2− public void fire() throws IllegalActionException {
3−
4− ObjectToken ot = (ObjectToken) input.get(0);
5− MuxDataFrame mf = (MuxDataFrame) ot.getValue();
6−
7− if(_frameCounter == 0) {
8− if(_iteration == 0) {
9− _fastCRC(mf, false);
10− _iteration = 1;
11− }
12− else {
13− _CRC(0, _fastReg);
14− mf.setFastByte(_fastReg);
15− _fastReg = 0;
16− _fastCRC(mf, true);
17− }
18− }
19− else
20− if(_frameCounter == 1) {
21− if(_iteration == 1) {
22− _interCRC(mf, false);
23− _iteration = 2;
24− }
25− else {
26− _CRC(0, _interReg);
27− mf.setFastByte(_interReg);
28− _interReg = 0;
29− _interCRC(mf, true);
30− }
31− }
32− else {
33− if(_frameCounter % (_S + 1) == 0) {
34− _fastCRC(mf, true);
35− }
36− else {
37− _interCRC(mf, true);
38− }
39− }
40−
41− _frameCounter++;
42− if(_frameCounter == ATUC.framesPerSuperframe +
43− ATUC.framesPerSuperframe*_S)
44− _frameCounter = 0;
45−
46− ot = new ObjectToken(mf);
47− output.broadcast(ot);
48− }
49−
50− public void initialize() throws IllegalActionException {
51− ....
52− _iteration = 0;
53− _frameCounter = 0;
54− _interReg = 0;
55− _fastReg = 0;
56− }
57−
58− private void _fastCRC(MuxDataFrame mf, boolean first) {
59− if(first) {
60− _fastReg = _CRC(mf.getFastByte(), _fastReg);
61− }
62−
63− if(!_AS0Map) {
70
64− for(int i = 0;i  _AS0Bytes;i++)
65− _fastReg = _CRC(mf.getAS0Byte(i), _fastReg);
66− }
67− ....
68− if(mf.hasAEX()) {
69− _fastReg = _CRC(mf.getAEXByte(), _fastReg);
70− }
71− .....
72− }
73−
74− private int _CRC(int b, int reg) {
75− int out;
76− for(int i = 0;i  8;i++) {
77− out = reg  0x80;
78− reg = (reg  1) | ((b  i)  0x01);
79− if(out != 0) reg = reg ^ _poly;
80− }
81− return reg;
82− }
83− ....
84− private int _frameCounter;
85− private int _fastReg;
86− private int _interReg;
87− private final static int _poly = 0x1d;
88− private byte _iteration;
89− ....
Figura 4.14 − Trecho de código do ator CRCTx.
O trecho de código das linhas 7 à 39 verifica as diferentes condições para o cálculo do CRC. A
variável _frameCounter (linha 84) é utilizada para identificar o início de um novo superframe. Quando
esta variável for igual à zero, significa que o ator esta processando o primeiro frame para o buffer fast.
Desta forma, o CRC calculado anteriormente deve ser armazenado (linhas 13 à 16). A mesma situação se
aplica ao primeiro frame para o buffer interleaved (linhas 20 à 31). A variável _frameCounter é
atualizada nas linhas 41 e 44. A constante ATUC.framesPerSuperframe indica a quantidade de frames
em um superframe. Embora o padrão ADSL determine o valor 67 para essa constante, utilizamos um
valor menor a fim de aumentar o desempenho da execução.
O método _fastCRC() (linhas 52 à 72) calcula o CRC correspondente aos dados do buffer fast.
Para tal, é aplicado o algoritmo do CRC (linhas 74 à 82) para cada campo do frame. Um método similar
é utilizado para os dados do buffer interleaved.
4.3.3.4 CRCRx
A funcionalidade do ator CRCRx é bastante parecida com a de seu irmão transmissor. A única
diferença é que quando o CRC é calculado, ocorre uma comparação com o valor do CRC recebido. Isso
é feito nas linhas 11 à 16 e 29 à 34 do código da figura 4.15, para os dados buffer fast e interleaved,
respectivamente.
1− ....
2− public void fire() throws IllegalActionException {
3− ....
4− if(_frameCounter == 0) {
5− if(_iteration == 0) {
6− ....
7− }
8− else {
9− _CRC(0, _fastReg);
10− int check = mf.getFastByte();
11− if((byte) check != (byte) _fastReg) {
12− System.out.println(ttt !! ATUC−CRCRx : FAST CRC FAILED !!);
13− }
14− else {
15− System.out.println(ttt ## ATUC−CRCRx : FAST CRC OK ##);
16− }
17− _fastReg = 0;
18− _fastCRC(mf, true);
19− }
20− }
21− else
71
22− if(_frameCounter == 1) {
23− if(_iteration == 1) {
24− ....
25− }
26− else {
27− _CRC(0, _interReg);
28− int check = mf.getFastByte();
29− if((byte) check != (byte) _interReg) {
30− System.out.println(tt!! ATUC−CRCRx:INTER CRC FAILED !!);
31− }
32− else {
33− System.out.println(tt ## ATUC−CRCRx:INTER CRC OK ##);
34− }
35− _interReg = 0;
36− _interCRC(mf, true);
37− }
38− }
39− else {
40− ....
41− }
42− ....
43− }
44− ....
Figura 4.15 − Trecho de código do ator CRCRx.
4.3.3.5 ScramblerTx
Esse ator implementa a função de embaralhamento dos dados a serem transmitidos. A figura
4.16 apresenta um trecho de seu código. Inicialmente um novo mux data frame é obtido (linhas 4 e 5) e é
determinado se os dados são destinados ao buffer fast ou interleaved, dependendo do valor da variável
_frameCounter. Das linhas 9 à 20 esta implementado o processamento dos dados para o buffer fast.
Neste caso, o método _processFast() (linhas 31 à 43) é responsável por embaralhar os dados. Os dados
para o buffer interleaved são transformados de maneira similar. Este ator também pode ser implementado
utilizando qualquer um dos seis MoCs.
1− ....
2− public void fire() throws IllegalActionException {
3−
4− ObjectToken ot = (ObjectToken) input.get(0);
5− MuxDataFrame mf = (MuxDataFrame) ot.getValue();
6− int b;
7−
8− if(_frameCounter == 0) {
9− b = _processFast(mf.getFastByte());
10− mf.setFastByte(b);
11−
12− if(!_AS0Map) {
13− for(int i = 0;i  _AS0Bytes;i++) {
14− b = _processFast(mf.getAS0Byte(i));
15− mf.setAS0Byte(b, i);
16− }
16− }
17− ....
18− if(mf.hasAEX()) {
19− b = _processFast(mf.getAEXByte());
19− mf.setAEXByte(b);
20− }
21− ....
22− }
23− else {
24− ....
25− }
26− _frameCounter++;
27− if(_frameCounter == 1 + _S) _frameCounter = 0;
28− .....
29− }
30− ....
31− private int _processFast(int b) {
32− int ret = 0;
33− int b1, b2, b3;
34−
35− for(int i = 0;i  8;i++) {
36− b1 = (_fastReg  23)  0x01;
72
37− b2 = (_fastReg  18)  0x01;
38− b3 = (b  i)  0x01;
39− ret |= ((b1 ^ b2 ^ b3)  i);
40− _fastReg = (_fastReg  1) | (b1 ^ b2 ^ b3);
41− }
42− return ret;
43− }
44− ....
45− private int _fastReg;
46− private int _interReg;
47− private int _frameCounter;
48− ....
Figura 4.16 − Trecho de código do ator ScramblerTx.
4.3.3.6 ScramblerRx
Esse ator implementa o desembaralhamento e seu comportamento é idêntico ao do ator
ScramblerTx.
4.3.3.7 FECTx
Esse ator é responsável por calcular os bytes de paridade com base no algoritmo RS, conforme
mencionado no item 4.2.5. A figura 4.17 apresenta parte do código desse ator. Novamente, a variável
_frameCounter é utilizada para determinar quando o mux data frame recebido está alocado no buffer
fast ou interleaved. Dois novos parâmetros foram introduzidos, _parityFast e _parityInter (linha 2).
Esses parâmetros especificam o número de bytes de paridade que devem ser utilizados para os dados do
buffer fast e interleaved.
1− ....
2− public Parameter parityFast, parityInter;
3− ....
4− public void fire() throws IllegalActionException {
5−
6− if(_frameCounter == 0) {
7− ObjectToken ot = (ObjectToken) input.get(0);
8− MuxDataFrame mf = (MuxDataFrame) ot.getValue();
9− int [] data = mf.getAsArray();
10− if(_nnFast != _kkFast) {
11− int [] parity = _rsEncode(data, _nnFast, _kkFast, Gg_polyFast);
12− FastFECDataFrame df = _newFastFECDataFrame(data, parity);
13− fast.send(0, new ObjectToken(df));
14− }
15− else {
16− ot = new ObjectToken(_newFastFECDataFrame(data, new int[0]));
17− fast.send(0, ot);
18− }
19− _frameCounter = 1;
20− }
21− else
22− if(_frameCounter  _S + 1) {
23− ObjectToken ot = (ObjectToken) input.get(0);
24− MuxDataFrame mf = (MuxDataFrame) ot.getValue();
25− int [] w = mf.getAsArray();
26− for(int j = 0;j  w.length;j++) {
27− _interdata[_intercount] = w[j];
28− _intercount++;
29− }
30− _frameCounter++;
31− }
32− else {
33− if(_nnInter != _kkInter) {
34− int [] parity = _rsEncode(_interdata, _nnInter,
35− _kkInter, Gg_polyInter);
36− for(int i = 0;i  (_nnInter − _kkInter);i++)
37− _interdata[_kkInter + i] = parity[i];
38− }
39− inter.broadcast(new ObjectToken(_interdata));
40− _frameCounter = 0;
41− _intercount = 0;
42− }
73
43− }
44−
45− public void initialize() throws IllegalActionException {
46− ....
47− _kkFast = _kkInter = 1;
48− if(!_AS0Map) {
49− _kkFast += _AS0Bytes;
50− }
51− else {
52− _kkInter += _AS0Bytes;
53− }
54− if(!_LS0Map) {
55− _kkFast += _LS0Bytes;
56− }
57− else {
58− _kkInter += _LS0Bytes;
59− }
60− if(!_AS0Map /* || !_AS1Map || ... */) {
61− _kkFast += 1;
62− }
63− if(!_AS0Map || !_LS0Map) {
64− _kkFast += 1;
65− }
66− if(_AS0Map) {
67− _kkInter += 1;
68− }
69− if(_AS0Map || _LS0Map) {
70− _kkInter += 1;
71− }
72− _kkInter *= _S;
73−
74− it = (IntToken) parityFast.getToken();
75− _nnFast = _kkFast + it.intValue();
76−
77− it = (IntToken) parityInter.getToken();
78− _nnInter = _kkInter + it.intValue();
79− ....
80− }
81− ....
82− private int _frameCounter;
83− private int _interdata[];
84− private int _intercount;
85−
86− private int _nnFast, _kkFast;
87− private int _nnInter, _kkInter;
88− ....
Figura 4.17 − Trecho de código do ator FECTx.
Os dados destinados ao buffer fast são processados nas linhas 7 à 19. Os dados são obtidos do
mux data frame (linhas 7 à 9) e utilizados pelo método _rsEncode() (linha 11) para gerar os bytes de
paridade. Em seguida, um novo FastFECDataFrame é construído (linha 12) e enviado (linha 13) ao
próximo ator.
Os dados destinados ao buffer interleaved são processados nas linhas 22 à 42. Como os dados de
um frame destinados à esse buffer podem estar divididos em vários mux data frame (parâmetro S), o ator
FECTx deve primeiramente obter a quantidade necessária de mux data frame. Isto está implementado
entre as linhas 22 e 30. Uma vez que todos os dados sejam obtidos, os bytes de paridade são calculados
(linha 34 e 35) e um novo frame é enviado (linha 39).
O método initialize() (linhas 45 à 80) calcula, entre outras informações, o valor das variáveis
_nnFast, _kkFast, _nnInter e_kkInter. As variáveis _kkFast e _kkInter armazenam o número de dados
destinados ao buffer fast e interleaved em um frame. As variáveis _nnFast e _nnInter armazenam o
número total de bytes.
4.3.3.8 FECRx
Esse ator é responsável por receber frames do buffer fast e interleaved e verificar a integridade
dos dados baseado no algoritmo RS. A figura 4.18 apresenta um trecho do código desse ator.
1− ....
74
2− public void fire() throws IllegalActionException {
3−
4− ObjectToken ot;
5− if(_frame == 0  fast.hasToken(0)) {
6− ot = (ObjectToken) fast.get(0);
7− FastFECDataFrame fastf = (FastFECDataFrame) ot.getValue();
8− int [] data = fastf.getAsArray();
9− if(_nnFast != _kkFast) {
10− if(_syndromeFast(data) == −1) {
11− System.out.println(ATUC−FECRx : Errors with fast data... +
12− trying to fix....);
13− data = _rsdecode(data, _synFast, _nnFast, _kkFast);
14− }
15− else {
16− System.out.println(ATUC−FECRx : No errors with fast data);
17− }
18− }
19− MuxDataFrame mf = _newMuxDataFrame(data);
20− output.send(0, new ObjectToken(mf));
21− _frame = 1;
22− }
23− if(_frame == 1  inter.hasToken(0)) {
24− ot = (ObjectToken) inter.get(0);
25− int [] data = (int []) ot.getValue();
26− if(_nnInter != _kkInter) {
27− if(_syndromeInter(data) == −1) {
28− System.out.println(ATUC−FECRx : Errors with interleaved  +
29− data...trying to fix....);
30− data = _rsdecode(data, _synInter, _nnInter, _kkInter);
31− }
32− else {
33− System.out.println(ATUC−FECRx : No errors with  +
34− interleaved data);
35− }
36− }
37− int k = 0;
38− for(int i = 0;i  _S;i++) {
39− MuxDataFrame ret = new MuxDataFrame(0, 0, 0, 0,
40− _LS0Map ? _LS0Bytes : 0, 0, 0);
41− ret.setFastByte(data[k]);
42− k++;
43−
44− if(_LS0Map) {
45− for(int j = 0;j  _LS0Bytes;j++) {
46− ret.setLS0Byte(data[k], j);
47− k++;
48− }
49− }
50− if(ret.hasLEX()) {
51− ret.setLEXByte(data[k]);
52− k++;
53− }
54− output.send(0, new ObjectToken(ret));
55− }
56− _frame = 0;
57− }
58− }
59− ....
Figura 4.18 − Trecho de código do ator FECRx.
Entre as linhas 5 e 22 os dados provenientes do buffer fast são processados. Após os dados
serem obtidos (linha 6 à 8), o método _syndrome() verifica a consistência dos dados (linha 10). Caso
algum erro seja encontrado, o método _rsDecode() (linha 13) tenta corrigi−los. O dados resultantes são
processados pelo método _newMuxDataFrame() (linha 19) para a construção de um novo mux data
frame. Os dados provenientes do buffer interleaver sofrem um tratamento similar (linha 23 à 36). A
diferença é que ao invés de um, _S mux data frame devem ser criados. Isto é implementado entre as
linhas 37 e 57.
4.3.3.9 InterleaverTx
Esse ator implementa a função de interleave dos dados. O buffer de dados é implementado como
75
um vetor grande o suficiente para armazenar a quantidade necessária de dados. Esse tamanho depende da
profundidade D de interleave (linha 2). A figura 4.19 apresenta um trecho do código desse ator.
1− ....
2− public Parameter D;
3− ....
4− public void fire() throws IllegalActionException {
5− int j;
6− ObjectToken ot = (ObjectToken) input.get(0);
7− int [] d = (int []) ot.getValue();
8−
9− int index = 0;
10− if(_isEven) {
11− index = 1;
12− }
13− for(j = 0;j  d.length;j++) {
14− _buffer[(_writePos + index + (_D − 1) * index) % _bufSize] = d[j];
15− index++;
16− }
17− if(_isEven) {
18− _readPos = (_readPos + 1) % _bufSize;
19− }
20− int out[] = new int[_nnInter];
21− for(j = 0;j  _nnInter;j++) {
22− out[j] = _buffer[_readPos];
23− _readPos = (_readPos + 1) % _bufSize;
24− }
25− _writePos = _readPos;
26− output.send(0, new ObjectToken(out));
27− }
28−
29− public void initialize() throws IllegalActionException {
30− ....
31− if(_nnInter % 2 == 0) {
32− _isEven = true;
33− _bufSize = _nnInter + 1 + (_D − 1) * (_nnInter + 1);
34− }
35− else {
36− _isEven = false;
37− _bufSize = _D * _nnInter;
38− }
39− _buffer = new int[_bufSize];
40− _readPos = 0;
41− _writePos = 0;
42− }
43−
44− private int _buffer[];
45− private int _bufSize;
46− private int _readPos;
47− private int _writePos;
48− private boolean _isEven;
49− private int _nnInter, _kkInter;
50− ....
Figura 4.19 − Trecho de código do ator InterleaverTx.
Uma vez que o frame é obtido(linha 6 e 7), cada um de seus bytes é armazenado na respectiva
posição do buffer interleaved (linha 13 à 16). Um novo frame é criado extraindo seqüencialmente os
dados do buffer (linhas 20 à 26). Os primeiros D frames enviados pelo ator irão conter alguma
quantidade de dados inválidos e deverão ser descartados pelo receptor do outro modem. O método
initialize() (linhas 29 à 42) calcula (linha 33 à 37) o tamanho mínimo do buffer interleaved para suportar
a profundidade especificada.
4.3.3.10 InterleaverRx
Esse ator executa o comportamento inverso ao do ator InterleaverTx: os dados são armazenados
no buffer de forma seqüencial (linhas 6 à 13) e os tokens são produzidos extraindo dados de várias
posições do buffer (linha 19 à 31).
1− ....
76
2− public void fire() throws IllegalActionException {
3− ObjectToken ot = (ObjectToken) input.get(0);
4− int d[] = (int []) ot.getValue();
5−
6− if(_isEven) {
7− _writePos = (_writePos + 1) % _bufSize;
8− }
9− for(int j = 0;j  _nnInter;j++) {
10− _buffer[_writePos] = d[j];
11− _writePos = (_writePos + 1) % _bufSize;
13− }
14−
15− int index = 0;
16− if(_isEven) {
17− index = 1;
18− }
19− if(_iteration == _D − 1) {
20− int [] out = new int[_nnInter];
21− for(int j = 0;j  _nnInter;j++) {
22− out[j] = _buffer[(_readPos + index + (_D − 1) * index) % _bufSize];
23− index++;
24− }
25− if(_isEven) {
26− _readPos = (_readPos + _nnInter + 1) % _bufSize;
27− }
28− else {
29− _readPos = (_readPos + _nnInter) % _bufSize;
30− }
31− output.send(0, new ObjectToken(out));
32− }
33− else {
34− _iteration++;
35− }
36− ....
Figura 4.20 − Trecho do código do ator InterleaverRx.
A variável _iteration é utilizada para contar as D primeiras iterações, uma vez que nesse período
não há dados válidos suficientes para criar um frame.
4.3.3.11 Map
O ator Map obtêm frames fast do ator FECTx e frames interleaved do ator InterleaverTx, e com
base nesses dados, produz 256 números complexos através da codificação de constelações. A figura 4.21
apresenta um trecho do código desse ator. Implementamos duas formas para a transmissão dos 256
valores: através de 256 portos individuais, um para cada valor, ou através de um porto que transmite os
dados como um vetor. Inicialmente utilizamos a primeira versão, mas devido a velocidade da execução,
a segunda opção foi adotada.
1− ....
2− public TypedIOPort fast;
3− public TypedIOPort inter;
4−
5− public TypedIOPort subChannel[];
6− ....
7− public Map(TypedCompositeActor container, String name, int outputMode)
8− throws NameDuplicationException, IllegalActionException {
9− super(container, name);
10− _createInterface(outputMode);
11− }
12−
13− public void fire() throws IllegalActionException {
14−
15− switch(_func) {
16− case 0 : {
17− if(fast.hasToken(0)) {
18− ObjectToken ot = (ObjectToken) fast.get(0);
19− FastFECDataFrame fastf = (FastFECDataFrame) ot.getValue();
20− int [] data = fastf.getAsArray();
21− for(int i = 0;i  data.length;i++) {
22− _dataBuffer[_dataCounter] = data[i];
23− _dataCounter++;
24− }
25− _func = 1;
77
26− }
27− } break;
28−
29− case 1 : {
30− if(inter.hasToken(0)) {
31− ObjectToken ot = (ObjectToken) inter.get(0);
32− int [] data = (int []) ot.getValue();
33− for(int j = 0;j  data.length;j++) {
34− _dataBuffer[_dataCounter] = data[j];
35− _dataCounter++;
36− }
37− _func = 2;
38− }
39− } break;
40−
41− case 2 : {
42− if(_outputMode == 0) {
43− int k = 0;
44− int w = 0;
45− for(int i = 0;i  255;i++) {
46− if(i == 63) {
47− subChannel[63].send(0, new
48− DoubleToken(_gainTable[63].real));
49− subChannel[63].send(0, new
50− DoubleToken(_gainTable[63].imag));
51− continue;
52− }
53− int b = 0;
54− for(int j = 0;j  _bitTable[i];j++) {
55− b |= (((_dataBuffer[w]  k)  0x01)  j);
56− k++;
57− if(k == 8) {
58− k = 0;
59− w++;
60− }
61− }
62− Complex Z = _constellation(b, _bitTable[i]);
63− subChannel[_subChannelTable[i]].send(0,
64− new DoubleToken(Z.real));
65− subChannel[_subChannelTable[i]].send(0,
66− new DoubleToken(Z.imag));
67− }
68− }
69− else {
70− int k = 0;
71− int w = 0;
72− double symbol[] = new double[510];
73− for(int i = 0;i  255;i++) {
74− if(i == 63) {
75− symbol[2*i] = _gainTable[63].real;
76− symbol[2*i + 1] = _gainTable[63].imag;
77− }
78− int b = 0;
79− for(int j = 0;j  _bitTable[i];j++) {
80− b |= (((_dataBuffer[w]  k)  0x01)  j);
81− k++;
82− if(k == 8) {
83− k = 0;
84− w++;
85− }
86− }
87− Complex Z = _constellation(b, _bitTable[i]);
88− symbol[2*i] = Z.real;
89− symbol[2*i + 1] = Z.imag;
90− }
91− subChannel[0].send(0, new ObjectToken(symbol));
92− }
93− _dataCounter = 0;
94− _func = 0;
95− } break;
96− }
97− }
98−
99− public void initialize() throws IllegalActionException {
100− ....
101− _bitTable = new int[255];
102− _subChannelTable = new int[255];
103− _gainTable = new Complex[255];
104−
78
105− int tot = (_nnFast + _nnInter) * 8;
106− for(int i = 0;i  255;i++) {
107− _subChannelTable[i] = i;
108− if(i == 63) {
109− _bitTable[i] = 0;
110− _gainTable[i] = new Complex(1,1);
111− continue;
112− }
113− _gainTable[i] = new Complex(1, 1);
114−
115− if(tot  4) {
116− _bitTable[i] = 5;
117− tot −= 5;
118− }
119− else
120− if(tot  0) {
121− _bitTable[i] = tot;
122− tot = 0;
123− }
124− else {
125− _bitTable[i] = 0;
126− }
127− }
128− _dataBuffer = new int[_nnInter + _nnFast];
129− _dataCounter = 0;
130− _func = 0;
131− }
132−
133− private void _createInterface(int outputMode) throws
134− IllegalActionException, NameDuplicationException {
135− ....
136− if(_outputMode == 0) {
137− subChannel = new TypedIOPort[255];
138− for(int i = 0;i  255;i++) {
139− subChannel[i] = new TypedIOPort(this, subChannel_ + i,
140− false, true);
141− subChannel[i].setTypeEquals(DoubleToken.class);
142− }
143− }
144− else {
145− subChannel = new TypedIOPort[1];
146− subChannel[0] = new TypedIOPort(this, subChannel0, false, true);
147− subChannel[0].setTypeEquals(ObjectToken.class);
148− }
149− ....
150− }
151− private int _bitTable[];
152− private int _subChannelTable[];
153− private Complex _gainTable[];
154−
155− private int _dataBuffer[];
156− private int _dataCounter;
157− private int _func;
158− private int _outputMode;
159− ....
Figura 4.21 − Trecho de código do ator Map.
Entre as linhas 16 e 26 os dados do frame fast são obtidos, e entre as linha 30 e 38 os dados do
frame interleaved. Entre as linhas 43 e 67 (utilizando a versão com 256 portos de saída) ou entre as linha
70 e 91 (utilizando a versão com um porto de saída), os dados são divididos para cada subcanal (linha 53
à 61, ou 78 à 86) baseados em uma tabela (o vetor _bitTable da linha 151) que especifica os números de
bits por subcanal. Essa tabela já deve estar ordenada por tom. Como não implementamos toda a máquina
de inicialização, determinamos o número de bits por subcanal de maneira aleatória (método initialize()).
O método _constellation() (linhas 62 ou 87) implementa a codificação de constelações descrita no item
4.2.8. O método _createInterface() (linhas 133 à 150) é responsável por construir o ator com a versão
correta dos portos de saída.
4.3.3.12 Demap
O ator Demap executa a função inversa do ator Map: recebe uma certa quantidade de valores
complexos e produz um frame fast e um frame interleave. A figura 4.22 apresenta um trecho de código
79
do ator. Como este ator é utilizado pelo modem ATU−C, ele recebe 32 valores complexos. Assim como
no caso do ator Map, duas versão para a quantidade de portos de saída foram empregadas: uma com 32
portos individuais e outra com apenas um porto que recebe um vetor de números complexos.
1− ....
2− public void fire() throws IllegalActionException {
3−
4− int bc = 0, wc = 0;
5− int data[] = new int[_nnInter + _nnFast];
6−
7− if(_inputMode == 0) {
8− for(int i = 0;i  31;i++) {
9− if(i == 15) {
10− subChannel[15].get(0);
11− subChannel[15].get(0);
12− continue;
13− }
14− DoubleToken dtr =
15− (DoubleToken) subChannel[_subChannelTable[i]].get(0);
16− DoubleToken dti =
17− (DoubleToken) subChannel[_subChannelTable[i]].get(0);
18−
19− Complex Z = new Complex(dtr.doubleValue(), dti.doubleValue());
20− int b = _constellation(Z, _bitTable[i]);
21− for(int j = 0;j  _bitTable[i];j++) {
22− data[wc] |= (((b  j)  0x01)  bc);
23− bc++;
24− if(bc == 8) {
25− bc = 0;
26− wc++;
27− }
28− }
29− }
30− }
31− else {
32− ObjectToken ot = (ObjectToken) subChannel[0].get(0);
33− double [] indata = (double []) ot.getValue();
34− for(int i = 0;i  31;i++) {
35− if(i == 15) {
36− continue;
37− }
38− Complex Z = new Complex(indata[2*i], indata[2*i + 1]);
39− int b = _constellation(Z, _bitTable[i]);
40− for(int j = 0;j  _bitTable[i];j++) {
41− data[wc] |= (((b  j)  0x01)  bc);
42− bc++;
43− if(bc == 8) {
44− bc = 0;
45− wc++;
46− }
47− }
48− }
49− }
50− wc = 0;
51− FastFECDataFrame fastf = new FastFECDataFrame(0, 0, 0, 0,
52− _LS0Map ? 0 : _LS0Bytes, 0, 0, _nnFast − _kkFast);
53−
54− fastf.setFastByte(data[wc]);
55− wc++;
56− if(!_LS0Map) {
57− for(int i = 0;i  _LS0Bytes;i++) {
58− fastf.setLS0Byte(data[wc], i);
59− wc++;
60− }
61− }
62− if(fastf.hasLEX()) {
63− fastf.setLEXByte(data[wc]);
64− wc++;
65− }
66− for(int i = 0;i  (_nnFast − _kkFast);i++) {
67− fastf.setParityByte(data[wc], i);
68− wc++;
69− }
70− fast.send(0, new ObjectToken(fastf));
71−
72− int [] out = new int[_nnInter];
73− for(int i = 0;i  _nnInter;i++) {
80
74− out[i] = data[wc];
75− wc++;
76− }
77− inter.broadcast(new ObjectToken(out));
78− }
79− ....
Figura 4.22 − Trecho de código do ator Demap.
Entre as linha 8 e 29 ou 32 e 48, o conjunto de valores complexos são obtidos, transformados em
um vetor de bits e armazenados em um buffer. Esse bloco utiliza a mesma tabela que o ator Map e
utiliza−a para construir os diferentes frames (linha 50 à 77).
4.3.3.13 IDFT
Esse ator é responsável por implementar a transformada discreta inversa de Fourier. A entrada é
composta por 256 valores complexos, que são estendido para 512 valores (linhas 25 à 29 ou 36 à 39).
Esse ator também utiliza as duas versões para os portos de saída.
1− ....
2− public void fire() throws IllegalActionException {
3− int index;
4−
5− if(mode.hasToken(0)) {
6− BooleanToken bt = (BooleanToken) mode.get(0);
7− _mode = bt.booleanValue();
8− }
9− else {
10− if(_mode) {
11− index = 0;
12− }
13− else {
14− index = 1;
15− }
16−
17− _data[0] = 0;
18− _data[1] = 0;
19− _data[512] = 0;
20− _data[513] = 0;
21−
22− if(_inputMode == 0) {
23− for(int i = 1;i  256;i++) {
24− DoubleToken dt = (DoubleToken) subChannel[i − 1].get(index);
25− _data[2*i] = dt.doubleValue();
26− _data[1024 − 2*i] = _data[2*i];
27− dt = (DoubleToken) subChannel[i − 1].get(index);
28− _data[2*i + 1] = dt.doubleValue();
29− _data[1025 − 2*i] = −1 * _data[2*i + 1];
30− }
31− }
32− else {
33− ObjectToken ot = (ObjectToken) subChannel[0].get(index);
34− double [] tmp = (double []) ot.getValue();
35− for(int i = 1;i  256;i++) {
36− _data[2*i] = tmp[2*i − 2];
37− _data[1024 − 2*i] = _data[2*i];
38− _data[2*i + 1] = tmp[2*i − 1];
39− _data[1025 − 2*i] = −1 * _data[2*i + 1];
40− }
41− }
42− _idft();
43−
44− if(_outputMode == 0) {
45− for(int i = 0;i  512;i++) {
46− output.send(0, new DoubleToken(_data[2*i]));
47− }
48− }
49− else {
50− double [] symbol = new double[512];
51− for(int i = 0;i  512;i++) symbol[i] = _data[2*i];
52− output.send(0, new ObjectToken(symbol));
53− }
54− }
81
55− }
56− ....
Figura 4.23 − Trecho de código do ator IDFT no MoC DE.
Conforme pode ser observado na figura 4.12, o ator IDFT possui duas conexões nos seus portos
de entrada: uma proveniente do ator Map e outra do ator Initializer. A conexão com o ator Map é
referente ao processamento normal dos dados a serem transmitidos. A conexão com o ator Initializer é
necessária para enviar os sinais de inicialização. Dessa forma, esse ator deve ser capaz de a qualquer
momento, obter os dados de diferentes portos, conforme um modo de operação especificado (linhas 5 à
15). Isso implica que os modelos data flow e o modelo PN não são adequados para capturar tal ator.
Utilizamos o modelo DE.
4.3.3.14 DFT
Esse ator implementa a transformada discreta de Fourier. Ele recebe 64 valores reais (linhas 12
ou 18) e produz 32 valores complexos (linhas 34 à 37 ou 40 à 44). Assim como o ator IDFT, é
necessário que esse ator possua duas conexões diferentes para seus portos de saída.
1− ....
2− public void fire() throws IllegalActionException {
3−
4− int index;
5− if(mode.hasToken(0)) {
6− BooleanToken bt = (BooleanToken) mode.get(0);
7− _mode = bt.booleanValue();
8− }
9− else {
10− if(_inputMode == 0) {
11− for(int i = 0;i  128;i += 2) {
12− DoubleToken dt = (DoubleToken) input.get(0);
13− _data[i] = dt.doubleValue();
14− _data[i+1] = 0;
15− }
16− }
17− else {
18− ObjectToken ot = (ObjectToken) input.get(0);
19− double [] tmp = (double []) ot.getValue();
20− for(int i = 0;i  64;i++) {
21− _data[2*i] = tmp[i];
22− _data[2*i + 1] = 0;
23− }
24− }
25− _dft();
26−
27− if(_mode) {
28− index = 0;
29− }
30− else {
31− index = 1;
32− }
33− if(_outputMode == 0) {
34− for(int i = 1;i  32;i++) {
35− subChannel[i − 1].send(index, new
36− DoubleToken(Math.round(_data[2*i])), 1.0);
37− subChannel[i − 1].send(index, new
38− DoubleToken(Math.round(_data[2*i + 1])), 1.0);
39− }
40− }
41− else {
42− double [] out = new double[62];
43− for(int i = 2;i  64;i++) {
44− out[i − 2] = Math.round(_data[i]);
45− }
46− subChannel[0].send(index, new ObjectToken(out), 1.0);
47− }
48− }
49− }
50− ....
Figura 4.24 − Trecho de código do ator DFT no MoC DE.
82
4.3.3.15 Initializer
Esse ator implementa a máquina de estados responsável pela inicialização do modem. Conforme
o item 4.2.10, a inicialização corresponde a troca de símbolos pré−definidos durante durações exatas, e a
execução de operações de configuração e treinamento de blocos do data path.
A fim de tornar a implementação desse ator mais clara, criamos a classe auxiliar InitSymbols que
contém constantes representando os diferentes estados e sinais de inicialização para ambos os modems.
A figura 4.25 apresenta um trecho de código do ator.
1− public class InitSymbols {
2−
3− public static final int C_QUIET1 = 0;
4− public static final int MY_CSILENT1 = 1;
5− public static final int C_IDLE = 2;
6− public static final int C_TONE = 3;
7− public static final int C_ACT = 4;
8− public static final int C_QUIET2 = 5;
9−
10− public static final int R_ACT_REQ = 0;
11− public static final int MY_DETECT_C_ACT = 1;
12− public static final int R_QUIET1 = 2;
13− public static final int R_ACK = 3;
14−
12− public static final int SHOWTIME = 60000;
13−
14− public static final int C_ACT1_S1 = 0;
15− public static final int C_ACT1_S2 = 1;
16− ....
17− public static final int R_ACT_REQ_S1 = 0;
18− public static final int R_ACT_REQ_S2 = 1;
19− ....
20−
21− public InitSymbols() {
22−
23− _ATUCSymbolTable = new double[12][510];
24−
25− _ATUCSymbolTable[C_ACT1_S1][96] = 1;
26− _ATUCSymbolTable[C_ACT1_S1][97] = 1;
27− _ATUCSymbolTable[C_ACT1_S2][96] = 2;
28− _ATUCSymbolTable[C_ACT1_S2][97] = 2;
29− ....
30−
31− _ATURSymbolTable = new double[9][62];
32−
33− _ATURSymbolTable[R_ACT_REQ_S1][16] = 1;
34− _ATURSymbolTable[R_ACT_REQ_S1][17] = 1;
35− ....
36− }
37−
38− public boolean checkATURSymbol(int symbol, double [] value) {
39− for(int i = 0;i  62;i++) {
40− if(value[i] != _ATURSymbolTable[symbol][i]) {
41− return false;
42− }
43− }
44− return true;
45− }
46− ....
47− public double [] getATURSymbol(int symbol) {
48− return _ATURSymbolTable[symbol];
49− }
50− ....
51− private double _ATUCSymbolTable[][];
52− private double _ATURSymbolTable[][];
53− }
Figura 4.25 − Classe auxiliar com os símbolos de inicialização.
Os estados para o modem ATU−C estão representados entre as linhas 3 e 8 e para o modem
ATU−R entre as linhas 10 e 13. A potência de transmissão de cada sinal é codificada por um valor real
armazenado em uma matriz (linhas 51 e 52), onde a primeira dimensão indica um sinal e a segunda uma
freqüência. A codificação do sinal C−ACT1 é feita nas linhas 14 e 15, e é dividida em duas constantes:
C_ACT1_S1 e C_ACT1_S2. As duas constantes representam os dois níveis de potência do sinal C−
83
ACT1. Isso é feito similarmente para os outros sinais de inicialização. O método _checkATURSymbol()
(linhas 38 à 45) é utilizado para obter o código de um sinal do modem ATU−R, dado um vetor de
números reais. O método _getATURSymbol() (linhas 47 à 49) é utilizado para a função inversa. Métodos
similares também existem para o modem ATU−C.
A partir da definição do processo de inicialização, é possível observar que o momento da
ocorrência e o tempo de duração de um sinal é de fundamental importância para a correta inicialização
do modem. Dessa forma, é necessário utilizar um modelo computacional flexível e com um modelo de
tempo. Como os atores do data path do modem utilizam o modelo DE, e este MoC apresenta as
características mencionadas, decidimos utilizá−lo também na captura da máquina de inicialização.
Entretanto, deparamos com uma dificuldade na utilização do método fireAt() para o escalonamento de
ativamentos futuros. Por exemplo, o sinal R−ACT−REQ é definido como 128 símbolos consecutivos
seguidos por um período sem transmissão. Poderíamos modelar essa situação com uma chamada do tipo
fireAt(currentTime + quietTime), onde quietTime é o tempo sem atividade de transmissão. Entretanto,
caso o modem ATU−C tenha detectado o sinal R−ACT−REQ e respondido apropriadamente, o modem
ATU−R deve mudar para outro estado muito provavelmente antes do tempo quietTime. Entretanto, o
ativamento futuro devido à chamada do método fireAt() continuaria válido e iria escalonar o ator. Isso
pode levar à um comportamento indevido.
Uma solução para o problema mencionado seria introduzir a capacidade de eliminar eventos
futuros da fila do modelo DE. Entretanto, tal solução parece complexa. Adotamos outra solução que
utiliza o ator Timer. A figura 4.26 apresenta o método fire() desse ator. Esse ator é responsável por gerar
eventos em momentos requisitados através de um evento no porto StartTimer. A requisição fica ativa até
o momento da geração do evento ou até que um evento no porto StopTimer() seja detectado. Neste caso,
embora o ator seja ativado (divido ao método fireAt()), nenhum evento é produzido.
1− ....
2− public void fire() throws IllegalActionException {
3−
4− if(stopTimer.hasToken(0)) {
5− if(_active == 0) {
6− throw new IllegalActionException(this, Trying to stop when  +
7− it was not started);
8− }
9− stopTimer.get(0);
10− _active = 0;
11− }
12− if(startTimer.hasToken(0)) {
13− if(_active != 0) {
14− throw new IllegalActionException(this, Trying to start when  +
15− it was not stoped);
16− }
17− DoubleToken dt = (DoubleToken) startTimer.get(0);
18− double t = dt.doubleValue() + getCurrentTime();
19− fireAt(t);
20− _active = t;
21− }
22− if(getCurrentTime() == _active) {
23− _active = 0;
24− timerEvent.send(0, new Token());
25− }
26− }
27− private double _active;
28− ....
Figura 4.26 − Método fire() do ator Timer.
Entre as linhas 4 e 11 está implementada a anulação de uma requisição. Isso ocorre quando o
valor da variável _active é igualado à zero. Entre as linhas 12 e 21 uma nova requisição é processada. A
variável _active armazena o instante de tempo em que um evento deve ser produzido. Entre as linhas 22
e 25 um evento é produzido. Note que apenas uma requisição pode ser processada ao mesmo tempo.
Além da classe auxiliar InitSymbols e do ator Timer, desenvolvemos alguns métodos auxiliares
para o ator Initializer.
O método _getSymbol() (figura 4.27) é utilizado para receber e armazenar símbolos. Devido as
conexões entre o ator Initializer e os atores IDFT e DFT, o ator Initializer deve possuir as duas versões
(vários portos recebendo valores numéricos ou um porto recebendo um vetor) de portos de comunicação.
84
1− private void _getSymbol() throws IllegalActionException {
2−
3− _lastFire = _currentFire;
4− _currentFire = getCurrentTime();
5−
6− if(fromATUR[0].hasToken(0)) {
7− for(int i = 0;i  _symbol.length;i++) {
8− _lastsymbol[i] = _symbol[i];
9− }
10− if(_fromATURMode == 0) {
11− DoubleToken dt;
12− for(int i = 0;i  31;i++) {
13− dt = (DoubleToken) fromATUR[i].get(0);
14− _symbol[2*i] = dt.doubleValue();
15− dt = (DoubleToken) fromATUR[i].get(0);
16− _symbol[2*i + 1] = dt.doubleValue();
17− }
18− }
19− else {
20− ObjectToken ot = (ObjectToken) fromATUR[0].get(0);
21− _symbol = (double []) ot.getValue();
22− }
23− _hassymbol = true;
24− }
25− else {
26− for(int i = 0;i  _symbol.length;i++) {
27− _lastsymbol[i] = _symbol[i];
28− _symbol[i] = 0;
29− }
30− _hassymbol = false;
31− }
32− }
Figura 4. 27 − Método _getSymbol() do ator Initializer.
Na linha 4 o momento da recepção do símbolo é armazenado. Entre as linha 7 e 9 o valor do
último símbolo é armazenado e entre as linhas 10 e 18 ou 19 e 22 o valor do símbolo recebido é
armazenado. A variável _hassymbol (linha 30) indica se um símbolo foi recebido.
O método auxiliar _sendSymbol() é utilizado para enviar um símbolo. A figura 4.28 apresenta o
código.
1− private void _sendSymbol(int symbol) throws IllegalActionException {
2− double [] s = _initSymbols.getATUCSymbol(symbol);
3− if(_toATURMode == 0) {
4− for(int i = 0;i  255;i++) {
5− toATUR[i].send(0, new DoubleToken(s[2*i]));
6− toATUR[i].send(0, new DoubleToken(s[2*i + 1]));
7− }
8− }
9− else {
10− toATUR[0].send(0, new ObjectToken(s));
11− }
12− }
Figura 4.28 − Método _sendSymbol() do ator Initializer.
Na linha 2, o vetor de sinais necessário para enviar o símbolo especificado é obtido com o
auxílio da classe InitSymbols.
A figura 4.29 apresenta o método _detectSignal(). Esse método foi desenvolvido a fim de
simplificar a implementação da máquina de estados. Sua função é determinar quando um sinal foi
recebido com sucesso.
1− private byte _detectSignal(int S1, int S2) throws IllegalActionException {
2−
3− if(_rxCounter == 0) {
4− if(_hassymbol) {
5− if(_initSymbols.checkATURSymbol(S1, _symbol)) {
6− _rxCounter++;
7− }
8− else {
9− _rxCounter = 0;
10− return 0;
11− }
12− }
13− else {
85
14− _rxCounter = 0;
15− return 0;
16− }
17− }
18− else
19− if(_rxCounter  64/_BASE) {
20− if(_hassymbol  ((_currentFire − _lastFire) == 250)) {
21− if(_initSymbols.checkATURSymbol(S1, _symbol)) {
22− _rxCounter++;
23− }
24− else {
25− _rxCounter = 0;
26− return 0;
27− }
28− }
29− else {
30− _rxCounter = 0;
31− return 0;
32− }
33− }
34− else
35− if(_rxCounter  128/_BASE) {
36− if(_hassymbol  ((_currentFire − _lastFire) == 250)) {
37− if(_initSymbols.checkATURSymbol(S2, _symbol)) {
38− _rxCounter++;
39− if(_rxCounter == 128/_BASE) {
40− _rxCounter = 0;
41− return 2;
42− }
43− }
44− else {
45− _rxCounter = 0;
46− return 0;
47− }
48− }
49− else {
50− _rxCounter = 0;
51− return 0;
52− }
53− }
54− return 1;
55− }
Figura 4.29 − Método _detectSignal() do ator Initializer.
O código da figura 4.29 é dividido em três situações. Entre as linhas 3 e 17 ocorre a recepção do
primeiro símbolo do sinal. Apenas o valor do símbolo é verificado (linha 5). Quando alguma condição
de recepção for violada, a variável _rxCounter, responsável por contar a quantidade de símbolos
recebidos, é igualada à zero. A segunda situação está implementada entre as linha 19 e 33. Neste trecho
ocorre a verificação do primeiro nível de potência do sinal. Na linha 20 é verificado se o símbolo foi
recebido no instante adequado e na linha 21 o valor do símbolo é verificado. O mesmo ocorre para o
segundo nível de potência do sinal entre as linhas 35 e 53. O método _detectSignal() retorna três valores:
0 caso ocorra um erro, 1 caso o ator tenha recebido um símbolo válido mas não é o último do sinal e 2
caso seja o último símbolo.
O método _transmitSignal() é apresentado na figura 4.30. Sua função é transmitir um sinal.
1− private byte _transmitSignal(int S1, int S2) throws IllegalActionException {
2− if(_txCounter  64/_BASE) {
4− _sendSymbol(S1);
5− startTimer.send(0, new DoubleToken(249));
6− _txCounter++;
7− }
8− else
9− if(_txCounter  128/_BASE) {
10− _sendSymbol(S2);
11− _txCounter++;
12− if(_txCounter == 128/_BASE) return 1;
13− startTimer.send(0, new DoubleToken(249));
14− }
15− return 0;
16− }
Figura 4.30 − Método _transmitSignal() do ator Initializer.
Entre as linhas 2 e 7 o primeiro nível de potência do sinal é enviado e entre as linhas 9 e 14 o
86
segundo nível de potência. O método retorna 0 enquanto o sinal não tenha sido enviado e 1 quando a
transmissão for completada.
Finalmente, o código para a máquina de estados ATU−C é apresentado na figura 4.31.
1− ....
1− public void fire() throws IllegalActionException {
2−
3− if(timerEvent.hasToken(0)) {
4− timerEvent.get(0);
5− _timerEvent = true;
6− }
7− else {
8− _timerEvent = false;
9− }
10− _getSymbol();
11−
12− if(reset.hasToken(0)) {
13− ....
14− }
15− else {
16− switch(_state) {
17− case _initSymbols.C_QUIET1: {
18− _ret = _detectSignal(_initSymbols.R_ACT_REQ_S1,
19− _initSymbols.R_ACT_REQ_S2);
20− if(_ret == 2) {
21− _state = _initSymbols.MY_CSILENT1;
22− startTimer.send(0, new DoubleToken(251));
23− }
24− } break;
25−
26− case _initSymbols.MY_CSILENT1: {
27− if(!_hassymbol) {
28− _state = _initSymbols.C_ACT;
29− _C_ACTCounter++;
30− }
31− else {
32− _state = _initSymbols.C_QUIET1;
33− _C_ACTCounter = 0;
34− }
35− startTimer.send(0, new DoubleToken(250));
36− _rxCounter = 0;
37− } break;
38−
39− case _initSymbols.C_ACT: {
40− _ret = _transmitSignal(_initSymbols.C_ACT2_S1,
41 _initSymbols.C_ACT2_S2);
42− if(_ret == 1) {
43− _txCounter = 0;
44− _state = _initSymbols.C_QUIET2;
45− startTimer.send(0, new DoubleToken(250 * 129));
46− }
47− } break;
48−
49−
50− case _initSymbols.C_QUIET2: {
51− _ret = _detectSignal(_initSymbols.R_ACK2_S1,
52− _initSymbols.R_ACK2_S2);
53− if(_ret == 2) {
54− _state = _initSymbols.SHOWTIME;
55− _rxCounter = 0;
56− startTimer.send(0, new DoubleToken(250));
57− if(!_timerEvent) stopTimer.send(0, new Token());
58− _C_ACTCounter = 0;
59− }
60− else
61− if(_ret == 0) {
62− if(_C_ACTCounter  2) {
63− _state = _initSymbols.C_ACT;
64− _C_ACTCounter++;
65− }
66− else {
67− _state = _initSymbols.C_QUIET1;
68− _C_ACTCounter = 0;
69− }
70− _rxCounter = 0;
71− if(!_timerEvent) stopTimer.send(0, new Token());
72− }
73− else
87
74− if(_ret == 1) {
75− if(_timerEvent) {
76− if(_C_ACTCounter  2) {
77− _state = _initSymbols.C_ACT;
78− _C_ACTCounter++;
79− }
80− else {
81− _state = _initSymbols.C_QUIET1;
82− _C_ACTCounter = 0;
83− }
84− _rxCounter = 0;
85− }
86− }
87− } break;
88− ....
89− }
90− }
91− }
92− ....
Figura 4.31 − Método fire() do ator Initializer.
Entre as linhas 3 e 9 é verificado a presença de um evento enviado pelo ator Timer. Na linha 10 a
presença de um símbolo é testada. Entre as linhas 12 e 14 a condição de reinicialização é verificada. O
estado C−QUIET1 está implementado entre as linhas 17 e 23. Caso um sinal válido seja detectado (linha
18), ocorre a transição para o estado MY−CSILENT1 (linha 20 e 21). Esse estado (linhas 26 à 37) é
utilizado para verificar a ausência do sinal R−ACT−REQ. O estado C−ACT (linhas 39 à 47) é utilizado
para transmitir o respectivo sinal (linha 40 e 41). Na linha 45 está implementado a máxima duração
permitida para a espera do sinal de acknowledgment do modem ATU−R. Caso esse sinal seja detectado,
o código da linha 54 à 58 é executado e a fase de ativação está encerrada. Caso contrário, o máquina de
estados irá esperar até atingir o limite máximo ou continuar recebendo o sinal.
A máquina de estados para o modem ATU−R é implementada de maneira similar, mas
respeitando os estados e transições descritos no item 4.2.10.
4.4 Discussão
Esse capítulo apresentou o estudo de caso que desenvolvemos. Criamos uma especificação
executável para um subconjunto de um modem ADSL. O principal objetivo desse esforço é identificar
alguma estratégia para utilizar os resultados obtidos através do estudo das primitivas comportamentais na
escolha de um modelo computacional.
Após termos analisado a especificação textual do modem (item 4.2), determinamos que o MoC
PN parecia ser o mais adequado, devido à grande quantidade de blocos transformacionais (primitiva
comportamental ¨seqüência de expressões¨). Além disso, esse modelo também apresentava uma noção de
tempo (ao contrário dos modelos data flow), o que era necessário para capturar o comportamento da
inicialização. De fato, foi possível criar uma especificação eficiente para todo o data path do modem
(vide figura 4.12). Entretanto, quando tentamos introduzir o bloco de inicialização, não foi possível
implementar eficientemente os atores IDFT e DFT. Conforme foi mostrado no item 4.3.3.15, é
necessário que esses atores se comuniquem com a máquina de inicialização, além dos atores do data
path. Alem disso, o padrão de comunicação não é conhecido. Dessa forma, temos a primitiva
comportamental de desvio condicional. Como foi demonstrado no capítulo 2, o modelo PN não é o mais
adequado para capturar essa primitiva.
Para terminar a descrição do modem, tivemos então que escolher entre dois MoCs: SR e DE.
Escolhemos o modelo DE devido ao modelo explícito de tempo. Entretanto, a utilização do modelo SR é
possível e deve ser considerada como um trabalho futuro. O modelo DE se mostrou adequado na captura
do restante do modem. O único problema encontrado foi referente à necessidade de anular eventos já
escalonados, conforme foi mencionado no item 4.3.3.15. Desenvolvemos uma solução eficiente que
emprega um ator temporizador.
Podemos considerar a captura da máquina de inicialização como um exemplo de uma nova
primitiva comportamental: protocolo de comunicação. Essa primitiva se caracteriza pela definição
precisa da troca de eventos entre dois ou mais atores, incluindo restrições temporais. Como trabalho
futuro, utilizaremos nossa ferramenta para depuração/profiling na especificação executável do modem, a
88
fim de estudar tal primitiva.
89
Capítulo 5
Conclusões e Trabalhos Futuros
5.1 Considerações Finais
Nessa dissertação abordamos o problema da adequação/eficiência de um modelo computacional
para a criação de especificações executáveis. O ambiente Ptolemy II foi utilizado como ferramenta de
software para o desenvolvimento do trabalho, uma vez que além de possuir implementado alguns
modelos computacionais, ele também provê uma infra−estrutura eficiente para a implementação de
outros MoCs. Seis modelos computacionais foram comparados. Nossa metodologia fundamenta−se no
conceito de primitiva comportamental, conforme definida no capítulo 3. Desenvolvemos especificações
para capturar três primitivas e analisamos a adequação de cada uma com relação aos seis modelos.
Outras cinco primitivas comportamentais foram enumeradas. Uma ferramenta para depurar a execução
de uma especificação foi desenvolvida para auxiliar a análise de cada primitiva. Essa ferramenta também
pode ser utilizada para outras finalidades, como melhorar a eficiência de uma especificação ou comparar
diferentes implementações de um mesmo modelo computacional. Finalmente, desenvolvemos um estudo
de caso de um sistema real, com o intuito de aplicar os resultados da análise das três primitivas
comportamentais. Como resultado, identificamos como cada parte do sistema utilizava uma primitiva
comportamental. Além disso, uma nova primitiva foi identificada.
5.2 Trabalhos Futuros
Podemos enumerar as seguintes contribuições pretendidas por este trabalho:
1. uma biblioteca de critérios para auxiliar o projetista nas tarefas de escolher os modelos
computacionais mais adequados e construir as especificações executáveis. Duas métricas
devem ser consideradas: captura/validação e implementação física (síntese);
2. ferramentas de análise de especificações executáveis;
3. uma estratégia de como aplicar os resultados das contribuições anteriores em um sistema de
grande porte.
Neste trabalho ainda não alcançamos tais objetivos. Analisamos três primitivas comportamentas
considerando o critério de captura/validações e desenvolvemos o protótipo de uma ferramenta para
análise de especificações executáveis. As seguintes tarefas futuras devem ser efetuadas para alcançarmos
tais objetivos:
1. ampliar o número de primitivas comportamentais analisadas;
2. avaliar outros modelos computacionais que não estão implementados no ambiente Ptolemy;
3. criar variantes de modelos computacionais implementados no ambiente Ptolemy;
4. analisar a utilização de combinações (pares, triplas, etc.) de MoCs para descrever sistemas
(toy benchmarks) compostos por uma ou mais primitivas comportamentais;
5. incorporar à análise uma métrica que permita estimar a eficiência da utilização de um
determinado MoC para a implementação física de uma primitiva comportamental;
6. desenvolver estudos de caso de sistemas de grande porte.
A primeira tarefa consiste em realizar a análise de primitivas comportamentais que já
identificamos como relevantes. São seis primitivas ao total: sincronismo, compartilhamento de recursos,
concorrência, preempção, recursão e protocolo de comunicação. Outras primitivas poderão ser incluídas
90
durante o desenvolvimento do trabalho.
As tarefas 2 e 3 procuram ampliar o leque de alternativas para a análise das primitivas. Para tal,
pretendemos introduzir novos modelos computacionais ao ambiente Ptolemy e modificar modelos já
implementados através da variação de alguma de suas características, como por exemplo o escalonador.
O ambiente Ptolemy é implementado de tal forma que é possível utilizar, em uma mesma
especificação executável, vários modelos computacionais distintos, por exemplo modelos data flow,
discrete event, synchronous reactive, redes de Petri etc. O principal argumento para a necessidade dessa
característica é a heterogeneidade das aplicações, ou seja, em um único sistema existem subsistemas de
hardware dominados pelo fluxo de dados, software de tempo real, circuitos assíncronos, circuitos
analógicos, hardware dominado pelo fluxo de controle, etc. Dessa forma, um único modelo
computacional não é capaz de capturar eficientemente tais aplicações. A tarefa 4 visa estudar a aplicação
de mais de um modelo computacional em uma única aplicação. Com base no resultado da adequação do
par primitiva/MoC, iremos utilizar especificações executáveis de sistema reais, ou parte desses sistemas,
para verificar se o particionamento da especificação em vários MoCs é vantajosa.
É necessário considerar a implementação dos sistemas a partir de especificações executáveis. Isto
implica em analisar as metodologias de síntese, ou seja, uma seqüência de tarefas (e ferramentas CAD
associadas) que refinam a especificação inicial até atingir um grau de abstração conhecido e dominado.
Por exemplo, no caso da síntese de circuitos, o objetivo é criar uma descrição em nível comportamental
[MIC94], ou uma descrição RTL para posterior síntese [MIC94]. No caso de software o objetivo é obter
um programa em alguma linguagem seqüencial (por exemplo C), mais o sistema operacional. Na tarefa
5, pretendemos escolher um número reduzido de MoCs adequados para síntese, e estudar a viabilidade
de tradução/refinamento dos outros modelos para os modelos de síntese.
Na tarefa 6, iremos aplicar os resultados e conclusões obtidos nas 5 tarefas anteriores. A fim de
evitar o tempo (longo) necessário para estudar as especificações de um sistema de grande porte e gerar
uma descrição inicial, adotaremos a estratégia de interagir com grupos de pesquisa que já realizaram essa
tarefa e com os quais estamos em contato. Possíveis exemplos18
para estudo de caso são:
  sistema para tradução de textos: em [FRA00], foi descrito um sistema embutido para a
tradução de textos utilizando uma solução com microprocessadores e ASIC. Tal sistema foi
desenvolvido por colegas da Universidade Federal do Rio Grande do Sul;
  codificador/Decodificador de Voz: em [GAJ00] está descrito detalhadamente em vários
níveis de abstração um sistema vocoder para telefones celulares;
  sistemas para comunicação sem fio: nosso grupo de pesquisa possui um conjunto de
pesquisadores desenvolvendo soluções relacionadas à comunicação sem fio. Já foi
desenvolvido em [GIU98] um módulo codificador/decodificador de canal utilizando códigos
convolucionaiss. Atualmente um aluno de mestrado e outro de doutorado estão aprimorando
tal módulo utilizando técnicas iterativas [GIU00]. Outro aluno de doutorado esta
desenvolvendo como parte de seu trabalho a implementação de alguns módulos do sistema
Bluetooth [BLUE], um padrão de comunicação em radio freqüência para curtas distâncias;
  modem ADSL: podemos ampliar o estudo de caso desenvolvido nesse trabalho ampliando a
funcionalidade capturada, implementando novos módulos e utilizando outros MoCs.
18 Oportunamente, um ou mais destes exemplos, ou outros que possam surgir, serão usados para esta tarefa.
91
Referências Bibliográficas
[ADSL] ANSI/T1E1.413, Asymmetric Digital Subscriber Line (ADSL) Mettalic Interface, June 12,
1998.
[ARV82] Arvind, K. P. Gostelow, The U−Interpreter, Computer, February, 1982.
[BEN91] A. Benveniste, G. Berry, The Synchronous Approach to Reactive and Real−Time Systems,
Proceedings of the IEEE, Vol. 79, No. 9, September 1991.
[BEC92] D. Becker, R. K. Singh, S. G. Tell, An Engineering Environment for HW/SW Co−
Simulation, Proceedings of the Design Automation Conference, 1992.
[BER98] G. Berry, The Esterel v5 Language Primer, Centre de Mathématiques Appliquées, Ecole des
Mines and INRIA, March, 1998.
[BHA94] S. S. Bhattacharyya, Compiling Dataflow Programs for Digital Signal Processing, PhD
Dissertation, Dept. EECS, University of California, Berkeley, July, 1994.
[BIL96] G. Bilsen, M. Engels, R. Lauwereins, J. Peperstraete, Cyclo−Static Dataflow, IEEE
Transactions on Signal Processing, Vol. 44, No. 2, February, 1996.
[BLUE] Especificação do Padrão Bluetooth: http://www.bluetooth.com
[BRE98] J. E. Brewer, A New and Improved Roadmap, IEEE Circuits  Devices, March, 1998.
[BRO81] J. D. Brock, W. B. Ackerman, Scenarios, a model of nondeterminate computation¨, Conf.
on Formal Definition of Programming Concepts, LNCS 107, 1981.
[BUC93] J. T. Buck, Scheduling Dynamic Dataflow Graphs with Bounded Memory using the
Token Flow Model, PhD Dissertation, Dept. EECS, University of California, Berkeley, 1993.
[CHA99] H. Chang, L. Cooke, M. Hunt, G. Martin, A. McNelly, L. Todd, Surviving the SOC
Revolution : A Guide to Platform−Based Design, Kluwer Academic Publishers, ISBN 0−7923−
8679−5, November, 1999.
[COS99] P. Coste, et al, Multilanguage Systems Codesign, 7th
International Workshop on
Hardware/Software Codesign, May, 1999.l
[DAV82] A. L. Davis, R. M. Keller, Data Flow Program Graphs, Computer, February, 1982.
[DAV99] J. Davis II, et al, Ptolemy II: Heterogeneous Concurrent Modeling and Design in Java,
Report ERL No. M99/63, Dept. EECS, University of California, Berkeley, December, 1999.
[DEN80] J. B. Dennis, Data Flow Supercomputers, Computer, November, 1980.
[EDW94] S. Edwards, An Esterel Compiler for a Synchronous/Reactive Development System,
Report ERL No. M94/43, Dept,. EECS, University of California, Berkeley, December, 1994.
[EDW97] S. Edwards, The Specification and Execution of Synchronous Reactive Systems, PhD
Dissertation, Report ERL, No. M97/31, Dept. EECS, University of California, Berkeley, 1997.
[FDR] Ferramenta FDR2, www.formal.demon.co.uk/FDR2.html , Formal Systems.
92
[FRA00] D. T. Franco, L. Carro, Printed Text Translation System in FPGA, XV International
Conference on Microelectronics and Packaging (SBMicro), September, 2000.
[GAJ00] D. D. Gajski, J. Zhu, R. Dömer, A. Gerstlauer, S. Zhao, SPECC: Specification Language and
Methodology, Kluwer Academic Publishers, 2000.
[GIR99] A. Girault, B. Lee, E. A. Lee, Hierarchical Finite State Machines with Multiple
Concurrency Models, IEEE Transactions on Computer−Aided Design of Integrated Circuits and
Systems, Vol. 18., No. 6, June, 1999.
[GIU98] A. Giulietti, Projeto de um Par Codificador/Decodificador Convolucional Integrado,
Dissertação de Mestrado, Departamento de Engenharia Elétrica, Escola Politécnica, Universidade de São
Paulo, 1998.
[GIU00] A. Giulietti, et al., A Trade−off Study on Concatenated Channel Coding Techniques for
High Data Rate Satellite Communications, 2nd
International Symposium on Turbo Codes  Related
Topics (BREST), September 2000.
[GOE98] M. Goel, Process Networks in Ptolemy II, Report ERL M98/69, Dept. EECS, University of
California, Berkeley, December, 1998.
[GUP97] R. K. Gupta, S. Y. Liao, Using a Programming Language for Digital System Design, IEEE
Design  Test of Computers, April−June, 1997.
[HAL93] N. Halbwachs, Synchronous programming of reactive systems, Kluwer Academic
Publishers, ISBN 0−7923−9311−2, 1993.
[HAR87] D. Harel, StateCharts: A visual formalism for complex systems, Sci. Comput. Prog., vol 8,
pp. 231−274, 1987.
[HUD89] P. Hudak, Conception, Evolution, and Application of Functional Programming
Languages, ACM Computing Surveys, Vol. 21, No. 3, September 1989.
[HOA78] C. A. R. Hoare, Communicating Sequential Processes, Communications of the ACM, Vol.
21, No. 8, August, 1978.
[HOA85] C. A. R. Hoare, Communicating Sequential Processes, Prentice Hall International, ISBN 0−
13−153271−5, 1985.
[KAH74] G. Kahn, The Semantics of a Simple Language for Parallel Programming, Information
Processing, 1974.
[KAH77] G. Kahn, D. B. MacQueen, Coroutines and Networks of Parallel Processes, Information
Processing, 1977.
[KAR66] R. M. Karp, R. E. Miller, Properties of a Model for Parallel Computations: Determinacy,
Termination, Queueing, SIAM J. Appl. Math, Vol. 14, No. 6, November, 1966.
[LAV99] L. Lavagno, E. Sentovich, ECL: A Specification Environment for System−Level Design,
Proceedings of the ACM Design Automation Conference, 1999.
[LAV00] L. Lavagno, A. Sangiovanni−Vincentelli, E. Sentovich, Models of Computation for System
Design, em: Egon Boerger (ed.), Architecture Design and Validation Methods, Springer−Verlag, January
2000, pp. 243−296.
93
[LEE87a] E. A. Lee, D. G. Messerschmitt, Static Scheduling of Synchronous Data Flow Programs of
Digital Signal Processing, IEEE Transactions on Computer, Vol. C−36, No. 1, January, 1997.
[LEE87b] E. A. Lee, D. G. Messerschmitt, Synchronous Data Flow, Proceedings of the IEEE, Vol. 75,
No. 9, September, 1987.
[LEE95] E. A. Lee, T. M. Parks, Dataflow Process Networks, Proceedings of the IEEE, Vol 83., No. 5,
May, 1995.
[LEE98] E. A. Lee, A. Sangiovanni−Vincentelli, A Framework for Comparing Models of
Computation, IEEE Transactions on Computer−Aided Design of Integrated Circuits and Systems, Vol.
17, No. 12, December, 1998.
[LIU98] J. Liu, Continuous Time and Mixed−Signal Simulaiton in Ptolemy II, Report ERL M98/74,
Dept. EECS, University of California, Berkeley, December, 1998.
[MAR98] J. Martin, et al, Modeling Reactive Systems in Java, ACM Transaction on Design
Automation of Electronic Systems, Vol 3, No. 4, October 1998.
[MIC94] G. De Micheli, Synthesis and Optimization of Digital Circuits, McGraw−Hill International
Editions, ISBN 0−07−016333−2, 1994.
[MUL99] L. Muliadi, Discrete Event Modeling in Ptolemy II, MS Report, Dept. EECS, University of
California, Berkeley, May, 1999.
[PAN92] P. Panagaden, V. Shanbhogue, The expressive power of indeterminate dataflow primitives,
Inf. And Computation, Vol. 98, No. 1, May, 1992.
[PAR95] T. M. Parks, Bounded Scheduling of Process Networks, PhD Dissertation, Dept. EECS,
University of California, Berkeley, December, 1995.
[PET81] J. L. Peterson, Petri Net Theory and the Modeling of Systems, Pretince−Hall, ISBN 0−13−
661983−5, 1981.
[REE95] H. J. Reekie, Realtime Signal Processing, PhD Dissertation, School of Electrical Engineering,
University of Technology at Sydney, September, 1995.
[SMY98] N. Smyth, Communicating Sequential Processes Domain in Ptolemy II, Report ERL
M98/70, Dept. EECS, University of California, Berkeley, December, 1998.
[SYSC] System C User’s Guide, Version 1.0. http://www.systemc.org.

Mais conteúdo relacionado

PDF
Rastreamento
PDF
Monografia - Luiz Fernando Cruz v5
PDF
2010 ms thesis_almeida
PDF
Programacao Orientada A Objetos (Java)
PDF
Modelagem de Base de Conhecimentos Baseada em Ontologia Estudo de Caso em Rec...
PDF
O fantc3a1stico-mundo-da-linguagem-c
PDF
PDF
Monografia ifes-everton-bada
Rastreamento
Monografia - Luiz Fernando Cruz v5
2010 ms thesis_almeida
Programacao Orientada A Objetos (Java)
Modelagem de Base de Conhecimentos Baseada em Ontologia Estudo de Caso em Rec...
O fantc3a1stico-mundo-da-linguagem-c
Monografia ifes-everton-bada

Semelhante a Study about Models of Computatoin for describing Digital Systems (20)

PDF
Estrutura de dados 3
PDF
Introdução a estrutura de dados
PDF
Monografia - Engenharia de software baseada em modelos um estudo sobre WebML ...
PDF
Manual getic 23-out_09
PDF
Tese marinho
PDF
Apostila de pspice petee ufmg
PDF
PDF
monografia_andre_paro
PDF
ANTONIO INACIO FERRAZ-ESTUDANTE DE FARMÁCIA EM CAMPINAS SP.
PDF
antonio inacio ferraz-Eletronicadigital-técnico em eletronica-colégio cruzeir...
PDF
Eletronica digital
PDF
Programação Orientada a Objetos com Java
DOC
Estudo de caso: Windows NT
PDF
Tcc aop-e-persistencia
PDF
sistemas_operacionais-livro.pdf
PDF
Eletronica digital fet potencia[1]
PDF
Treino redes2003
PDF
Manual magalhaes
PDF
Toturial autocad2009(1)
Estrutura de dados 3
Introdução a estrutura de dados
Monografia - Engenharia de software baseada em modelos um estudo sobre WebML ...
Manual getic 23-out_09
Tese marinho
Apostila de pspice petee ufmg
monografia_andre_paro
ANTONIO INACIO FERRAZ-ESTUDANTE DE FARMÁCIA EM CAMPINAS SP.
antonio inacio ferraz-Eletronicadigital-técnico em eletronica-colégio cruzeir...
Eletronica digital
Programação Orientada a Objetos com Java
Estudo de caso: Windows NT
Tcc aop-e-persistencia
sistemas_operacionais-livro.pdf
Eletronica digital fet potencia[1]
Treino redes2003
Manual magalhaes
Toturial autocad2009(1)
Anúncio

Mais de ijeukens (6)

PDF
Capturing Monotonic Components from Input Patterns
PDF
On the Choice of Models of Computation for Writing Executable Specificatoins ...
PDF
Pattern-based Definition and Generation of Components for a Synchronous React...
PDF
Exploring Models of Computation through Static Analysis
PDF
A Methodology for Embedded software design based on Models of Computation
PDF
Comparison between heuristic algorithms for graph otimization
Capturing Monotonic Components from Input Patterns
On the Choice of Models of Computation for Writing Executable Specificatoins ...
Pattern-based Definition and Generation of Components for a Synchronous React...
Exploring Models of Computation through Static Analysis
A Methodology for Embedded software design based on Models of Computation
Comparison between heuristic algorithms for graph otimization
Anúncio

Último (20)

PPTX
NR35 - Treinamento Aurea Medic - altura.pptx
PPTX
(7) NR10 - SEP - Liberação de instalação para serviços.pptx
PDF
Técnicas Digitais Sistema de Numeração
PPTX
Aplicacion de model de markowitzz de optimizaion
PPT
Primeiros Socorros e Saúde Ocupacional Ferrosos Sul.ppt
PDF
Poluição sonora xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
PPTX
1_Integração DE SEGURANÇA TRABALHO.2.pptx
DOC
PPRA contru+º+úo civil 3. Construção civil
PPTX
ENGENHARIA DE GESTÃO LOGÍSTICA E DOS TRANSPORTES.pptx
PPT
Aula-Completação de poços de petroleo e gas
PDF
MATERIAIS DE CONSTRUÇÃO Solo cimento - 07.pdf
PPT
1 - Serviços em Eletricidade - 1° SOS RCP DEA - Rev a.ppt
PDF
Aspectos Tecnicos e Legais da Insalubridade.pdf
PPTX
cultivo de folhosas alface rúcula almeirão.pptx
PPT
Drenagem_Mapas_freaticos-7d8e2d1eee0040649b4e15eaa9d0c8c6.ppt
PPTX
Reciclagem do Munck.pptxdddddddddddddddd
PDF
Aula 7 - Choque Eletrico e Queimaduras.pdf.pdf
PPTX
Slide_Atualizações dos Protocolos de BLS e ACLS.pptx
PPT
aula 1 biologia celular 2025.2 introdução.ppt
PDF
aula 5 - Curvas horizontais circulares.pdf
NR35 - Treinamento Aurea Medic - altura.pptx
(7) NR10 - SEP - Liberação de instalação para serviços.pptx
Técnicas Digitais Sistema de Numeração
Aplicacion de model de markowitzz de optimizaion
Primeiros Socorros e Saúde Ocupacional Ferrosos Sul.ppt
Poluição sonora xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
1_Integração DE SEGURANÇA TRABALHO.2.pptx
PPRA contru+º+úo civil 3. Construção civil
ENGENHARIA DE GESTÃO LOGÍSTICA E DOS TRANSPORTES.pptx
Aula-Completação de poços de petroleo e gas
MATERIAIS DE CONSTRUÇÃO Solo cimento - 07.pdf
1 - Serviços em Eletricidade - 1° SOS RCP DEA - Rev a.ppt
Aspectos Tecnicos e Legais da Insalubridade.pdf
cultivo de folhosas alface rúcula almeirão.pptx
Drenagem_Mapas_freaticos-7d8e2d1eee0040649b4e15eaa9d0c8c6.ppt
Reciclagem do Munck.pptxdddddddddddddddd
Aula 7 - Choque Eletrico e Queimaduras.pdf.pdf
Slide_Atualizações dos Protocolos de BLS e ACLS.pptx
aula 1 biologia celular 2025.2 introdução.ppt
aula 5 - Curvas horizontais circulares.pdf

Study about Models of Computatoin for describing Digital Systems

  • 1. Ivan Jeukens Um Estudo sobre a Utilização de Modelos Computacionais para a Representação de Sistemas Digitais Dissertação apresentada à Escola Politécnica da Universidade de São Paulo para obtenção do título de Mestre em Engenharia Elétrica. São Paulo 2000
  • 2. Ivan Jeukens Um Estudo sobre a Utilização de Modelos Computacionais para a Representação de Sistemas Digitais Dissertação apresentada à Escola Politécnica da Universidade de São Paulo para obtenção do título de Mestre em Engenharia Elétrica. Área de Concentração: Microeletrônica Orientador: Prof. Dr. Marius Strum São Paulo 2000
  • 3. Jeukens, Ivan Um Estudo sobre a Utilização de Modelos Computacionais para a representação de Sistemas Digitais São Paulo, S. P., Brasil, 2000. p. 108 Dissertação de Mestrado. Escola Politécnica da Universidade de São Paulo. Departamento de Engenharia Elétrica. 1. Sistemas Digitais 2. Modelos computacionais 3. Especificações Executáveis 4. Metodologias. I. Universidade de São Paulo. Escola Politécnica. Departamento de Engenharia Elétrica II. t.
  • 5. Agradecimentos Ao meu orientador Prof. Marius Strum pelas inúmeras discussões que acabaram por moldar e assegurar a qualidade desse trabalho. Aos membros da banca de defesa pela paciência de ler esse texto. Aos colegas da sala C2−53 Alexandre Giulietti, Oscar Guilarte, Alfredo Coelho e João Paulo. Ao grupo de desenvolvimento do ambiente Ptolemy por disponibilisar um software de alta qualidade. A CAPES pelo financiamento desse trabalho através de uma bolsa de mestrado.
  • 6. SUMÁRIO Lista de tabelas Lista de figuras Resumo ¨Abstract¨ 1 Introdução ............................................................................................................1 1.1 Considerações Iniciais ..............................................................................1 1.2 Trabalhos Relacionados ...........................................................................2 1.3 Estrutura do Texto ...................................................................................3 2 Fundamentos ........................................................................................................4 2.1 Introdução ................................................................................................4 2.2 O Ambiente Ptolemy ................................................................................4 2.2.1 A implementação do Ambiente Ptolemy .............................................4 2.2.1.1 O Pacote Kernel: Sintaxe da Especificação ...................................5 2.2.1.2 O Pacote Actor: Suporte à Semântica ............................................6 2.2.1.3 Outras Classes Importantes ...........................................................8 2.2.2 Utilizando o Ambiente Ptolemy ..........................................................9 2.3 Os Modelos Computacionais ..................................................................11 2.3.1 Discrete Event ...................................................................................11 2.3.2 Synchronous Reactive .......................................................................13 2.3.3 Communicating Sequential Processes ...............................................17 2.3.4 Synchronous Data Flow ....................................................................19 2.3.5 Dynamic Data Flow ..........................................................................21 2.3.6 Kahn Process Networks ....................................................................24 2.4 Outros Modelos Computacionais ............................................................24 2.5 A Ferramenta de Depuração/Profile .......................................................26 2.5.1 Dados Coletados ...............................................................................26 2.5.2 A Interface Gráfica ............................................................................27 3 Primitivas Comportamentais .............................................................................32 3.1 Introdução ..............................................................................................32 3.2 Seqüência de Expressões ........................................................................32
  • 7. 3.2.1 Exemplo de Sistema ..........................................................................32 3.2.2 Especificação Executável ..................................................................33 3.2.3 Análise da Execução .........................................................................34 3.2.3.1 SDF .............................................................................................34 3.2.3.2 DDF ............................................................................................34 3.2.3.3 PN ...............................................................................................34 3.2.3.4 DE ..............................................................................................35 3.2.3.5 CSP .............................................................................................35 3.2.3.6 SR ...............................................................................................35 3.3 Desvio Condicional ..........................................................................................35 3.3.1 Exemplo de Sistema ..........................................................................35 3.3.2 Especificação Executável ..................................................................36 3.3.2.1 SDF .............................................................................................36 3.3.2.2 DDF ............................................................................................38 3.3.2.3 PN ...............................................................................................39 3.3.2.4 CSP .............................................................................................39 3.3.2.5 DE ..............................................................................................40 3.3.2.6 SR ...............................................................................................41 3.3.3 Análise da Execução .........................................................................42 3.3.3.1 SDF .............................................................................................42 3.3.3.2 DDF ............................................................................................42 3.3.3.3 PN ...............................................................................................43 3.3.3.4 CSP .............................................................................................43 3.3.3.5 DE ..............................................................................................43 3.3.3.6 SR ...............................................................................................43 3.4 Iteração com Duração Fixa .....................................................................44 3.4.1 Exemplo de Sistema ..........................................................................44 3.4.2 Especificação Executável ..................................................................44 3.4.2.1 SDF .............................................................................................45 3.4.2.2 DDF ............................................................................................46 3.4.2.3 PN ...............................................................................................46 3.4.2.5 CSP .............................................................................................47 3.4.2.4 DE ..............................................................................................47 3.4.2.6 SR ...............................................................................................48 3.4.3 Análise da Execução .........................................................................48 3.4.3.1 SDF .............................................................................................48 3.4.3.2 DDF ............................................................................................48 3.4.3.3 PN ...............................................................................................49 3.4.3.4 CSP .............................................................................................50 3.4.3.5 DE ..............................................................................................50
  • 8. 3.4.3.6 SR ...............................................................................................50 3.5 Outras Primitivas Comportamentais .......................................................51 3.5.1 Sincronismo ......................................................................................51 3.5.2 Compartilhamento de Recursos .........................................................51 3.5.3 Concorrência .....................................................................................52 3.5.4 Preempção ........................................................................................52 3.5.5 Recursão ...........................................................................................53 3.6 Discussão ...............................................................................................53 4 Estudo de Caso ...................................................................................................56 4.1 Introdução ..............................................................................................56 4.2 O Modem ADSL ....................................................................................56 4.2.1 Introdução .........................................................................................56 4.2.2 Framming .........................................................................................57 4.2.3 Código de Redundância Cíclica .........................................................59 4.2.4 Embaralhamento ...............................................................................60 4.2.5 Correção de Erros .............................................................................60 4.2.6 Interleaver ........................................................................................60 4.2.7 Ordenamento de Tons .......................................................................60 4.2.8 Codificação de Constelações .............................................................61 4.2.9 Modulação ........................................................................................61 4.2.10 A inicialização ................................................................................61 4.3 As Especificações Executáveis ...............................................................63 4.3.1 O sistema ..........................................................................................63 4.3.2 Framming .........................................................................................65 4.3.3 A especificação dos modems ATU−C e ATU−R ...............................66 4.3.3.1 MuxSyncTx ..................................................................................67 4.3.3.2 MuxSyncRx .................................................................................69 4.3.3.3 CRCTx ........................................................................................69 4.3.3.4 CRCRx ........................................................................................70 4.3.3.5 ScramblerTx ................................................................................71 4.3.3.6 ScramblerRx ...............................................................................72 4.3.3.7 FECTx ........................................................................................72 4.3.3.8 FECRx ........................................................................................73 4.3.3.9 InterleaverTx ...............................................................................75 4.3.3.10 InterleaverRx ............................................................................75 4.3.3.11 Map ...........................................................................................76 4.3.3.12 Demap .......................................................................................79
  • 9. 4.3.3.13 IDFT .........................................................................................80 4.3.3.14 DFT ..........................................................................................81 4.3.3.15 Initializer ..................................................................................82 4.4 Discussão ...............................................................................................87 5 Conclusões e Trabalhos Futuros ........................................................................89 5.1 Considerações Finais ..............................................................................89 5.2 Trabalhos Futuros ..................................................................................89 Referências Bibliográficas .....................................................................................91
  • 10. LISTA DE TABELAS Tabela 3.1 Comparação dos escalonadores do MoC DE ..........................................50
  • 11. LISTA DE FIGURAS Figura 2.1 Diagrama estático de classes simplificado dos pacotes Actor e Kernel.......5 Figura 2.2 Uma topologia não hierárquica .................................................................5 Figura 2.3 Exemplo de topologia hierárquica ............................................................6 Figura 2.4 Exemplo de troca de dados .......................................................................6 Figura 2.5 Exemplo de topologia para ilustrar o uso da classe Director .....................7 Figura 2.6 Type lattice do ambiente Ptolemy .............................................................8 Figura 2.7 Um exemplo genérico de ator atômico ......................................................9 Figura 2.8 Exemplo de arquivo principal de uma especificação executável ..............10 Figura 2.9 Situação de eventos simultâneos no MoC DE .........................................11 Figura 2.10 O ator Delay no MoC DE ....................................................................12 Figura 2.11 Ilustrações das definições do MoC SR ..................................................16 Figura 2.12 Ator de atraso no MoC SR ....................................................................17 Figura 2.13 O comando CDO no ambiente Ptolemy ................................................18 Figura 2.14 Dois grafos SDF ...................................................................................19 Figura 2.15 Dois grafos SDF inválidos mas com matrizes consistentes ....................20 Figura 2.16 O ator Delay no MoC SDF ...................................................................21 Figura 2.17 Os atores Switch e Select no modelo DDF ............................................22 Figura 2.18 Código do ator Select no MoC DDF .....................................................23 Figura 2.19 A janela inicial da ferramenta PTII Analyzer ........................................27 Figura 2.20 A janela da topologia ............................................................................28 Figura 2.21 Janela inicial para o profiling ................................................................29 Figura 2.22 Tabela de dados estatísticos de atores ...................................................29 Figura 2.23 A tabela de dados dos portos .................................................................30 Figura 2.24 Gráfico da evolução do estado de um ator .............................................31 Figura 2.25 Gráfico alternativo da evolução dos estados de um ator ........................31 Figura 2.26 Exemplo de gráfico da evolução da quantidade de tokens em uma conexão .......................................................................................................31 Figura 3.1 Descrição em linguagem C do cálculo dos pontos da curva borboleta .....33 Figura 3.2 A topologia da especificação executável para o exemplo curva borboleta.............................................................................................33 Figura 3.3 Situação da execução em um momento inicial utilizando o escalonador com classificação de ator deferrable .............................................................34 Figura 3.4 Situação da execução em um momento inicial utilizando o escalonador
  • 12. sem classificação de ator deferrable .............................................................34 Figura 3.5 Descrição em linguagem C do cálculo do coeficiente angular em uma reta .................................................................................................36 Figura 3.6 Topologia da primeira especificação em SDF .........................................36 Figura 3.7 Trecho do código do ator Mux ................................................................37 Figura 3.8 Topologia da segunda especificação utilizando o MoC SDF ...................37 Figura 3.9 Interface para uma função do exemplo do coeficiente angular ................37 Figura 3.10 Topologia da primeira especificação utilizando o modelo DDF ............38 Figura 3.11 Topologia da segunda especificação utilizando o MoC DDF ................39 Figura 3.12 Trecho de código do ator MuxND no MoC CSP ...................................40 Figura 3.13 Topologia da especificação no MoC DE ...............................................40 Figura 3.14 Trecho de código do ator Coef no MoC DE ..........................................41 Figura 3.15 Método fire() do ator MuxND no MoC SR ............................................41 Figura 3.16 Exemplo da evolução do estado do ator Comp2 para a especificação da figura 3.10 no MoC DDF .............................................................................42 Figura 3.17 Exemplo da evolução do estado do ator Comp2 para a especificação da figura 3.11 no MoC DDF .............................................................................42 Figura 3.18 Exemplo da evolução do estado do ator Comp2 para a especificação da figura 3.11 no MoC PN ................................................................................43 Figura 3.19 Multiplicação de duas matrizes de ordem N ..........................................44 Figura 3.20 A primeira especificação para a primitiva de iteração fixa ....................44 Figura 3.21 Implementação alternativa para a geração dos índices da iteração .........45 Figura 3.22 A iteração com duração fixa no MoC SDF ...........................................45 Figura 3.23 Trecho de código dos atores CT e Data ................................................46 Figura 3.24 Código dos atores Data e CT no MoC PN ............................................47 Figura 3.25 Trecho do ator Sequencer no MoC PN ..................................................47 Figura 3.26 Trecho do ator Sequencer no MoC DE .................................................47 Figura 3.27 Evolução do estado dos atores Data (em azul) e Accum (em verde) ......48 Figura 3.28 Evolução do estado dos atores Data e Accum com a alteração do parâmetro do método waitFor() ...................................................................49 Figura 3.29 Evolução do estado do ator Data quando o tamanho inicial das filas é 1 ..................................................................................................49 Figura 3.30 Evolução do estado do ator Data quando o tamanho inicial das filas é 8 ..................................................................................................49 Figura 3.31 Discrete Cosine Transform : exemplo para a primitiva de sincronismo .51
  • 13. Figura 3.32 Um exemplo para a primitiva de preempção .........................................52 Figura 3.33 Máquina de Mealy para o código da figura 3.32 ...................................53 Figura 4.1 Modelo de referência de sistemas ADSL ................................................56 Figura 4.2 Diagrama funcional do transmissor do modem ATU−C ..........................57 Figura 4.3 A estrutura de frame e superframe do modem ADSL ..............................58 Figura 4.4 A estrutura de um frame de dados do buffer fast .....................................58 Figura 4.5 A estrutura de um frame de dados do buffer interleaved .........................59 Figura 4.6 Constelação quando o comprimento de b for igual à 3 ............................61 Figura 4.7 Ativação e Acknowledgment ...................................................................62 Figura 4.8 Topologia da especificação do sistema ...................................................63 Figura 4.9 Trecho de código do ator BroadBandNet no MoC DE ............................64 Figura 4.10 Trecho de código da classe MuxDataFrame .........................................65 Figura 4.11 Trecho de código da classe FastFECDataFrame ..................................66 Figura 4.12 A topologia da especificação para o modem ATU−C ............................66 Figura 4.13 Trecho de código do ator MuxSyncTx no MoC DE ...............................68 Figura 4.14 Trecho de código do ator CRCTx ..........................................................70 Figura 4.15 Trecho de código do ator CRCRx ..........................................................71 Figura 4.16 Trecho de código do ator ScramblerTx .................................................72 Figura 4.17 Trecho de código do ator FECTx ..........................................................73 Figura 4.18 Trecho de código do ator FECRx ..........................................................74 Figura 4.19 Trecho de código do ator InterleaverTx ................................................75 Figura 4.20 Trecho de código do ator InterleaverRx ................................................76 Figura 4.21 Trecho de código do ator Map ..............................................................78 Figura 4.22 Trecho de código do ator Demap ..........................................................80 Figura 4.23 Trecho de código do ator IDFT no MoC DE .........................................81 Figura 4.24 Trecho de código do ator DFT no MoC DE ..........................................82 Figura 4.25 Classe auxiliar com os símbolos de inicialização ..................................82 Figura 4.26 Método fire() do ator Timer ..................................................................83 Figura 4.27 Método _getSymbol() do ator Initializer ...............................................84 Figura 4.28 Método _sendSymbol() do ator Initializer .............................................84 Figura 4.29 Método _detectSignal() do ator Initializer ............................................85 Figura 4.30 Método _transmitSignal() do ator Initializer .........................................86 Figura 4.31 Método _fire() do ator Initializer ..........................................................87
  • 14. RESUMO Neste trabalho abordamos o problema da captura e validação da especificação funcional de um sistema digital através de especificações executáveis. Nos concentramos na comparação de modelos computacionais, isto é, o suporte semântico de uma linguagem de descrição de sistemas. O ambiente Ptolemy II foi escolhido para o desenvolvimento deste trabalho, uma vez que ele possui uma infra−estrutura para a implementação de modelos computacionais e criação de especificações executáveis. Comparamos seis modelos computacionais. Fundamentamos nossa metodologia na analise de comportamentos primitivos presentes em sistemas reais. Três comportamentos foram estudados: seqüência de expressões, desvio condicional e iteração com duração fixa. Uma ferramenta de software foi desenvolvida para auxiliar na comparação dos diferentes modelos computacionais através da reprodução da execução de uma especificação. Foi possível observar que o comportamento de seqüência de expressões foi capturado eficientemente pelos seis modelos computacionais. Os comportamentos de desvio condicional e iteração com duração fixa apresentaram dificuldades para a captura com alguns modelos. Finalmente, desenvolvemos a especificação de um modem ADSL. Utilizamos o modelo computacional que se mostrou mais eficiente para a captura dos três comportamentos. Identificamos um novo comportamento primitivo a partir desse estudo.
  • 15. ABSTRACT In this dissertation we studied the problem of capture and validation of a functional specification of a digital system using executable specifications. We focused on comparing different models of computation, i.e., the semantic support of a system description language. We have chosen the Ptolemy II framework for the development of this work, since it has several models implemented and efficiently supports the addition of others. We compared six different models. We based our methodology on the analysis of primitive behaviors present in real systems. Three different behaviors we studied: sequence of expressions, conditional execution and fixed length iteration. We developed a software tool for helping the comparison of different models of computation by reproducing an execution. It was possible to efficiently capture the sequence of expressions with all six models. The other two primitive behaviors presented some difficulties under certain models. Finally, we developed the specification of an ADSL modem. An executable specification using the most efficient model for capturing the three behaviors was employed. We were able to identify a new primitive behavior from this case study.
  • 16. 1 Capítulo 1 Introdução 1.1 Considerações Iniciais O desenvolvimento das tecnologias de microeletrônica vem possibilitando a criação de circuitos integrados cada vez mais complexos. Conforme foi previsto pela lei de Moore [BRE98], a capacidade de integração dobra a cada um ou dois anos. Os microprocessadores de alto desempenho são um bom exemplo dessa evolução: já existem processadores com mais de 20 milhões de transistores operando a freqüências acima de 1 GHz. Também já é uma realidade o projeto de circuitos de aplicação específica (ASIC) com milhões de transistores. Essa abundância de recursos possibilita a implementação de sistemas cada vez mais complexos, que podem ser realizados utilizando vários componentes discretos ou em apenas uma única pastilha. Este último caso é denominado de System−on−Chip (SOC). Embora o desenvolvimento da tecnologia de circuitos integrados seja fundamental para a implementação de sistemas cada vez mais versáteis e completos, ele também traz problemas. A principal dificuldade é lidar com a complexidade do projeto. Por exemplo, foi avaliado que durante o período de 20 anos, o número de transistores de um CI aumentou à uma taxa de 58% ao ano, enquanto que no mesmo período, a produtividade de um projetista, medida em número de transistores projetados por dia, aumentou à uma taxa de 21% [GAJ00]. Isso significa que não mais a capacidade de manufatura é o gargalo, mas sim o desenvolvimento do CI, ou seja, as ferramentas e metodologias CAD. A solução que a comunidade científica e industrial considera como sendo a mais promissora é elevar o nível de abstração na qual o projetista trabalha, pois assim a quantidade de detalhes é reduzida. Entretanto, um nível de abstração mais alto significa que linguagens de especificação, ferramentas de projeto, metodologias, etc, devem ser adequadas. Essa tarefa é bastante complexa pois envolve a mudança para um novo paradigma de projeto. Em [GAJ00], Gajski et al. identificam três metodologias sendo empregadas atualmente para o desenvolvimento de sistemas em alto nível: projeto baseado em plataformas (platform based design), integração de propriedade intelectual (IP Assembly), e síntese a partir de especificações (Synthesis from Specifications). Essas três soluções diferem em custo, esforço de projeto, flexibilidade e qualidade. O projeto baseado em plataforma [CHA99] fundamenta−se no uso de uma arquitetura pré− definida, otimizada para um tipo de aplicação. A metodologia empregada deve permitir a criação de uma especificação do sistema, para então mapeá−la na arquitetura. O projeto utilizando propriedade intelectual (IP) é baseado na reutilização de blocos pré− projetados e pré−caracterizados. A metodologia incorpora as tarefas de seleção de componentes e criação da arquitetura (envolve a interface entre blocos), além das tarefas presentes no projeto baseado em plataforma. O desafio para a solução utilizando blocos IPs é a criação de uma base de dados de componentes, além de formatos de troca de dados. Já existem esforços da comunidade de ferramentas CAD para atacar esses problemas [SYSC]. A síntese a partir de especificações significa implementar as diferentes funções da especificação diretamente em hardware e software. Essa solução é a mais flexível das três. A metodologia para projeto é bastante similar às outras duas soluções, com o problema adicional de particionar a especificação e criar métodos eficientes para explorar as inúmeras soluções arquiteturais. Embora existam três diferentes soluções sendo estudadas, todas elas requerem a criação de uma especificação executável para capturar o sistema nos diferentes níveis de abstração, desde sua funcionalidade até a implementação em uma arquitetura específica [GAJ00]. Conjuntamente com uma especificação para o ambiente no qual o sistema será implantado, é possível analisar o comportamento do sistema sendo desenvolvido através dos resultados da execução, tal qual em uma simulação tradicional. Esse tipo de técnica já vem sendo empregada na industria para o projeto de ASICs [SYSC], onde a linguagem de programação C é utilizada para capturar o algoritmo a ser implementado. Só após extensas análises e otimizações é que tal descrição em linguagem C é refinada para uma descrição em linguagem VHDL (comportamental/RTL).
  • 17. 2 Toda linguagem de programação ou de descrição de hardware possui uma definição sintática e semântica. A semântica da linguagem define o significado de cada comando, ou seja, como aquele comando produz alguma alteração no estado do sistema ou ambiente e como ele está relacionado à outros comandos. A semântica de uma linguagem é definida com base em um modelo computacional (MoC), ou seja , um conjunto de definições e regras que possibilitam a representação formal e não ambígua de um algoritmo. Um exemplo clássico de modelo computacional é a máquina RAM [AHO74] associada à programas seqüenciais. O item 3.2 deste texto apresenta uma lista de alguns modelos computacionais. A escolha de um, ou um conjunto, de MoCs para o desenvolvimento de uma especificação executável depende de fatores como o domínio de aplicação, características semânticas do modelo e interação com outros modelos computacionais. Essa escolha irá afetar as tarefas de captura/validação do comportamento do sistema bem como posterior tarefa de sintetizar sua arquitetura. Nesse trabalho, iremos estudar a eficiência de alguns MoCs para capturar e validar a funcionalidade de um sistema. A motivação para nosso trabalho é: 1. o fato do projeto de sistemas eletrônicos através de metodologias top−down partirem da captura de sua especificação através de uma linguagem SLDL; 2. linguagens SLDL são sempre baseadas em um ou um conjunto de modelos computacionais o que pode limitar a possibilidade de capturar as especificações; 3. na existência de uma grande variedade de modelos computacionais. Portanto, é necessário que exista algum auxílio para o projetista, na forma de ferramentas, critérios, metodologias, tal que este possa identificar os modelos computacionais mais adequados para capturar as especificações de sua aplicação. 1.2 Trabalhos Relacionados O problema da escolha de um método de descrição para sistemas surgiu com o desenvolvimento das técnicas de co−projeto hardware/software (hardware/software codesign), pois uma única linguagem não era adequada para a captura do comportamento dos módulos em hardware e software. Isto ocorre devido as diferenças intrínsecas entre hardware e software. No nível sistêmico esse problema torna−se mais evidente, uma vez que é necessário capturar comportamentos heterogêneos como hardware analógico, hardware digital dominado pelo fluxo de dados, hardware digital dominado pelo fluxo de controle, dispositivos mecânicos, software com restrições temporais, etc. Três soluções foram estudadas: desenvolver uma nova linguagem, estender uma linguagem existente ou utilizar várias linguagens conjuntamente. Dentre inúmeros trabalhos que exemplificam tais soluções, podemos enumerar os seguintes:   [BEC92]: a linguagem de programação C e a linguagem de descrição de hardware Verilog são utilizadas conjuntamente para cosimular um sistema hardware/software;   [COS99]: inicialmente a linguagem SDL é utilizara para capturar a parte eletrônica de uma sistema mecatrônico e a linguagem Matlab para capturar o comportamento dos componentes mecânicos. Em seguida, o código em linguagem SDL é refinado para uma descrição utilizando linguagem C e VHDL;   [LAV99]: a semântica da linguagem C é estendida com comandos da linguagem Esterel. Esse trabalho concentra−se em sistemas dominados pelo fluxo de controle, mas que necessitam de uma linguagem eficiente para descrever as partes dominadas pelo fluxo de dados;   [MAR98]: a linguagem de programação Java foi estendida com algumas classes, tornando−a adequada para capturar sistemas reativos;   [REE95]: a linguagem Haskell é utilizada conjuntamente com um ambiente gráfico para capturar sistemas dominados pelo fluxo de controle;
  • 18. 3   [SYSC]: a linguagem C++ é estendida através de um conjunto de classes para permitir a descrição de circuitos síncronos;   [GAJ00]: uma nova linguagem foi desenvolvida visando a captura e refinamento de sistemas digitais;   [DAV99]: a linguagem Java é utilizada conjuntamente com um grupo de classes que permitem a implementação de diversos modelos computacionais. Nenhum dos trabalhos mencionados procura analisar quando cada linguagem ou característica semântica deve ser empregada. Em geral, uma classe específica de sistema é escolhida e a linguagem mais apropriada é empregada. O único trabalho que encontramos que aborda a comparação de modelos computacionais é [LEE98]. Entretanto, a comparação é feita de maneira descritiva e teórica. 1.3 Estrutura do Texto Esta dissertação apresenta os seguintes capítulos:   Capítulo 2: descreveremos os fundamentos teóricos e práticos necessários para o desenvolvimento desse trabalho. Descreveremos uma ferramenta de software que desenvolvemos;   Capítulo 3: apresentaremos nossa metodologia para o estudo do problema da escolha de modelos computacionais;   Capítulo 4: apresentaremos o estudo de caso de um sistema real que desenvolvemos para aplicar alguns dos resultados obtidos no capítulo 3;   Capítulo 5: contém o resumo final do trabalho e propostas para trabalhos futuros.
  • 19. 4 Capítulo 2 Fundamentos 2.1 Introdução Nesse capítulo faremos uma breve descrição dos fundamentos utilizados nesse trabalho e introduziremos termos utilizados ao longo dessa dissertação. Inicialmente, o ambiente Ptolemy será descrito. Seus componentes internos serão apresentados juntamente com um exemplo de como uma especificação executável é criada. Em seguida, descreveremos os modelos computacionais que estudamos e enumeraremos outros importantes modelos encontrados na literatura. Ao final do capítulo apresentaremos uma ferramenta que desenvolvemos para a análise da execução de especificações. 2.2 O Ambiente Ptolemy O ambiente Ptolemy II [DAV99] é a segunda geração de um projeto sendo desenvolvido na Universidade da Califórnia em Berkeley desde 1990. Inicialmente voltado para o desenvolvimento de sistemas (hardware e software) para processamento digital de sinais, atualmente seu principal objetivo é dar suporte ao desenvolvimento de sistemas embutidos (embedded systems), principalmente os componentes de software do sistema. O fundamento central do ambiente Ptolemy1 é a utilização conjunta de diferentes modelos computacionais para a captura das especificações do sistema. A heterogeneidade observada em sistemas embutidos é a justificativa para essa abordagem, isto é, um sistema embutido pode utilizar várias tecnologias distintas como hardware analógico, hardware digital, componentes ópticos, componentes mecânicos, software de tempo real, etc. Dessa forma, um único MoC não é eficiente para a captura do sistema. 2.2.1 A Implementação do Ambiente Ptolemy A linguagem de programação Java foi escolhida pelo grupo de desenvolvimento do ambiente Ptolemy. Essa também é a linguagem de programação utilizada para criar uma especificação executável, ou seja, um especificação no ambiente Ptolemy é um conjunto de classes criadas pelo usuário mais um conjunto de classes disponíveis no ambiente. A figura 2.12 apresenta resumidamente um diagrama estático de classes pertencentes aos dois pacotes centrais do ambiente: Kernel e Actor. Outros pacotes auxiliares também estão disponíveis, além de pacotes que implementam modelos computacionais. 1 Quando utilizamos o termo Ptolemy, estamos nos referindo à versão II do ambiente, e não a versão Classic (anterior). Caso seja necessário, faremos explicitamente a distinção entre as versões. 2 Por conveniência, as figuras mostradas no item 2.2 foram extraídas da documentação do ambiente Ptolemy[DAV99]. Para uma descrição completa do ambiente, utilize tal referência.
  • 20. 5 Figura 2.1 − Diagrama estático de classes simplificado dos pacotes Actor e Kernel. 2.2.1.1 O Pacote Kernel: Sintaxe da Especificação O pacote Kernel é composto por um conjunto de classes para implementar e manipular grafos com nós hierárquicos, também chamados de topologias. Uma topologia não hierárquica é constituída por entidades (Entity) e relações (Relation). A figura 2.2 apresenta um exemplo de topologia3 . Figura 2.2 − Uma topologia não hierárquica. Cada entidade pode possuir vários portos (Port) e uma relação pode conectar vários portos entre si. O uso de portos e relações torna uma topologia diferente de um grafo matemático. Uma relação é uma conexão entre múltiplos elementos. Já em grafos matemáticos, arcos são conexões entre dois elementos. Um grafo matemático poderia ser representado através de entidades designando os nós do grafos e cada uma possuindo exatamente um porto. No caso de um grafo direcionado, cada entidade possuiria dois portos, uma para arcos de entrada e outro para arcos de saída. 3 Ao contrário da figura 2.2, nas representações gráficas de topologias apresentadas nos capítulos seguintes, utilizaremos retângulos como representação gráfica para atores e setas direcionadas para representar conexões entre dois ou mais atores. O terminal com a seta indica o ator de destino e o outro terminal o ator de origem. Dessa forma, não iremos mostrar de forma explícita as relações. Nameable <<Interface>> <<Interface>> Executable <<Interface>> Actor NamedObj Entity ComponentEntity AtomicActor Manager Port Relation Workspace CompositeEntity CompositeActor Director ComponentPort ComponentRelation 0..n 0..1 0..n 1 0..n Container 0..n 0..nLink Link Container 0..1 0..n 0..1 0..1 1 0..2 Container 0..n 0..1 1 0..1 0..n Implementa Deriva Pertence ao pacote Kernel Pertence ao pacote Actor Attribute Entidade Porto Entidade Porto Entidade Porto Relação Link Link Link
  • 21. 6 Como mostrado na figura 2.1, cada uma das três principais classes do pacote Kernel possui uma classe derivada com o prefixo Component. A finalidade dessas classes é permitir a criação de topologias hierárquicas. Além dessas três classes, a classe CompositeEntity é utilizada para conter objetos do tipo ComponentEntity e ComponentRelation. Dessa forma, uma CompositeEntity contém uma sub−topologia. Entidades que não contenham sub−grafos são denominadas de atômicas. A figura 2.3 apresenta um exemplo de topologia hierárquica, onde E0 e E1 são entidades do tipo CompositeEntity. Figura 2.3 − Exemplo de topologia hierárquica. Os portos P3 e P4 da figura 2.3 são denominados de transparentes (transparent), enquanto que os demais são denominados de opacos (opaque). Esse conceito também se aplica a entidades, como será mostrado mais adiante. Um porto é opaco quando a entidade que o contém é opaca, e é transparente quando a entidade que o contém é transparente. Quando um porto for opaco, conexões com uma possível sub−topologia são invisíveis. Já um porto transparente permite que algoritmos percorram todas as conexões associadas a ele. Por exemplo, existe um método na classe ComponentPort denominado deepConnectedPortList(). Esse método retorna uma lista de todos os portos conectados, independentemente do nível de hierarquia. No caso do porto P1 da figura 2.3, esse procedimento retornaria os portos P2, P5 e P6. Portos transparentes não são retornados. Quando o procedimento atinge o porto P4, através da relação R2, o procedimento continua pesquisando, através das relações R3 e R4. 2.2.1.2 O Pacote Actor: Suporte à Semântica As classes do pacote Kernel definem uma topologia onde as entidades não possuem semântica. O pacote Actor é composto por um conjunto de classes para adicionar semântica à topologia e permitir a implementação de um modelo computacional específico, derivando−se das principais classes do pacote actor. Além das classes mostradas na figura 2.1, a classe IOPort e a interface Receiver também possuem papel importante. Duas características são necessárias para a implementação de uma semântica: troca de dados entre os atores da topologia e o controle da execução. Troca de Dados A figura 2.4 ilustra o processo básico de troca de dados. Figura 2.4 − Exemplo de troca de dados Dois portos são utilizados na figura 2.4: um de saída (P1) e um de entrada (P2). Eles são E0 E1 E2 E3 E4 E5 P1 P2 P3 P4 P5 P6 R1 R2 R3 R4 send(0, t0) send(1, t1) 2 2 2 get(0), get(1) token t0, t1 receiver.put(0) receiver.put(1) P1 P2
  • 22. 7 instâncias da classe IOPort. Essa classe disponibiliza vários métodos, entre eles o método send(), utilizado por um porto de saída para enviar dados aos portos conectados, e o método get(), utilizado por um porto de entrada para receber dados. No ambiente Ptolemy, os dados são transmitidos encapsulados em tokens. Uma vez criado, o valor de um token é imutável4 . A função do método send() é transferir os dados para um elemento denominado de receptor (receiver). Cada conexão com um porto de entrada possui associado um receptor. Na figura 2.4, temos duas conexões associadas. Cada conexão também é denominada de canal. Quando um porto possui mais de um canal, como na figura 2.4, deve−se especificar no método send() e get() por qual canal o token deve ser enviado ou recebido. Isso é feito através do primeiro argumento desses métodos. Uma vez que os dados estejam presentes no respectivo receptor, o ator de destino pode executar o método get() e obter os dados. No pacote Actor, o receptor não é uma classe concreta, mas sim uma interface. O receptor é implementado como parte do modelo computacional, pois diferentes modelos possuem semânticas distintas de comunicação entre elementos. Por exemplo, atores em um modelo data flow comunicam−se através de filas. As filas são implementadas em um receptor. Já no modelo Discrete Event, o receptor deve enviar os dados para uma fila global. Controle da Execução A classe Director do pacote Actor tem o principal papel no controle da execução. A implementação de um modelo computacional deve derivar essa classe a fim de implementar métodos como fire(), prefire() e postfire() definidos na interface Executable. Em um Director, esses métodos são responsáveis pelo controle da execução dos blocos do sistema. Para tal, é introduzido o conceito de iteração. A cada iteração da execução, o método prefire() é o primeiro a ser chamado, em seguida o método fire(), seguido pelo método postfire(). O comportamento exato de cada método é definido pela implementação de um modelo computacional. Em geral, o escalonador5 de um MoC é implementado nos métodos fire() e postfire() do respectivo Director. Um Director deve sempre estar associado a um único ator Composite. Essa associação torna o ator opaco. A utilização de um Director é ilustrada na figura 2.5. Figura 2.5 − Exemplo de topologia para ilustrar o uso da classe Director. O Director D1 está associado ao ator E0, que representa o nível mais alto da hierarquia (ator toplevel). Nesse nível, existem três atores: E1, E2, E3. O bloco E1 é um ator atômico. O bloco E3 é um ator hierárquico transparente, já que não existe nenhum Director associado à ele. Quem governa a execução da sub−topologia do ator E3 é o Director D1. Já o bloco E2 é um ator hierárquico opaco, pois o Director D2 está associado à ele. Para o Director D1, o bloco E2 é um ator atômico. Ao chamar o método fire() de E2, o controle é passado para o Director D2. Dessa forma, a ferramenta implementa um mecanismo transparente para a co−simulação de sistemas utilizando MoCs distintos. A figura também apresenta um Manager. Este deve ser único no sistema e associado ao ator toplevel. Sua função é iniciar, interromper e coordenar a execução de todo o sistema. 2.2.1.3 Outras Classes Importantes 4 Como será descrito adiante no texto, o tipo ObjectToken é uma exceção à essa regra. 5 Escalonador é um algoritmo utilizado para determinar uma ordem de execução dos atores. O escalonador pode ser seqüencial (apenas um ator executa a cada momento) ou paralelo (mais de um ator executa a cada momento). E0 D1: local director E1 P1 P2 P5 E4 P6 E2 D2: local director E5 P7P4P3 E3
  • 23. 8 Além dos pacotes descritos acima e os que implementam diversos modelos computacionais, o ambiente Ptolemy apresenta um conjunto de classes auxiliares, como o pacote Plot, uma aplicação para visualizar gráficos bidimensionais, pacotes de funções matemáticas e um pacote para implementação de um sistema de tipos de dados (type system). Tipos de Dados no Ambiente Ptolemy A classe Token e suas derivadas compõem o coração do pacote de dados do ambiente Ptolemy. Como mencionado anteriormente, todos os dados transmitidos entre atores são encapsulados em tokens. A classe Token apresenta apenas a definição de alguns métodos utilitários, como operações aritméticas. Entretanto, o ambiente possui um sistema de tipos com várias subclasses para diferentes tipos de dados. A figura 2.6 apresenta o type lattice do ambiente. Os tipos Numerical e Scalar são classes abstratas. Figura 2.6 − Type lattice do ambiente Ptolemy. Em geral, os portos de entrada/saída e os parâmetros6 de um ator podem ser criados com um tipo associado. Esse tipo pode ser especificado pelo usuário de forma exata, utilizando o método setTypeEquals(), ou de forma relativa. Neste caso, três métodos estão disponíveis: setTypeAtLeast(), especifica que o tipo do objeto em questão deve ser maior ou igual ao do parâmetro do método; setTypeAtMost(), indica que o tipo do objeto deve ser menor ou igual ao do parâmetro do método: setTypeSameAs(), indica que o tipo do objeto deve ser igual ao do parâmetro do método. No início da execução, o ambiente (mais precisamente, uma instância do objeto Manager) irá verificar se existe a consistência de tipos, ou seja, se um porto de entrada conectado à um porto de saída apresentam o mesmo tipo, ou se existe a possibilidade de conversão sem perdas (baseado no lattice da figura 2.6). Caso alguns tipos não estejam especificados ou foram especificados de forma relativa, haverá a resolução de tipos. 2.2.2 Utilizando o Ambiente Ptolemy 6 Parâmetro é um objeto que também pode armazenar um token, e dessa forma também é tipado. O parâmetro pode ser utilizado para especificar valores de configurações de um objeto, tornando−o mais flexível. General String Matrix Numerical Object BooleanMatrix Boolean FixMatrix Fix Scalar LongMatrix ComplexMatrix DoubleMatrix IntMatrix Array Long Complex Double Int NaT
  • 24. 9 O item 2.2.1 ilustrou de forma suscinta os principais componentes internos do ambiente Ptolemy. Utilizando essa infra−estrutura, podemos criar uma especificação executável. Para isso, é necessário criar os atores da especificação, interconectá−los e associar à especificação um ou mais modelos computacionais. A figura 2.7 apresenta o esqueleto básico de um ator atômico. 1− public class AtorExemplo extends TypedAtomicActor { 2− 3− public TypedIOPort entrada; 4− public TypedIOPort saida; 5− 6− public Parameter par; 7− 8− public AtorExemplo(TypedAtomicActor container, String name) 9− throws NameDuplicationException, IllegalActionException { 10− 11− super(container, name); 12− 13− entrada = new TypedIOPort(this, ¨entrada¨, true, false); 14− entrada.setTypeEquals(BaseType.INT); 15− 16− saida = new TypedIOPort(this, ¨saida¨, false, true); 17− saida.setTypeEquals(BaseType.DOUBLE); 18− 19− par = new Parameter(this, ¨parametro1¨, new IntToken(1)); 20− 21− } 22− 23− public void initialize() throws IllegalActionException { 24− 25− IntToken it = (IntToken) par.getToken(); 26− _var = it.intValue(); 27− 28− } 29− 30− public boolean prefire() throws IllegalActionException { 31− 32− .... 33− return super.prefire(); 34− } 35− 36− public void fire() throws IllegalActionException { 37− 38− IntToken it = (IntToken) entrada.get(0); 39− 40− saida.broadcast(DoubleToken.convert(it)); 41− } 42− 43− public boolean postfire() throws IllegalActionException { 44− 45− .... 46− return super.postfire(); 47− } 48− 49− public void wrapup() throws IllegalActionException { 50− 51− super.wrapup(); 52− 53− .... 54− } 55− 56− private int _var; 57− } Figura 2.7 − Um exemplo genérico de ator atômico. Na linha 1, o ator é criado derivando a classe TypedAtomicActor, ou seja, é um ator atômico com portos tipados. Em certos modelos computacionais, novas classes de atores atômicos são criadas estendendo a classe TypedAtomicActor. Da linha 3 à linha 6, dois portos e um parâmetro são declarados como variáveis públicas. Da linha 8 à linha 21 está implementado o construtor da classe. Duas exceções implementadas pelo ambiente Ptolemy são declaradas na linha 9. Na linha 13 o porto de entrada (o terceiro parâmetro do construtor é true) é criado . Em seguida, o tipo do porto é determinado como sendo um inteiro. O porto de saída (neste caso, o quarto parâmetro do construtor deve ser true) é criado de maneira similar nas linhas 16 e 17. Na linha 19, o parâmetro do ator é criado e inicializado como tendo tipo inteiro com valor inicial igual à 1 (terceiro parâmetro).
  • 25. 10 Observando a figura 2.1, note que a classe AtomicActor (pai da classe TypedAtomicActor) implementa a interface Executable. Nessa interface estão declarados, entre outros, os métodos initialize(), prefire(), postfire(), fire() e wrapup(). Conforme mencionado no item 2.2.1.2, esses métodos são usados pela classe Director e suas derivadas para o controle da execução. No caso de um ator atômico, esses métodos são definidos pelo usuário para implementar o comportamento desejado. A função de cada método é:   initialize() (linha 23 à 28): chamado uma única vez durante a execução, antes de todos os outros métodos. No exemplo da figura 2.7, esse método é utilizado para obter e armazenar o valor do parâmetro do ator em uma variável interna7 (linhas 25 e 26);   prefire() (linha 30 à 34): primeiro a ser chamado, uma única vez, durante uma iteração da execução. Caso um valor false seja retornado, isso indica que o ator não está apto para ser executado;   fire() (linha 36 à 41): pode ser chamado várias vezes em uma mesma iteração, após o método prefire(). Na linha 38, um token é lido do porto de entrada, utilizando o método get(). Na linha 40, o valor do token é convertido de inteiro para real, e enviado utilizando o método broadcast(). Esse método envia o dado para todos os portos de entrada conectados à ele;   postfire() (linha 43 à 47): similar ao método prefire(). Ele é o último método a ser chamado em uma iteração;   wrapup() (linha 49 à 54): chamado uma única vez ao final da execução. Uma vez que todos os atores de uma especificação são criados, é necessário interconectá−los e criar os objetos que irão controlar a execução. A figura 2.8 apresenta o esqueleto básico de um trecho de código para tal função. 1− public class Sistema { 2− 3− public static void main(String args[]) throws 4− IllegalStateException, IllegalActionException, 5− NameDuplicationException { 6− 7− TypedCompositeActor toplevel = new TypedCompositeActor(); 8− toplevel.setName("teste1"); 9− 10− Manager exec = new Manager("exec"); 11− toplevel.setManager(exec); 12− 13− Director local = new Director(toplevel, "Local"); 14− 15− AtorExemplo a1 = new AtorExemplo(toplevel, ¨a1¨); 16− a1.par.setToken(new IntToken(10)); 17− 18− AtorExemplo a2 = new AtorExemplo(toplevel, ¨a2¨); 19− AtorExemplo a3 = new AtorExemplo(toplevel, ¨a3¨); 20− 21− toplevel.connect(a1.saida, a2.entrada); 22− 23− TypedIORelation r1 = new TypedIORelation(toplevel, ¨r1¨); 24− a2.saida.link(r1); 25− a3.entrada.link(r1); 26− 27− exec.run(); 28− } 29− } Figura 2.8− Exemplo de arquivo principal de uma especificação executável. Na linha 7, o ator de nível mais alto (toplevel) é criado. Nenhum outro ator Composite é utilizado, dessa forma, o exemplo da figura 2.8 não é hierárquico. Na linha 10, uma instância de objeto Manager é criada e associada ao ator hierárquico toplevel (linha 11). Na linha 13, um Director é criado e associado ao ator hierárquico. Esse ator, um objeto Manager e um objeto Director, sempre estarão presentes em qualquer especificação executável. Três instâncias do ator da figura 2.7 são utilizadas. Na linha 16, o valor padrão do parâmetro par do AtorExemplo exemplo é modificado. Na linha 21, é feita a conexão entre dois portos, através do método connect() do ator hierárquico. Outra forma de criar uma 7 Adotamos como notação adicionar um caractere _ ao início de um nome de variável quando esta for private.
  • 26. 11 conexão é exemplificada da linha 23 à linha 25, através da criação explícita de um objeto Relation. O comando da linha 27 é utilizado para iniciar a execução. 2.3 Os Modelos Computacionais Descreveremos nesse item a semântica dos seis modelos computacionais (MoC) que utilizamos e características referentes à implementação desses modelos no ambiente Ptolemy. Iremos ressaltar dois pontos importantes de um MoC: o escalonador e o modelo de tempo. Em seguida, apresentaremos uma lista não exaustiva de outros modelos. É importante notar que um mesmo MoC pode ser implementado de várias maneiras diferentes. Todos os resultados desse trabalho são referentes as implementações adotadas no ambiente Ptolemy. 2.3.1. Discrete Event O modelo computacional Discrete Event [MUL99] (DE) é baseado no processamento de eventos produzidos pelos atores. Um evento é um par composto por um token e um valor numérico real. Esse valor numérico é denominado de timestamp e sua função é modelar um valor temporal, ou seja, o modelo DE possui uma noção explícita de tempo. O tempo nesse modelo é compartilhado por todos os atores da especificação. Quando um ator produz um evento, este é enviado para uma fila de eventos controlada pelo escalonador, e não para o ator de destino. Os eventos são mantidos ordenados de forma crescente na fila, com base no valor do timestamp. A cada iteração, o escalonador remove o próximo evento da fila e envia−o ao ator de destino. Esse ator é então ativado, isto é, o escalonador executa os métodos prefire(), fire() e postfire(). É possível criar uma especificação com atores que não manipulam o valor do timestamp. Esse tipo de ator é denominado de atraso zero (zero delay). A execução de uma especificação no modelo DE é interrompida quando não há mais nenhum evento na fila ou quando um valor de tempo especificado para o fim da execução é alcançado. Uma situação de conflito que pode ocorrer nesse modelo é a presença de eventos simultâneos, ou seja, eventos com um mesmo timestamp. A figura 2.9 ilustra esse situação. Figura 2.9 − Situação de eventos simultâneos no MoC DE. Na situação da figura 2.9, o ator A produziu dois eventos com um mesmo timestamp T. Um evento é para o ator B, e o outro para o ator C. Nessa situação, qual deve ser o próximo ator a ser ativado? A solução implementada no ambiente Ptolemy associa a cada ator um valor inteiro não negativo e único (prioridade). Esse valor é obtido através de uma ordem topológica de um grafo, onde os nós são os atores e os arcos representam as conexões que não apresentam atraso. Como mencionado em [MUL99], essa solução torna a execução da especificação determinística. É possível que o grafo gerado contenha ciclos. Isso significa que a especificação contém um ciclo sem atraso, situação que impede a execução da especificação, pois introduz um loop com geração infinita de eventos. Neste caso, o ambiente gera uma mensagem indicando para o usuário do problema. Os principais passos do algoritmo de escalonamento do MoC DE são: 1. caso a fila de eventos esteja vazia ou o instante de tempo final tenha sido alcançado, vá para o passo 9; 2. obtenha o próximo evento da fila; Ramp A Ramp B Ramp C T T
  • 27. 12 3. atualize o instante de tempo atual para o valor do timestamp do evento obtido no passo 2; 4. determine o ator de destino do evento obtido e introduza o token no respectivo porto de destino; 5. caso não exista nenhum outro evento simultâneo para o ator do passo 4, vá para o passo 7; 6. obtenha todos os eventos simultâneos para o ator do passo 4 e introduza−os nos respectivos portos do ator; 7. ative o ator até que todos os tokens em seus portos tenham sido consumidos; 8. vá para o passo 1; 9. fim. Quando mais que um evento com o mesmo timestamp estiver disponível para um ator, todos são removidos da fila de eventos (passos 4, 5, 6). Nessa condição, caso mais que um evento seja para o mesmo porto, uma fila é utilizada no porto de entrada/saída, respeitando a ordem desses eventos na fila do escalonador. É possível criar atores de atraso zero utilizando as classes TypedAtomicActor e TypedAtomicPort, mencionadas no item 2.2. Entretanto, para utilizar as características específicas do MoC DE, as classes DEIOPort e DEActor estão disponíveis. A figura 2.10 apresenta um trecho de código de um ator utilizando tais classes. 1− .... 2− import ptolemy.domains.de.kernel.*; 3− import ptolemy.domains.de.lib.DETransformer; 4− .... 5− 6− public class Delay extends DETransformer { 7− 8− public Delay(TypedCompositeActor container, String name) throws 9− NameDuplicationException, IllegalActionException { 10− super(container, name); 11 12− delay = new Parameter(this, "delay", new DoubleToken(1.0)); 13− delay.setTypeEquals(BaseType.DOUBLE); 14 15− input.delayTo(output); 16− } 17− 18− public Parameter delay; 19− 20− .... 21− 22− public void fire() throws IllegalActionException { 23− _currentInput = input.get(0); 24− } 25− 26− public boolean postfire() throws IllegalActionException { 27− output.broadcast(_currentInput, 28− ((DoubleToken)delay.getToken()).doubleValue()); 29− return super.postfire(); 30− } 31− 32− private Token _currentInput; 33− } Figura 2.10 − O ator Delay no MoC DE. O ator da figura 2.10 implementa um atraso por um tempo especificado através de um parâmetro (linha 18). O comando da linha 15 indica que existe um caminho com atraso entre um porto de entrada e um porto de saída. Essa informação é utilizada durante a criação do grafo utilizado para determinar o valor de prioridade de cada ator. Nas linhas 27 e 28 o evento é criado, através de uma versão específica do método broadcast(). Nessa versão, o segundo parâmetro indica o valor do atraso. 2.3.2 Synchronous Reactive O modelo computacional Synchronous Reactive [EDW94][EDW97] (SR) é baseado na hipótese
  • 28. 13 de sincronismo: o sistema é capaz de produzir uma resposta à estímulos externos de forma infinitamente rápida. Cada reação do sistema é instantânea e atômica, dessa forma, a evolução do tempo é dividida em uma série de instantes discretos. A hipótese de sincronismo também é utilizada por uma classe de linguagens de programação, tais como Esterel, Lustre, Signal, denominada de síncrona [BER98][HAL93][BEN91], além de circuitos digitais síncronos. Antes de descrevermos com maior detalhe a semântica do modelo SR, é necessário mencionar algumas definições, proposições e teoremas importantes. As demonstrações para esses teoremas e proposições podem ser encontradas em [EDW97]. Definição 1: Um conjunto parcialmente ordenado (poset) é um conjunto S com uma relação parcial de ordem ¡ que, para quaisquer x, y, e z pertencentes à S, satisfaz:   x ¡ x (reflexiva);   x ¡ y e y ¡ x implica que x = y (antissimétrica);   x ¡ y e y ¡ z implica que x ¡ z (transitiva); Definição 2: Um limite superior de um conjunto T é um elemento u tal que t ¡ u para todos t ¢ T. O menor limite superior de um conjunto T, representado por £ T, é um elemento l tal que l ¡ u para todos limites superiores u. Proposição 1: O menor limite superior de um conjunto, se existir, é único. Definição 3: Uma cadeia é um conjunto totalmente ordenado C, isto é, para todo x, y ¢ C, ou x ¡ y ou y ¡ x. Definição 4: Um poset, onde toda cadeia em S possui um menor limite superior em S, é denominado de conjunto parcialmente ordenado completo (CPO). Proposição 2: O menor limite superior de uma cadeia finita sempre existe e é seu maior elemento. Corolário 1: Um poset apenas com cadeias finitas é um CPO. Definição 5: O limite inferior de um poset, representado por ¤ , é um membro de S tal que ¤ ¡ s para todo s ¢ S. Um poset com um limite inferior é denominado de poset pontual. Proposição 3:
  • 29. 14 Se D1 e D2 são CPOs, então D1 ¥ D2 é um CPO sob a ordem (x1, x2) ¡ (y1, y2) se e somente se x1 ¡ y1 e x2 ¡ y2 e se x1 = (x1 1, x1 2) x2 = (x2 1, x2 2), .... ¦ {x1 ,x2 ,...} = ( £ {x1 1, x2 1, ...}, £ {x1 2, x2 2, ...}). Definição 6: Uma função f:D § E entre posets D e E é monotônica se para todo x,y ¢ D tal que x ¡ y, f(x) ¡ f(y). Definição 7: Uma função f:D § E entre CPOs D e E é contínua se para todas as cadeias C ⊆ D, f( £ C) =£ {f(c) ¨ c ¢ C}. Proposição 4: Uma função contínua é monotônica. Proposição 5: Uma função monotônica cujo domínio é um CPO com apenas cadeias finitas é contínua. Proposição 6: A composição de duas funções contínuas também é contínua. Proposição 7: A composição de duas funções monotônicas também é monotônica. Proposição 8: Seja D, E e F CPOs. Se f:D § E e g:D § F contínuas, então f¥ g também é contínua. Definição 8: Seja D um poset, f:D § D uma função e x © D:   se f(x) ¡ x, então x é um ponto prefixo;   se f(x) = x, então x é também um ponto fixo;   se x é um ponto prefixo e x ¡ p para todo ponto prefixo p, então x é um menor ponto prefixo;   se x é um ponto fixo e x ¡ y para todo ponto fixo y, então x é um menor ponto fixo; Teorema 1:
  • 30. 15 Seja f:D § D uma função contínua sobre um CPO pontual D. Então: fix(f) ≡ £ {¤ f(¤ ), f(f(¤ )), ...., fk (¤ ), ...} existe e é tanto um único menor ponto fixo como um único menor ponto prefixo de f. Definição 9: Seja I = I1 ¥ .... ¥ In e O = O1 ¥ .... ¥ Om vetores de CPOs pontuais. Denomina−se bloco uma função vetorial contínua de I sobre O. Definição 10: Seja b : I § O um bloco, J = J1 ¥ .... ¥ Ja um vetor de CPOs pontuais e w1,....,wn uma seqüência tal que wk ¢ {1, .... , a}. Se Jwk ⊆ Ik, então o bloco conectado à J com as conexões w1, ...., wn é a função c : J § O tal que c(j1, ...., ja) = b(jw1, ...., jwn) onde (j1, ...., ja) ¢ J. Definição 11: Seja b1 : I § O, b2 : I § O, ...., bn : I § O um conjunto de blocos, I = I1 ¥ .... ¥ In um vetor de CPOs pontuais, O = O1 ¥ .... ¥ Os e J = I ¥ O. O sistema aberto composto por esses blocos é a função d: J § O tal que d(j) = c1(j) ¥ c2(j) ¥ .... ¥ cs(j) onde ck é o bloco bk conectado ao vetor J, e j ¢ J. O modelo computacional SR enxerga o sistema como sendo uma função. Seja d um sistema aberto, a função SR é a menor função e:I § O que satisfaça e(i) = d(i, e(i)), (1) onde I e O são vetores de CPOs pontuais. A figura 2.11 apresenta uma ilustração das definições 10, 11 e de uma função SR.
  • 31. 16 Figura 2.11 − Ilustrações das definições do MoC SR: (a) O sistema; (b) O sistema aberto correspondente; (c) O bloco correspondente. A equação (1) é um ponto fixo, onde a função e é o parâmetro. Dessa forma, podemos escrever: e = B(e), onde B:(I § O) § (I § O) é uma função que transforma uma função em outra função. Utilizando o teorema 1, Edwards demonstra que a função B apresenta um único menor ponto fixo. Para tal, ele demonstra que o domínio de B é um CPO pontual e que B é contínua. Isso implica que o MoC SR é determinístico. O valor de cada sinal no MoC SR pode estar em um de três estados: indefinido, definido e ausente (nenhum evento no instante) ou definido e presente (evento com um valor no instante). No início de cada instante, todos os sinais, a menos das entradas do sistema, estão no estado indefinido. Uma vez que um sinal passa do estado indefinido para um definido, esse sinal não pode mais mudar de estado ou valor. Essa regra é necessária para assegurar que o ator implementa uma função monotônica (e pela proposição 5, contínua). Os três estados formam uma CPO, com o estado indefinido sendo o limite inferior. A implementação do MoC SR no ambiente Ptolemy consiste de um escalonador que resolva o cálculo de um menor ponto fixo para o sistema. Edwards descreve um método capaz de determinar em tempo de compilação uma ordem de ativamento de atores que encontra o menor ponto fixo. Entretanto, o método é relativamente complexo para ser descrito brevemente nessa dissertação. O escalonador que utilizamos baseia−se no teorema 1 para encontrar o menor ponto fixo: 1. execute todos os atores e verifique se algum sinal passou do estado indefinido para o definido; 2. se não houver modificação no estado dos sinais, então o ponto fixo foi encontrado. Do contrário, repita o passo 1. Em [EDW94], Edwards prova que esse escalonador irá encontrar o menor ponto fixo em um Ramp F1 F2 F3 F4 F4 F1 F2 F3 1 2 3 4 5 1 2 3 4 5 O O J I d I Oe (c) (b) (a)
  • 32. 17 número finito de iterações. No MoC SR, um ator atômico é dividido em dois tipos: Strict e Non−Strict. Atores Strict são aqueles que requerem que todos os sinais de entrada estejam definidos para ocorrer o ativamento. Atores Non−Strict não possuem essa restrição, e portanto, são mais flexíveis. Uma das razões para a existência de atores desse tipo é que em caminhos de realimentação, caso apenas atores Strict fossem utilizados, o sistema entraria em deadlock. A figura 2.12 apresenta um exemplo de ator Non−Strict que implementa um atraso por um instante. 1− .... 2− public class SRPre extends TypedAtomicActor { 3− 4− public SRIOPort input; 5− public SRIOPort output; 6− 7− public SRPre(TypedCompositeActor container, String name) 8− throws IllegalActionException, NameDuplicationException { 9− super(container, name); 10− 11− input = new SRIOPort(this, input1, true, false); 12− input.setTypeEquals(BaseType.INT); 13− 14− output = new SRIOPort(this, output, false, true); 15− output.setTypeEquals(BaseType.INT); 16− output.setMultiport(true); 17− 18− ((SRDirector) getDirector()).setStrict(this, false); 19− } 20− 21− public void initialize() throws IllegalActionException { 22− super.initialize(); 23− _state = 0; 24− } 25− 26− public void fire() throws IllegalActionException { 27− if(!output.known(0)) { 28− output.send(0, new IntToken(_state)); 29− } 30− } 31− 32− public boolean postfire() throws IllegalActionException { 33− if(input.present(0)) { 34− _state = ((IntToken) input.get(0)).intValue(); 35− } 36− return true; 37− } 38− 39− private int _state; 40− } Figura 2.12 − Ator de atraso no MoC SR. A linha 18 utiliza o método setStrict() para determinar que o ator é do tipo Non−Strict. Na linha 27, é determinado se o sinal de saída ainda não está definido. Caso positivo, o valor atual da variável interna é enviado. No MoC SR, o método fire() pode ser chamado inúmeras vezes a cada instante. Dessa forma, ações que o usuário deseje realizar uma única vez por instante devem ser efetuadas no método postfire(). No caso do ator da figura 2.12, a atualização do estado (linha 34) é feita nesse método. 2.3.3 Communicating Sequential Processes O modelo computacional Communicating Sequential Processes (CSP) [HOA78] [HOA85] [SMY98] é fundamentado no uso de processos concorrentes, cada ator associado à um único processo. O escalonamento dos processos é feito pelo ambiente de execução da linguagem Java. Nesse modelo não há armazenamento de dados entre processos, a transferência de dados é feita através do protocolo de rendezvous. Nesse protocolo, um ator A executa o método send() ou broadcast() para enviar um dado à um ator B. O ator A permanece bloqueado enquanto o ator B não consome o token. O mesmo acontece quando um ator tenta consumir um token quando este ainda não está disponível. O MoC CSP permite o rendezvous não−determinístico através de comandos de comunicação
  • 33. 18 condicional (guarded). Esses comandos apresentam a seguinte forma: guard; comunicação = lista de comandos; O guard é uma expressão que retorna um valor booleano. Não é permitido que seu cálculo produza alterações no estado interno do processo. Caso o valor retornado seja falso, o comando é encerrado. Caso contrário, o comando de comunicação especificado é iniciado e quando ele for completado, a lista de comandos é executada. Dois tipos de comando de comunicação condicional estão disponíveis: CIF e CDO. O comando CIF apresenta a seguinte forma: CIF { guard1; comunicação1 = lista de comandos1; [] guard2; comunicação2 = lista de comandos2; [] .... } Para cada caminho do comando CIF, os respectivos guards são verificados. Essa verificação é feita concorrentemente, ou seja, é irrelevante a ordem dos guards. Caso um ou mais guards sejam verdadeiros, os respectivos comandos de comunicação são iniciados. Caso nenhum comando de comunicação esteja pronto, todo o comando CIF irá bloquear até que uma comunicação esteja pronta. Caso mais que uma comunicação esteja pronta, a escolha de uma é feita de maneira não−determinística. Uma vez que a comunicação é completada, a lista de comandos associada é executada e o comando CIF é encerrado. O comando CIF é ignorado quando todos os guards forem falsos. O comando CDO é similar ao comando CIF. Entretanto, ao acabar a execução de uma das listas de comandos, o comando CDO é reiniciado. Esse comando é encerrado apenas quando todos os guards forem falsos. A implementação do MoC CSP no ambiente Ptolemy apresenta alguns métodos adicionais para implementar os comandos CIF e CDO. A figura 2.13 ilustra o comando CDO. 1− boolean continueCDO = true; 2− 3− while(continueCDO) { 4− 5− ConditionalBranch [] branches = new ConditionalBranch[3]; 6− 7− branches[0] = new ConditionalReceive(true, input, 0, 0); 8− branches[1] = new ConditionalReceive(true, input, 0, 1); 9− 10− token = new Token(); 11− branches[2] = new ConditionalSend(true, output, 0, 2, token); 12− 13− int result = chooseBranch(branches); 14− 15− if(result == 0) { 16− .... 17− Token t = branches[0].getToken(); 18− .... 19− } 20− else 21− if(result == 1) { 22− .... 23− Token t = branches[1].getToken(); 24− .... 25− } 26− else 27− if(result == 2) { 28− .... 29− } 30− else 31− if(result == −1) { 32− continueCDO = false; 33− } 34− }
  • 34. 19 Figura 2.13 − O comando CDO no ambiente Ptolemy. A primeira tarefa é a criação dos diferentes caminhos do comando utilizando os objetos ConditionalReceive e ConditionalSend. Isso é feito da linha 5 à linha 11. No exemplo da figura 2.13, dois caminhos possuem comandos de comunicação para recepção de dados (linha 7 e 8) e um caminho é para envio de um dado (linha 11). O primeiro parâmetro dos construtores é o guard do comando, que neste exemplo é sempre verdadeiro. O quarto parâmetro é um valor inteiro único associado ao caminho em questão. A segunda tarefa é a escolha de um dos caminhos, utilizando o método chooseBranch(). Esse método irá retornar o número inteiro associado com o caminho escolhido. Finalmente, a lista de comandos para o caminho ativado é executada (linhas 15 à 33). Embora originalmente esse modelo não apresente uma noção de tempo, a implementação no ambiente Ptolemy introduziu essa característica. A exemplo do MoC DE, esse MoC também utiliza o tempo centralizado. Cada processo (ator) pode se suspender por uma duração de tempo determinada. Quando esse instante for alcançado, o escalonador ativa novamente o processo. O tempo é avançado quando todos os processos se suspenderam ou quando todos os processos estão bloqueados devido ao rendezvous e pelo menos um processo se suspendeu. Nestas situações, o tempo atual é avançado o suficiente para ativar algum processo que se suspendeu. 2.3.4 Synchronous Data Flow O modelo computacional Synchronous Data Flow (SDF) [LEE87a][LEE87b pertence à classe dos modelos data flow [DAV82][DEN80][KAR66]. O modelo data flow foi inicialmente desenvolvido para descrever e analisar programas paralelos. Nesse modelo, o sistema é abstraído por um grafo onde os nós são atores e os arcos indicam dependência de dados entre os atores. Em cada arco do grafo existe uma fila unidirecional utilizada para a troca de dados. É introduzido o conceito de ativamento de um ator (nó): um nó executa apenas quando todos os dados necessários estiverem disponíveis (o ator está pronto), e sua execução é atômica. Não existe o conceito de fluxo de controle em grafos data flow. No modelo SDF, além de respeitar a semântica data flow, deve ser especificado, para cada ator, quantos tokens são consumidos (no caso de um porto de entrada) ou produzidos (no caso de um porto de saída) para todos os seus portos. Esses valores (também chamados de taxas de amostragem) são fixos e conhecidos em tempo de compilação. Um grafo SDF possui um escalonamento (seqüencial ou paralelo) estático, ou seja, antes da execução é determinado quantas vezes cada ator deve ser ativado e qual é a ordem de ativamento. A figura 2.14 apresenta dois grafos SDF. Descreveremos o modelo SDF mais formalmente seguindo [LEE87b]. Iremos considerar o caso de escalonamento seqüencial. O caso paralelo está descrito em [LEE87a]. Figura 2.14 − Dois grafos SDF. Os números próximos aos nós são as taxas de amostragem. Um grafo SDF pode ser representado por uma matriz de incidência: as linhas representam os arcos e as colunas os nós. Cada posição da matriz é uma taxa de amostragem: positiva quando o ator produz o token na respectiva conexão, negativa quando ele consome. Essa matriz é denominada de matriz de topologia. As matrizes de topologia para os grafos da figura 2.14 (a) e (b) são respectivamente: 1 2 3 1 2 3 1 2 11 1 1 1 2 3 1 2 3 1 2 21 1 1 (a) (b) 1 −1 0 2 0 −1 0 1 −1 1 −1 0 2 0 −1 0 2 −1 (a) (b) Γ Γa b = =
  • 35. 20 Durante a execução, a quantidade de dados em cada fila de comunicação irá variar. Em um determinado momento n, a quantidade de dados em cada fila é dada por um vetor coluna b(n). Para um escalonamento seqüencial, o vetor coluna v(n) indica qual ator é ativado em um instante n. Podemos descrever a variação de dados em cada fila pela equação b(n + 1) = b(n) + Γv(n). O valor do vetor b quando n = 0 indica em quais conexões existem dados antes do início da execução. Para se determinar um escalonamento de um grafo SDF, é necessário verificar se a matriz de topologia é consistente, ou seja, se as taxas de amostragem especificadas são válidas. Foi demonstrado que o rank da matriz de topologia, igual ao número de nós menos um, é uma condição necessária para a consistência. Por exemplo, o rank da matriz Γa é igual a três, enquanto que o rank da matriz Γb é igual à dois. Dessa forma, o grafo da figura 2.14 (a) é inválido e o da figura (b) é válido. Quando a matriz de topologia for válida, existe um vetor coluna q que satisfaz: Γq = 0. Para a matriz Γb, q = J[1 1 2]T , para qualquer J positivo. O vetor q determina quantas vezes cada nó deve ser ativado para a obtenção de um escalonamento periódico, ou seja, um escalonamento que quando completado, faz com que o valor do vetor b permaneça inalterado. Dessa forma, o grafo SDF pode ser executado infinitamente com capacidade limitada e conhecida de memória (filas). Mesmo quando a matriz de topologia é consistente, é possível que a especificação do grafo seja inválida. Isso ocorre quando existirem ciclos no grafo sem a quantidade adequada de dados no momento inicial da execução. A figura 2.15 ilustra essa situação. Figura 2.15 − Dois grafos SDF inválidos mas com matrizes consistentes. O losango da figura 2.15 (b) representa um ator de atraso, ou seja, um ator que no instante n envia o token recebido no instante n − 1. Para isso, esse ator produz tokens antes do início da execução (a entrada no vetor b com n = 0 para o porto de saída desse ator é maior que zero). Ambos os grafos da figura 2.15 não possuem condições iniciais válidas: a grafo da figura (a) não possui nenhum atraso, o da figura (b) apresenta um atraso de apenas um token, o que é insuficiente. A figura 2.16 apresenta a implementação de um ator de atraso no ambiente Ptolemy. 1− public class Delay extends Transformer { 2− public Delay(TypedCompositeActor container, String name) 3− throws IllegalActionException, NameDuplicationException { 4− 5− super(container, name); 6− new Parameter(output, TokenInitProduction, 7− new IntToken(1)); 8− 9− output.setTypeAtLeast(input); 10− } 11− 12− public void fire() throws IllegalActionException { 1 1 1 1 1 2 1 2 D (a) (b)
  • 36. 21 13− Token message = input.get(0); 14− output.broadcast(message); 15− } 16− 17− public void initialize() throws IllegalActionException { 18− output.broadcast(new Token()); 19− } 20− } Figura 2.16 − O ator Delay no MoC SDF. A linha 6 modifica o parâmetro TokenInitProduction que especifica a quantidade de tokens a serem produzidos antes do início da execução. Outra forma de fazer tal especificação é através do método setTokenInitProduction() da classe SDFIOPort. Métodos similares existem para especificar a produção e consumo de tokens a cada ativamento do ator. Quando o grafo SDF for consistente e com a quantidade necessária de atrasos, o seguinte algoritmo pode ser usado para a obtenção de um escalonamento seqüencial8 : 1. encontre o menor vetor q; 2. obtenha uma lista L de todos os atores da especificação; 3. percorra a lista L e para cada ator pronto, escalone−o; 4. caso cada ator α tenha sido escalonado qα vezes, FIM; 5. caso nenhum ator da lista L esteja pronto, ocorreu um deadlock (a especificação é inválida); 6. do contrário, vá para 3. O algoritmo encontra um escalonamento simulando a execução do sistema. Para encontrar o vetor q do passo 1, o seguinte método é utilizado: 1. escolha aleatoriamente um ator e assuma que ele é ativado uma única vez, ou seja, qa = 1; 2. para um ator B adjacente à A, calcule o valor de qb = (qa . pa)/cb, onde pa é a quantidade de tokens produzidos pelo ator A na conexão e cb a quantidade de tokens consumidos por B. O valor obtido pode ser fracional mas sempre racional; 3. repita o passo 2 recursivamente para um ator adjacente a B; 4. uma vez obtidos todos os valores de q, encontre o menor denominador comum desses valores a fim de torná−los inteiros. É fácil perceber que o método descrito irá encontrar o vetor q em tempo linear ao número de nós e arcos do grafo. No modelo SDF, não é necessário implementar explicitamente filas para a comunicação entre os atores. O fato desse MoC ser escalonado estaticamente permite associar uma posição de memória específica para cada dado a ser consumido ou escrito por um ator, em cada ativamento do mesmo. Entretanto, a semântica de comunicação por filas é respeitada. O modelo SDF não apresenta uma noção de tempo. 2.3.5 Dynamic Data Flow Assim como o modelo SDF, o modelo computacional Dynamic Data Flow [LEE95] (DDF) respeita a semântica data flow, e também não possui uma noção de tempo. Ao contrário do modelo SDF9 , não existe a necessidade de especificar as taxas de consumo e produção de tokens de cada porto, embora isso seja permitido. Esses valores podem variar durante a execução. Dois exemplos de atores DDF são mostrados na figura 2.17. 8 Outros algoritmos para escalonamento seqüencial de grafos SDF estão descritos em [BHA94]. 9 O modelo SDF é um subconjunto do modelo DDF.
  • 37. 22 Figura 2.17 − Os atores Switch e Select no modelo DDF. O ator Switch consome um token do porto de dados e baseado no valor do token do porto de controle, envia o token de dado para um dos dois portos de saída. Dessa forma, apenas um porto de saída produzirá dados a cada ativamento do ator Switch. O ator Select consome um token de dados, baseado no valor do token de controle. Podemos observar que no caso do ator Select, mais de uma condição de presença de dados pode ativá−lo. Esse fato é conhecido como as regras de ativamento de um ator (firing rules). No caso do modelo SDF, só existe uma regra para cada ator. No caso do modelo DDF, várias regras podem ser especificadas para um mesmo ator. Por exemplo, o ator Select possui duas regras: R1 = {[*], ¤ , [0]} R2 = {¤ , [*], [1]} A primeira posição das regras representa a entrada Dado1, a segunda a entrada Dado2 e a terceira a entrada Controle. O símbolo ¤ indica que a regra pode ser satisfeita independentemente da respectiva entrada. O símbolo * indica que é necessário um token, independente de seu valor. Os números dentro de colchetes indicam o valor necessário do token. O modelo DDF é determinístico (assim como o SDF), isto é, dado um mesmo conjunto de entradas, as saídas sempre terão o mesmo valor, independentemente da ordem de ativamento dos atores. Entretanto, para que uma especificação utilizando o MoC DDF seja válida (determinística), todos os atores devem implementar funções contínuas. Isso pode ser obtido utilizando regras de ativamento seqüenciais. Um algoritmo para determinar quando as regras de ativamento são seqüenciais é: 1. encontre um porto de entrada j tal que [*] ¡ Ri,j para todas as regras i = 1, ..., N, isto é, um porto de entrada tal que todas as regras necessitem de pelo menos um token neste porto. Caso nenhum porto seja encontrada, as regras não são seqüenciais; 2. para o porto j encontrado no passo 1, divida as regras em subconjuntos de acordo com o primeiro valor de Ri,j para todas as regras. Caso Ri,j = [*, ....], então Ri,j deve aparecer em todos os subconjuntos; 3. remova o primeiro elemento de Ri,j para todas as regras i; 4. caso todos os subconjuntos sejam vazios, então as regras de ativamento são seqüencias. Caso contrário, repita os passos para cada subconjunto não vazio. Para as regras do ator Select, o primeiro passo irá identificar j = 3. Dois subconjuntos são formados, um para cada regra. As duas novas regras são R1 = {[*], ¤¤ } e R2 = {¤ , [*], ¤ }. O procedimento encerra trivialmente para cada um dos subconjuntos. No ambiente Ptolemy, as regras de ativamento de um ator não são enumeradas explicitamente. Para garantir a validade do ator, a semântica de leitura bloqueante é utilizada, ou seja, ao tentar consumir um ou mais tokens, o ator fica bloqueado enquanto os dados não estiverem disponíveis. Note que o algoritmo para determinar se um conjunto de regras é seqüencial pode ser implementado através do uso de leitura bloqueante. A figura 2.18 exemplifica essa situação através do código do ator Select. 1− public class Select extends DDFAtomicActor { 2− 3− public DDFIOPort control; 4− public DDFIOPort data1; 5− public DDFIOPort data2; Switch Select Dado Controle Controle Dado1 Dado2 Saída1 Saída2 Saída
  • 38. 23 6− public DDFIOPort output; 7− 8− public Select(TypedCompositeActor container, String name) throws 9− IllegalActionException, NameDuplicationException { 10− 11− super(container, name); 12− 13− control = new DDFIOPort(this, control, true, false); 14− control.setTokenConsumptionRate(1); 15− control.setTypeEquals(BaseType.DOUBLE); 16− 17− data1 = new DDFIOPort(this, data1, true, false); 18− data1.setTokenConsumptionRate(0); 19− 20− data2 = new DDFIOPort(this, data2, true, false); 21− data2.setTokenConsumptionRate(0); 22− 23− output = new DDFIOPort(this, output, false, true); 24− output.setTokenProductionRate(1); 25− } 26− 27− public void fire() throws IllegalActionException { 28− int i; 29− 30− if(_readyToGo == false) { 31− DoubleToken t = (DoubleToken) control.get(0); 32− _ctrl = (double) t.doubleValue(); 33− } 34− 35− if(_ctrl == 0.0) { 36− if(data1.hasToken(0)) { 37− _readyToGo = false; 38− waitFor(control, 1); 39− output.broadcast(data1.get(0)); 40− } 41− else { 42− _readyToGo = true; 43− waitFor(data1, 1); 44− } 45− } 46− else { 47− if(data2.hasToken(0)) { 48− _readyToGo = false; 49− waitFor(control, 1); 50− output.broadcast(data2.get(0)); 51− } 52− else { 53− _readyToGo = true; 54− waitFor(data2, 1); 55− } 56− } 57− } 60− 61− public void initialize() throws IllegalActionException { 62− super.initialize(); 63− 64− _readyToGo = false; 65− waitFor(control, 1); 66− _ctrl = 0.0; 67− } 68− 69− private boolean _readyToGo; 70− private double _ctrl; 71− } Figura 2.18 − Código do ator Select no MoC DDF. As linhas 18 e 21 indicam que os portos de entrada data1 e data2 não possuem taxas de consumo constante. A leitura bloqueante é implementada através da utilização do método waitFor(). O primeiro parâmetro desse método é o porto de entrada e o segundo parâmetro indica quantos tokens devem ser lidos para o ativamento. Inicialmente, o ator Select deve ler o token de controle (linhas 65, 38 e 49) para em seguida ler o dado. Caso esse não esteja disponível, a leitura bloqueante é utilizada novamente para os portos de dados (linhas 43 e 54). Note a utilização de uma variável interna (_readyToGo) para indicar o estado em que o ator se encontra e assim determinar de qual porto o próximo token deve ser consumido.
  • 39. 24 Ao contrário do modelo SDF, o escalonador do MoC DDF não é estático. Dois tipos de escalonadores dinâmicos existem [PAR95]: data driven e demand driven. Escalonadores tipo data driven ativam um ator assim que uma de suas regras de ativamento seja válida. Já escalonadores tipo demand driven ativam um ator baseado na demanda de tokens de um ator sucessor. O ambiente Ptolemy utiliza um escalonador tipo data driven, classificando os atores prontos para serem ativados como deferrable ou não. Um ator é deferrable quando alguns de seus sucessores já possuem tokens suficientes na conexão com o ator. Um ator deferrable só é ativado quando não existirem atores classificados como não deferrable. Nesse caso, um dos atores deferrable é escolhido aleatoriamente para ser ativado. A classificação de ator deferrable visa evitar o acúmulo desnecessário de tokens em uma fila. Outra característica do MoC DDF é que, ao contrário do MoC SDF, não é possível determinar a quantidade necessária de armazenamento de cada fila de comunicação. É possível que uma execução venha a ser prematuramente interrompida devido ao estouro de espaço de memória. 2.3.6 Kahn Process Networks Assim como o MoC CSP, uma especificação executável utilizando o modelo computacional Kahn Process Networks [KA74][KAH77][GOE98][PAR95] é composta por uma rede de processos, onde cada ator da especificação é associado à um processo. Tal como nos modelos data flow, cada conexão entre atores contém uma fila. O modelo PN é determinístico quando cada processo implementa uma função contínua. Assim como no MoC DDF, isso é implementado através de leitura bloqueante: quando um ator tenta consumir um token de uma fila vazia, o respectivo processo é bloqueado, sendo reativado quando o token estiver disponível. O MoC PN não permite o teste da presença de um token, pois isso tornaria o modelo não−determinístico. Também não é permitido que um ator espere por tokens em mais de um porto ao mesmo tempo. Tal como no MoC DDF, não é possível determinar a capacidade necessária de cada fila. A solução adotada no ambiente Ptolemy utiliza a escrita bloqueante, ou seja, quando um ator tenta produzir um token e a respectiva fila está cheia, o processo do ator é bloqueado. Quando houver espaço na fila, o processo é reativado. É possível que uma execução venha a entrar em deadlock, ou seja, quando todos os atores estiverem bloqueados. Quando não existe nenhum ator bloqueado devido à situação de filas cheias, diz− se que o deadlock é real. Nessa situação a execução chegou ao fim. Caso contrário, temos um deadlock artificial. Neste caso, o ambiente Ptolemy determina uma fila cheia e aumenta seu tamanho, reativando o respectivo processo e prosseguindo com a execução. A implementação desse MoC no ambiente Ptolemy possui uma noção de tempo, similar ao do modelo CSP, ou seja, um processo pode se suspender até um determinado instante de tempo. 2.4 Outros Modelos Computacionais Vários outros modelos computacionais foram desenvolvidos, além dos seis modelos computacionais que utilizamos nesse trabalho. O próprio ambiente Ptolemy implementa outros três modelos. Enumeramos alguns modelos importantes e descritos na literatura:   Cyclo−Static Data Flow [BIL96]: modelo data flow que estende o MoC SDF permitindo que um ator tenha mais que uma regra de ativamento. Entretanto, as regras devem apresentar taxas de consumo e produção constantes e a ordem de utilização de cada regra é definida antes da execução. Esse modelo também apresenta um escalonamento estático;   Boolean Data Flow [BUC93]: modelo data flow que estende o MoC SDF permitindo a variação das taxas de consumo e produção durante a execução. Entretanto, diferentemente do MoC DDF, essa variação deve sempre estar condicionada à um valor booleano obtido de outro porto (por exemplo, os atores da figura 2.17). Em alguns casos, é possível encontrar um escalonamento estático;
  • 40. 25   Máquinas de Estado Finito [GAJ00]: modelo computacional clássico baseado em autômatos finitos;   Redes de Petri [PET81]: modelo de estados explícitos onde a especificação é composta por dois tipos de elementos: place e transição. Cada place pode conter um elemento denominado de token. Um place pode ser conectado à uma transição (place de entrada da transição) e uma transição pode ser conectada à um place (place de saída da transição). Quando todos os place de entrada de uma transição tiverem pelo menos um token, diz−se que a transição está pronta para ser ativada. O ativamento consiste em remover um token de todos os place de entrada e adicionar um token à todos os places de saída. A concorrência é possível pois mais de uma transição pode estar pronta para ser ativada em um mesmo instante. Neste caso, a escolha é feita de forma não determinística;   StateChars [HAR87]: modelo que adicionada à máquinas de estado finito o conceito de hierarquia e concorrência. A hierarquia é representada através de estados que contenham outras máquinas de estado. Quando ocorre a transição para um estado hierárquico, a máquina de estado interna é ativada. A concorrência é modelada através da possibilidade de que mais de um estado estar ativado simultaneamente. Nesse modelo, é permitido transições de estados pertencentes à diferentes níveis de hierarquia;   *Charts [GIR99]: modelo implementado no ambiente Ptolemy que adiciona os conceitos de hierarquia e concorrência às máquinas de estados finitos. Diferentemente do modelos StateCharts, o modelo *Charts não define estados concorrentes. A concorrência é obtida quando vários atores Composite, que implementam subsistemas em *Charts, são incluídos em uma topologia governada por um modelo com concorrência. Não é permitido transições entre estados de diferentes hierarquias;   Codesign Finite State Machine [LAV00]: modelo baseado em uma rede de máquinas de estado. A comunicação é feita através de broadcast de sinais: uma máquina produz um evento em um sinal e todas as outras máquinas que dependem desse sinal podem obter o novo evento. A hipótese de sincronismo é assumida para todas as máquinas de estado mas não aplicada à interação das máquinas, ou seja, uma vez que um novo evento é produzido, a resposta de outra máquina à esse novo evento não é infinitamente rápida. Dessa forma, é possível que eventos não sejam capturados por máquinas com reação lenta, uma vez que não existe o armazenamento de eventos;   Control−Data Flow Graph [MIC94]: modelo utilizado para representar um algoritmo seqüencial através de um grafo direcionado. Existem dois tipos básicos de nós: nós de controle e basic blocks. Um nó tipo basic block contém uma seqüência de comandos. Nós de controle determinam os diferentes caminhos possíveis para o fluxo de controle;   Continuous Time [LIU98]: modelo implementado no ambiente Ptolemy fundamentado na utilização de equações diferenciais para representar um sistema. Esse modelo possui uma noção de tempo contínuo. O escalonador desse modelo é composto por um algoritmo que obtenha resultados aproximados para o sistema de equações em alguns instantes de tempo. Esse modelo é mais apropriado para representar sistemas analógicos e mecânicos;   Distributed Discrete Event [DAV99]: modelo implementado no ambiente Ptolemy que procura solucionar os problemas relacionados ao emprego de uma noção global de tempo, tal como no modelo DE. O modelo mantém uma noção local de tempo em cada conexão entre atores. Cada ator é associado à um processo e este pode avançar seu valor de tempo para o mínimo entre os valores de suas conexões de entrada.
  • 41. 26 2.5 A Ferramenta de Depuração/Profile Conforme foi ilustrado no item 2.2, uma especificação executável no ambiente Ptolemy é composta por classes na linguagem Java, escritas pelo usuário, e por classes disponíveis pelo ambiente. Essas classes passam por um processo de compilação e o código resultante pode ser executado. A análise do comportamento da execução e dos resultados obtidos fica a cargo do usuário, isto é, o usuário deve adicionar trechos de códigos à especificação para coletar e apresentar informações relevantes à execução. Até o presente momento, o ambiente Ptolemy provê auxilio para esta tarefa apenas através de atores que utilizam o aplicativo PtPlot. Este aplicativo é capaz de ilustrar dados através de vários tipos de gráficos bidimensionais. Uma alternativa disponível para a análise da execução seria o uso de uma ferramenta de depuração para a linguagem Java. Essa solução é recomendável para encontrar erros no código fonte de cada ator, uma vez que cada comando do código pode ser analisado. Entretanto, esse tipo de ferramenta não é adequada para a análise de características mais genéricas referentes à interação entre diferentes atores sob um modelo computacional. Essas informações podem ser úteis para o usuário entender mais detalhadamente os resultados da execução da especificação e para encontrar erros lógicos não associados ao código de um ator isoladamente. Sob a luz do estudo apresentado nessa dissertação, uma ferramenta para a análise da execução de uma especificação torna−se útil, pois tal ferramenta auxilia identificar as diferenças encontradas ao utilizar modelos computacionais distintos na representação de uma mesma primitiva computacional. Dessa forma, uma ferramenta denominada de PTII Analyzer foi desenvolvida com esse objetivo. Sua principal função é reproduzir uma execução, ilustrando a troca de tokens entre diferentes atores. Informações estatísticas também podem ser extraídas a partir dos dados coletados. 2.5.1 Dados coletados A ferramenta desenvolvida baseia−se em dados coletados durante a execução de uma especificação. Para tal, acrescentamos trechos de código em classes específicas do ambiente Ptolemy. Dois tipos de conjunto de dados são armazenados: a produção ou o consumo de um token e a mudança de estado de um ator. Para cada evento de consumo ou produção de um token, armazenamos o seguinte conjunto de informações:   tipo do evento: consumo ou produção;   identificador do token: um valor inteiro único para cada token durante a execução;   ator de origem;   porto de origem;   ator de destino;   porto de destino;   valor do tempo no momento do evento;   instante. O instante é um valor inteiro único para cada conjunto de informações e utilizado a fim de ordenar os dados coletados permitindo ilustrar a reprodução da execução. O segundo tipo de conjunto de dados armazenado é referente a mudança de estado de um ator. Definimos três possíveis estados: bloqueado, pronto ou executando. Em alguns MoCs, subdividimos o estado bloqueado em bloqueado no consumo ou na produção de um token. Quando ocorre uma mudança de estado, armazenamos o seguinte conjunto de dados:   ator;   novo estado;   valor do tempo no momento do evento;   instante.
  • 42. 27 Definimos que um ator está no estado executando quando este está consumindo ou produzindo tokens. A determinação dos outros estados depende da semântica do modelo computacional que controla o ator. Para os seis modelos computacionais utilizados, definimos: SDF:   bloqueado: não há dados suficientes nos portos de entrada;   pronto: há dados suficientes nos portos de entrada. DDF: igual ao caso do MoC SDF. PN:   bloqueado: quando a respectiva fila estiver vazia (bloqueado no consumo) ou quando a respectiva fila estiver cheia (bloqueado na produção);   pronto: quando o ator estava executando mas foi escalonado. DE:   bloqueado: este estado não existe nesse MoC;   pronto: quando o ator não está executando. CSP:   bloqueado: quando o ator está esperando para iniciar ou concluir o rendezvous;   pronto: quando o ator estava executando mas foi escalonado. SR: Quando o ator é Strict:   bloqueado: não há a presença de eventos exigida;   pronto: há a presença dos eventos exigidos. Quando o ator é Non−Strict:   bloqueado: este estado não existe nesse caso;   pronto: quando o ator não está executando. 2.5.2 A Interface Gráfica O principal componente da ferramenta PTII Analyzer é sua interface gráfica, pois apresentar os dados coletados da execução na forma de uma tabela não seria útil ao usuário. A ferramenta permite dois tipos de análise: depuração e profiling. A depuração é feita reproduzindo−se graficamente a interação entre os atores. O profiling é feito através de estatísticas calculadas a partir dos dados e apresentados ao usuário na forma de gráficos e tabelas. A figura 2.19 apresenta a janela com o menu principal da ferramenta. Figura 2.19 − A janela inicial da ferramenta PTII Analyzer. Dados coletadosUm mesmo token Barra de scroll de instantes
  • 43. 28 A janela inicial da ferramenta contém uma tabela para mostrar os dados coletados. Quando o usuário seleciona o campo de identificação de um token, todos os demais campos da tabela referentes ao mesmo token são destacados. Inicialmente a primeira entrada da tabela é selecionada (instante 1). A janela inicial possui uma barra de scroll que permite selecionar a próxima entrada da tabela, ou a anterior. Aliado à janela que exibe a topologia da especificação, o usuário pode utilizar essa barra de scroll para reproduzir a execução. A janela da topologia é mostrada quando o usuário carrega o layout gráfico da topologia. A figura 2.20 apresenta essa janela. Figura 2.20 − A janela da topologia. Se os dados da execução foram carregados, a janela da topologia irá ilustrar o instante da entrada selecionada na tabela. Os atores são coloridos de acordo com o estado do instante selecionado: verde se estiver executando, amarelo se estiver pronto, vermelho se estiver bloqueado, roxo se estiver bloqueado na produção e rosa se estiver bloqueado no consumo. Os portos são coloridos quando o respectivo evento for selecionado na tabela: vermelho quando é uma produção e azul quando for um consumo. Também é possível inspecionar o estado de cada receiver até o instante selecionado. No canto direito inferior da figura 2.20, é mostrada uma janela que apresenta tal informação. A tarefa de depuração é feita com o auxilio da janela principal e da janela de topologia. A tarefa de profiling utiliza outras janelas. A figura 2.21 apresenta a janela inicial para essa tarefa. Ator Porto Informações sobre o nível de hierarquia Botões de edição da figura Estado do receiver no instante atualAtores da especificação
  • 44. 29 Figura 2.21 − Janela inicial para o profiling. A janela da figura 2.21 apresenta na seu lado esquerdo uma árvore representando a hierarquia dos atores e portos associados. Selecionando um ator opaco ou porto, o sumário das informações estatísticas para o respectivo objeto é apresentado. Também é possível restringir a análise à um subconjunto dos dados determinado−se um intervalo de instantes. O valor padrão inclui todos os dados. Através do menu dessa janela o usuário pode visualizar os dados em tabelas e gráficos. A figura 2.22 apresenta a tabela de dados referentes à atores. Figura 2.22 − Tabela de dados estatísticos de atores. A tabela da figura 2.22 apresenta nove dados para cada ator: as porcentagens de instantes em que o ator esteve em cada um dos três estados e o intervalo mínimo e máximo que um ator esteve em cada um dos três estados consecutivamente. A figura 2.23 apresenta a tabela de dados referente aos portos. Sumário das estatísticas Hierarquia de atores e portos da especificação Inclui os dados do ator ou porto nos gráficos
  • 45. 30 Figura 2.23 − A tabela de dados dos portos. A ferramenta utiliza duas tabelas para apresentar os dados estatísticos referentes aos portos: a primeira é para cada porto isoladamente e a segunda é referente à conexão entre dois portos. Para cada porto é calculado quantos dados foram produzidos, consumidos e os intervalos mínimos e máximos entre cada consumo e produção de tokens. Para cada conexão é registrado o valor máximo e a média de tokens acumulados durante o intervalo de instantes especificado. A janela inicial de profiling (figura 2.21) possui um botão para incluir os dados de um ator ou porto nos gráficos (os dados das tabelas são incluídos automaticamente). A figura 2.24 mostra o gráfico da evolução do estado de um ator com relação aos instantes. Estatísticas de cada porto Estatísticas de cada conexao
  • 46. 31 Figura 2.24 − Gráfico da evolução do estado de um ator. A janela da figura 2.24 utiliza o aplicativo PtPlot presente no ambiente Ptolemy para mostrar o gráfico. O valor 1.0 representa o estado executando, o valor 0.0 representa o estado pronto e o valor −1.0 o estado bloqueado. A ferramenta dispõe de um gráfico alternativo para a visualização da evolução do estado de um ator, conforme a figura 2.25 demonstra. Figura 2.25 − Gráfico alternativo da evolução dos estados de um ator. Com o gráfico da figura 2.25 é mais fácil comparar a evolução de vários atores simultaneamente. A evolução da quantidade de tokens em cada conexão também pode ser estudada através de um gráfico, exemplificado na figura 2.26. Figura 2.26 − Exemplo de gráfico da evolução da quantidade de tokens em uma conexão. Atores Atores Conexões
  • 47. 32 Capítulo 3 Primitivas Comportamentais 3.1 Introdução A metodologia que empregamos para determinar a eficiência de cada MoC é baseada no conceito de primitiva comportamental. Definimos uma primitiva comportamental como um tipo de comportamento básico utilizado pelo projetista para capturar um trecho do sistema. Uma primitiva comportamental deve possuir duas características: não ser ambígua e ser genérica. Criamos o conceito de primitiva comportamental a partir da analogia com o conceito de base em um espaço vetorial: uma especificação executável pode ser decomposta em um conjunto de primitivas da mesma forma como os pontos de um espaço vetorial podem ser encontrados a partir de bases. Entretanto, ao contrário de bases, primitivas comportamentais não são necessariamente independentes. Para capturar diferentes primitivas comportamentais utilizamos ¨toy benchmarks¨, que são exemplos de pequenos sistemas que demonstram fortemente a presença da primitiva. Podemos citar como exemplo de primitivas comportamentais construções de linguagens de programação imperativa tais como seqüência de expressões, iteração, desvios condicionais, chamada de procedimentos, etc. Comportamentos tais como recursão, sincronização, preempção e concorrência também são primitivas. Neste capítulo apresentaremos o estudo das primitivas comportamentais que enumeramos. Obtivemos essa lista de primitivas a partir de nossa experiência de criação de especificações executáveis. Cada primitiva é capturada através de um toy benchmark. Em seguida, uma ou mais especificações para cada um dos seis modelos computacionais utilizados são descritas e comentadas. Em seguida, observações referentes à análise da execução são apresentadas. Ao final do capítulo, será apresentado um resumo das conclusões referentes à adequação de cada modelo computacional na captura das primitivas comportamentais. 3.2 Seqüência de Expressões Uma seqüência de expressões (basic block) é um trecho de código caracterizado por ter fluxo de controle seqüencial sem repetições, desvios condicionais e chamadas de procedimentos. Essa primitiva foi escolhida pois sistemas costumam apresentar este tipo de comportamento. 3.2.1 Exemplo de Sistema O cálculo dos pontos de uma curva borboleta10 será usado como exemplo para capturar essa primitiva. Um procedimento em linguagem C que implementa esse cálculo é mostrado na figura 3.1. butterfly(double input, double *x, double *y) { double t1, t2, t3; t1 = input * 1/12.0; // Scale1 t1 = sin(t1); // Sine t2 = t1 * t1; // Mult1 t2 = t2 * t2; // Mult2 t3 = t2 * t1; // Mult3 t1 = input * 4.0; // Scale2 t1 = cos(t1); // Cos2 t1 = −2.0 * t1; // Scale3 10 Esse exemplo foi extraído do conjunto de demonstrações da versão anterior (Classic) do ambiente Ptolemy.
  • 48. 33 t2 = cos(input); // Cos1 t2 = exp(t2); // Exp t3 = t3 + t2 + t1; // Add *x = t3 * cos(input); // PtoR *y = t3 * sin(input); // PtoR } Figura 3.1 − Descrição em linguagem C do cálculo dos pontos da curva borboleta. A entrada para o sistema é um valor inteiro e o sistema produz um ponto da curva com base no valor de entrada. 3.2.2 Especificação Executável Uma das primeiras decisões a serem tomadas na criação de uma especificação executável no ambiente Ptolemy é a escolha de quais atores devem ser utilizados. Isso implica em definir qual é a funcionalidade de cada um e qual será a interação entre eles. Ao final, o conjunto de atores deve capturar o comportamento desejado. No caso do sistema da figura 3.1, tivemos que determinar a associação entre uma expressão do procedimento com um ator na especificação, isto é, determinar a granularidade da função implementada por cada ator. Embora uma solução fosse criar apenas um ator que implemente o procedimento, essa solução trivial não é interessante para o estudo da primitiva, uma vez que implementando−a através de um único ator não iria expor a primitiva às características do modelo computacional. Desta forma, optamos por utilizar uma granularidade a mais fina possível: cada expressão do procedimento é implementada por um ator. A única exceção são as últimas duas expressões do procedimento que são implementados por um único ator. A figura 3.2 apresenta a topologia obtida. O comentário ao lado de cada expressão do procedimento da figura 3.1 indica qual ator implementa a respectiva expressão. Os atores Ramp e Plotter são usados respectivamente para gerar valores e mostrar a curva resultante. Figura 3.2 − A topologia da especificação executável para o exemplo curva borboleta. Uma característica da especificação da figura 3.2 é que apenas atores domain polymorphic [DAV99] foram utilizados, devido à simplicidade da funcionalidade de cada ator. Desta forma, a mesma especificação pode ser utilizada com diferentes modelos computacionais. A única modificação necessária à especificação foi sob o modelo DE, uma vez que esse modelo computacional é baseado no processamento de eventos. Nesse caso um ator que gera eventos periodicamente (Clock) foi usado para iniciar um ciclo de execução. É importante notar que a especificação da figura 3.2 não captura o procedimento da figura 3.1, mas sim o cálculo da curva borboleta, isto porque o modelo computacional do procedimento é uma máquina RAM e outros modelos foram utilizados na especificação da figura 3.2. Por exemplo, mais de um ator pode ser executado simultaneamente utilizando um modelo data flow, o que não ocorre com o procedimento da figura 3.1. Ramp Ramp Ramp Scale1 Ramp Scale2 Ramp Cos1 Ramp Sine Ramp Cos2 Ramp Exp Ramp Mult1 Ramp Scale3 Ramp Mult2 Ramp Mult3 Ramp Add Ramp PtoR Ramp Plotter 22
  • 49. 34 3.2.3 Análise da Execução11 3.2.3.1 SDF A análise da execução de uma especificação usando o modelo SDF é sempre previsível, conforme foi descrito no capítulo 2. A especificação obtida para a primitiva é um grafo homogêneo (todas as taxas de amostragem são iguais à 1), pois a comunicação entre atores corresponde a escrita e leitura de variáveis temporárias (t1, t2, t3) do procedimento mostrado na figura 3.1. Desta forma, cada ator foi ativado uma vez a cada iteração. 3.2.3.2 DDF A seqüência de disparos de atores foi similar ao obtido utilizando o MoC SDF, devido ao escalonador utilizar o conceito de ator deferrable. O ator Ramp ilustra essa situação: uma vez que ele dispara, é classificado como deferrable até que o ator PtoR dispare. Utilizando um escalonador sem a classificação de ator deferrable, o ator Ramp e seus sucessores são disparados sucessivamente, permitindo a existência de paralelismo temporal. Nesse caso, o número máximo de tokens foi observado na conexão Ramp/PtoR, variando entre 7 e 8 tokens. Esse limite está relacionado ao caminho de dados mais longo do grafo a partir do ator Ramp até o ator PtoR. As figuras 3.3 e 3.4 ilustram momentos de uma execução utilizando ou não um escalonador puramente data driven. O número ao lado de alguns atores representa a quantidade de tokens nas respectivas filas de entrada. Figura 3.3 − Situação da execução em um momento inicial utilizando o escalonador com classificação de ator deferrable. Figura 3.4 − Situação da execução em um momento inicial utilizando o escalonador sem classificação de ator deferrable. 3.2.3.3 PN Como no caso do MoC DDF, a conexão Ramp/PtoR foi o empecilho para a presença de paralelismo temporal. Caso não inicializada com um valor diferente, a capacidade inicial de cada fila é 1, só sendo modificada na ocorrência de um deadlock artificial. Modificando esse valor inicial de 1 para 5, a porcentagem de instantes em que o ator Ramp esteve bloqueado caiu de 43 para 8.5. Aumentando esse 11 É importante ressaltar que as análises desse item são referentes às implementações dos modelos computacionais no ambiente Ptolemy. Ramp Ramp Ramp Scale1 Ramp Scale2 Ramp Cos1 Ramp Sine Ramp Cos2 Ramp Exp Ramp Mult1 Ramp Scale3 Ramp Mult2 Ramp Mult3 Ramp Add Ramp PtoR Ramp Plotter 1 2 2 1 1 1 3 Executando Pronto Bloqueado Ramp Ramp Ramp Scale1 Ramp Scale2 Ramp Cos1 Ramp Sine Ramp Cos2 Ramp Exp Ramp Mult1 Ramp Scale3 Ramp Mult2 Ramp Mult3 Ramp Add Ramp PtoR Ramp Plotter 1 1 1 Executando Pronto Bloqueado
  • 50. 35 valor suficientemente obtivemos uma execução similar ao MoC DDF com um escalonador puramente data driven. 3.2.3.4 DE Os atores foram ativados seguindo uma ordem topológica. Esse ordem foi gerada conforme descrito no capítulo 2. Apenas o ator Clock, utilizado para disparar o ator Ramp, gera eventos com atraso. 3.2.3.5 CSP Comparando com o MoC PN, a porcentagem de instantes que um ator esteve bloqueado aumentou, com conseqüente redução do número de instantes em que o mesmo ator esteve pronto. A semântica de comunicação rendezvous explica essa observação. O ator Scale3 ilustra tal situação: após iniciar o envio do token, ele é bloqueado esperando pelo ator Add obter o token. Esse ator também deve consumir tokens enviados pelos atores Mult3 e Exp. Desta forma, o ator Scale3 fica bloqueado durante um período que depende de como o ator Add foi implementado. Essa situação não ocorre com o MoC PN, uma vez que a comunicação é feita através de filas unidirecionais. Observamos que esse exemplo possui uma situação onde um deadlock pode ocorrer, decorrente de como um ator foi implementado e de como a topologia foi construída (vide figura 3.2). O ator Ramp envia um token para quatro outros atores: Scale1, Scale2, Cos1 e PtoR. O ambiente Ptolemy irá enviar tal token através de cada conexão. A ordem em que essas conexões são acessadas irá depender de como a topologia foi criada. Se a conexão entre o ator Ramp e o ator PtoR foi criada antes da conexão para o ator Cos1, a execução entra em deadlock. Isso porque o actor Ramp estaria bloqueado para produção (write blocked) na conexão com o ator PtoR, o actor PtoR estaria bloqueado para consumo (read blocked) na conexão com o actor Add e o esse ator nunca seria executado uma vez que o caminho a partir do ator Cos1 não produziria nenhum token. 3.2.3.6 SR Assim como o modelo SDF, a análise da execução é previsível. A especificação utilizada é composta apenas por atores strict e não possui nenhum ciclo. Desta forma, cada ator foi disparado uma vez, respeitando uma ordem topológica. 3.3 Desvio Condicional A primitiva comportamental desvio condicional é caracterizada pela execução ou não de um determinado bloco de atores com base no valor de uma expressão condicional. Um desvio condicional pode ser composto por mais de um bloco (branch) de atores, cada bloco associado à uma expressão condicional. As condições são verificadas de forma seqüencial, isto é, o resultado final irá depender da ordem em que forem especificadas. Uma vez que alguma condição seja satisfeita, o bloco associado é executado, sem calcular as condições seguintes. Essa primitiva também foi extraída de linguagens de programação imperativa. 3.3.1 Exemplo de Sistema O cálculo do coeficiente angular de uma reta foi o toy benchmark escolhido. A figura 3.5 apresenta a implementação desse exemplo em linguagem C. double coef(int x1, int y1, int x2, int y2) { if(x1 == x2) { // Cond1 return 1.0; // Cte1 } else if(y1 == y2) { // Cond2
  • 51. 36 return 0.0; // Cte2 } else { return (double) (y1 − y2)/(x1 − x2); // Coef } } Figura 3.5 − Descrição em linguagem C do cálculo do coeficiente angular de uma reta. Nesse exemplo, duas igualdades são as condições a serem computadas e três blocos de atores são utilizados. A entrada para o sistema é o valor de dois pontos ( (x1,y1) e (x2,y2) ) da reta e o sistema produz como saída o valor do coeficiente angular. 3.3.2 Especificação Executável A criação de uma especificação executável que capture a primitiva comportamental desvio condicional foi mais complexa com relação à primitiva anterior. Ao total, cinco diferentes especificações foram criadas. Descreveremos separadamente as soluções obtidas para cada modelo computacional estudado. 3.3.2.1 SDF O modelo SDF apresenta uma regra que dificulta a captura da primitiva: cada ator deve consumir e produzir um número fixo de tokens, sendo esses valores especificados a priori. Desta forma, a cada iteração, um ator deve ser ativado pelo menos uma vez. Ao contrário, para capturar adequadamente a primitiva, apenas um dos possíveis blocos de atores deve ser executado para cada conjunto de entradas. A topologia da primeira especificação desenvolvida para capturar a primitiva utilizando o MoC SDF é ilustrada na figura 3.6. Figura 3.6 − Topologia da primeira especificação em SDF. A topologia é composta pelos seguintes atores: Cte1 e Cte2: dois atores que produzem um valor constante (e especificado por um parâmetro) toda vez que são ativados; Coef: calcula a expressão do coeficiente angular; Comp1 e Comp2: produzem um valor igual à 1 no porto de saída caso o valor das entradas sejam iguais, do contrário produz um valor 0; Mux: recebe os resultados de todos os outros atores e produz o resultado final do desvio. A especificação criada pôde ser capturada no modelo SDF, pois todos os atores consomem e/ou produzem um token em cada conexão. Parte do comportamento da primitiva foi capturado pelos atores Comp1 Comp2 Cte1 Cte2 Coef Mux X1 X2 Y1 Y2
  • 52. 37 Comp1 e Comp2 e parte pelo ator Mux. Embora fosse possível implementar o ator Mux utilizando um comando tipo if−then−else presente na linguagem Java, isso foi evitado utilizando uma matriz associando o par de valores condicionais a um porto de entrada. A figura 3.7 mostra o corpo desse ator. 1− ... 2− IntToken t1 = (IntToken) v1.get(0); 3− IntToken t2 = (IntToken) v2.get(0); 4− DoubleToken t3 = (DoubleToken) v3.get(0); 5− 6− _table[1][0] = t1.intValue(); 7− _table[1][1] = _table[1][0]; 8− 9− _table[0][1] = t2.intValue(); 10− _table[0][0] = t3.doubleValue(); 11− 12− int index1 = ((IntToken) ctrl1.get(0)).intValue(); 13− int index2 = ((IntToken) ctrl2.get(0)).intValue(); 14− 15− output.broadcast( 16− new DoubleToken(_table[index1][index2])); 17− ... Figura 3.7 − Trecho do código do ator Mux. De maneira geral, a estratégia utilizada na especificação da figura 3.6 é valida para capturar a primitiva no MoC SDF. Entretanto, essa solução não é totalmente satisfatória, pois todos os atores implementando os blocos e condições são sempre ativados e somente em seguida uma decisão é tomada. Além desse desvantagem, o exemplo do coeficiente angular apresenta uma característica particular: o terceiro caminho só pode ser executado quando a condição (x1 == x2) for falsa. Caso contrário, uma divisão por zero irá ocorrer, gerando um erro fatal. Uma possível solução seria adicionar como entrada do ator Coef o valor da primeira condição calculada pelo ator Comp1, e utilizar um comando if−then−else dentro do ator. Para obter uma especificação válida para o cálculo do coeficiente angular, implementamos outra solução utilizando o conceito de função high−order [HUD89], ou seja, uma função que tem como parâmetros funções e/ou que retorna como resultado outras funções. A topologia da especificação obtida é mostrada na figura 3.8. Figura 3.8 − Topologia da segunda especificação utilizando o MoC SDF. O atores são: Comp1 e Comp2: mesma função que na especificação da figura 3.7; Map: recebe o valor de ambas as condições e determina qual deve ser a função a ser executada. Uma vez determinada, a função é inserida em um token do tipo objeto (ObjectToken) e enviada ao ator Apply. Apply: lê os valores das entradas (parâmetros da função) e executa a função recebida. Como duas funções diferentes devem ser executadas (geração de constantes e cálculo da expressão do coeficiente), utilizamos um objeto do tipo Interface da linguagem Java para transmitir a função do ator Map ao ator Apply. A figura 3.9 apresenta o código desse objeto: package ptij.domains.sdf.demo.ifelse2; public interface Function { public double apply(int x1, int x2, int y1, int y2); } Figura 3.9 − Interface para uma função do exemplo do coeficiente angular Comp1 Comp2 X1 X2 Y1 Y2 Map Apply X1 X2 Y1 Y2
  • 53. 38 Cada diferente função implementa a Interface da figura 3.9, definindo o código da função apply. Como a expressão para o cálculo do coeficiente necessita dos valores de ambos os pontos, estes estão incluídos como parâmetros da função apply. A implementação da geração de um valor constante ignora esses parâmetros. Isto também é válido para o ator Apply, que deve ter como entradas a união dos parâmetros de todas as funções que podem ser executadas. A necessidade de adicionar entradas, mesmo que desnecessárias para algumas funções, é um ponto negativo da especificação da figura 3.8, isto porque dados serão recebidos desnecessariamente. 3.3.2.2 DDF O MoC DDF permite a variação em tempo de execução da quantidade de tokens recebidos e enviados por um ator. Desta forma, desenvolvemos novas especificações utilizando tal característica. A figura 3.10 mostra a topologia da primeira especificação executável. Figura 3.10 − Topologia da primeira especificação utilizando o modelo DDF. Os atores Comp1, Comp2, Cte1, Cte2 e Coef implementam as mesmas funções que nas especificações anteriores. A única modificação necessária foi a inclusão de um porto de entrada (Trigger) aos atores Cte1 e Cte2. A finalidade deste porto é controlar o ativamento dos respectivos atores. Dois novos atores foram desenvolvidos: Switch e Select (vide item 2.3.5). O ator Switch é responsável por enviar os dados da entrada do sistema aos respectivos atores, com base no valor das condições. O ator Select determina qual é o porto de entrada que contém o próximo token, a partir do valor das condições. O ponto favorável da especificação da figura 3.10 é que um bloco do sistema só é executado quando a expressão condicional associada à ele for verdadeira. Dois pontos negativos podem ser observados: todos os valores de entrada dos blocos do sistema devem passar pelo ator Switch e ambas as condições são sempre computadas. Com o intuito de remover esses problemas, a especificação ilustrada na figura 3.11 foi criada. Comp1 Comp2 X1 X2 Y1 Y2 Switch Ramp Coef Cte2 Cte1 Select Trigger Trigger
  • 54. 39 Figura 3.11 − Topologia da segunda especificação utilizando o MoC DDF. A especificação da figura 3.11 contém os seguintes atores: Cte0, Cte1 e Cte2: igual à especificação da figura 3.10; Coef: como nas outras especificações; Comp1 e Comp2: como nas outras especificações; D: ator que implementa um demultiplexador. Com base em um token de controle (na figura 3.11, é a entrada mostrada no lado superior ou inferior do retângulo) determina para qual porto de saída o dado de entrada deve ser enviado. M: similar ao ator D, mas implementando uma função de multiplexador, ou seja, com base no token de controle, determina qual entrada deve ser a próxima a ser lida. A especificação da figura 3.11 calcula uma condição apenas quando as anteriores falharam. Os dados Y1 e Y2 são enviados ao ator Comp2 apenas quando a condição C1 é 0 (falsa), desta forma, controlando o ativamento do ator. No caso dos atores Cte1 e Cte2, o resultado dos atores condicionais roteia um token de controle (gerado pelo ator Cte0). No caso do ator Coef, os atores condicionais eliminam ou não os dados necessários pelo ator. Essa eliminação é necessária (também no caso do ator Comp2) devido às filas unidirecionais utilizadas em cada conexão: caso um ator não fosse ativado, e os dados não fossem descartados, ele iria receber na próxima execução dados antigos. 3.3.2.3 PN Todas as especificações mostradas até o momento são válidas para o MoC PN. Nenhuma outra foi desenvolvida utilizando características específicas do mesmo. 3.3.2.4 CSP As especificações mostradas anteriormente também podem ser usadas com o MoC CSP. Embora esse MoC não utilize filas entre atores para a comunicação dos dados, todas as especificações transmitem Comp1 X1 X2 D 0 1Cte0 D 0 1 D 0 1 Comp2 D 0 1 Cte1 Cte2 M 0 1 M 0 1 Ramp CoefD 0 1 D 0 1 Y1 Y2 C1 D 0 1 D 0 1 D 0 1 D 0 1 D 0 1 D 0 1 X1 X2 Y1 Y2 C1 C1 C1 Trigger Trigger
  • 55. 40 apenas um token em cada conexão. Neste MoC, fizemos uma tentativa de alteração da especificação da figura 3.11, a fim de torná− la mais eficiente. Os dois atores multiplexer (M) foram substituídos por um ator com comportamento não determinístico (MuxND da figura 3.13). A figura 3.12 mostra parte do código desse ator. 1− ConditionalBranch [] branches = new ConditionalBranch[3]; 2− 3− branches[0] = new ConditionalReceive(true, a, 0, 0); 4− branches[1] = new ConditionalReceive(true, b, 0, 1); 5− branches[2] = new ConditionalReceive(true, c, 0, 2); 6− 7− int result = chooseBranch(branches); 8− if(result == 0) { 9− output.broadcast(branches[0].getToken()); 10− } 11− else 12− if(result == 1) { 13− output.broadcast(branches[1].getToken()); 14− } 15− else 16− if(result == 2) { 17− output.broadcast(branches[2].getToken()); 18− } Figura 3.12 − Trecho de código do ator MuxND no MoC CSP. Para implementar o comportamento do ator MuxND, utilizamos o comando de comunicação guarded. Da linha 1 à linha 5 do código da figura 3.12, as três possibilidades são criadas, uma para cada porto de entrada do ator (a, b, c). A expressão condicional de cada comando de comunicação é sempre verdadeira, desta forma, evitando uma seqüência fixa de verificação dos portos de entrada. A inclusão do ator MuxND pretendia eliminar a necessidade de comunicar os valores condicionais aos atores multiplexer da especificação. Esses atores são utilizados para combinar os resultados dos blocos de atores do desvio condicional, enviando o resultado para a saída do sistema. Como apenas um dos três possíveis caminhos pelos atores do corpo do desvio condicional (Cte1, Cte2 e Coef) é utilizado para um dado conjunto de entradas, não seria necessário enviar os valores condicionais aos atores multiplexer. O problema com a modificação adotada é que ela torna o comportamento da especificação não determinístico. Isso ocorre pois não é possível garantir que a propagação dos resultados de um conjunto de entrada irá terminar antes da propagação de outro conjunto posterior devido ao escalonamento (desconhecido e aleatório) dos diferentes processos. Na figura 3.11, uma vez que o primeiro bloco de atores D tenha lido os valores C1, X1, X2, Y1, Y2, outro conjunto de entradas pode ser propagado, isto porque o ator Comp1 e os atores que geram os valores (X, Y) estão desbloqueados (terminaram o rendezvous) . Caso o próximo grupo de entradas seja propagado pelo ator Cte1, não é possível garantir a ordem final dos resultados. 3.3.2.5 DE Todas as especificações anteriores são válidas nesse MoC. Entretanto, utilizando as características particulares desse MoC, desenvolvemos uma nova especificação. A figura 3.13 apresenta a topologia desta especificação. Figura 3.13 − Topologia da especificação no MoC DE. Comp1 Comp2 Cte1 Cte2 Coef MuxND X1 X2 Y1 Y2 X1 X2 Y1 Y2 Trigger
  • 56. 41 Os atores do corpo do desvio, Cte1, Cte2 e Coef permanecem com a mesma função. Apenas o ator Coef foi modificado: quando um evento está presente em um dos portos de entrada, ele é armazenado em uma variável interna. A figura 3.14 mostra um trecho de código deste ator. 1− ... 2− if(x1.hasToken(0)) { 3− _x1 = ((IntToken)x1.get(0)).intValue(); 4− } 5− ... 6− if(trigger.hasToken(0)) { 7− trigger.get(0); 8− output.broadcast(new DoubleToken( (_y1 − _y2)/(_x1 − _x2) )); 9− } 10− ... Figura 3.14 − Trecho de código do ator Coef no MoC DE. Quando um evento está presente no porto Trigger (figura 3.13), um novo valor de saída é computado com base nos valores armazenados dos dados. Os atores que calculam os valores condicionais do desvio, Comp1 e Comp2, possuem na nova especificação três portos de entrada (o porto Trigger do ator Comp1 é ignorado) e dois de saída. Além dos dois valores de dados a serem comparados, um novo porto de entrada, utilizado para ativar o cálculo da comparação (mesma técnica do trecho de código da figura 3.14), foi incluído. Dois portos de saída são utilizados, um para cada condição. Apenas um dos dois portos terá um evento para cada par de valores de entrada. O ator MuxND foi utilizado nessa especificação: ao receber um evento em qualquer porto de entrada, o mesmo é enviado para o porto de saída. Caso existam eventos simultâneos, a ordem será conforme os portos de entrada são verificados. 3.3.2.6 SR Utilizando atores strict, é possível implementar as especificações das figuras 3.6, 3.8, 3.10, 3.11. A especificação da figura 3.13 também pode ser utilizada. Nesse caso, o ator MuxND passa a ser um ator non−strict, isto é, tão logo um evento em um dos portos de entrada esteja presente, o evento no porto de saída nesse instante será o valor lido. A figura 3.15 mostra o método fire() desse ator. 1− public void fire() throws IllegalActionException { 2− if(a.present(0) !output.known(0)) { 3− output.broadcast(a.get(0)); 4− } 5− 6− if(b.present(0) !output.known(0)) { 7− output.broadcast(b.get(0)); 8− } 9− 10− if(c.present(0) !output.known(0)) { 11− output.broadcast(c.get(0)); 12− } 13− } Figura 3.15 − Método fire() do ator MuxND no MoC SR. É importante notar que o ator MuxND, implementado como mostra a figura 3.15, não é um ator monotônico12 , portanto, sua utilização no MoC SR é inválida. Foi possível executar a modelo no ambiente Ptolemy, pois a implementação que fizemos do MoC SR não efetua nenhum tipo de verificação de consistência. Entretanto, podemos (o usuário) garantir que mesmo com esse ator, a especificação captura o comportamento de forma adequada (determinística). Isso se deve à mutua exclusão presente na primitiva de desvio condicional: é garantido que em um determinado instante, apenas um dos sinais (a, b, c) estará presente, tornando o código da figura 3.15 válido. 12 Na literatura, esse ator (e variantes) é denominado nondeterminate merge [PAN92][LEE95], e foi demonstrado que seu comportamento não é monotônico [BRO81].
  • 57. 42 3.3.3 Análise da Execução 3.3.3.1 SDF Como todas as especificações para a primitiva de desvio condicional são homogêneas, a execução ativou cada ator respeitando o escalonamento, que nesse caso é uma ordem topológica do grafo. 3.3.3.2 DDF As figuras 3.16 e 3.17 apresentam um exemplo da evolução do estado do ator Comp2 para as especificações das figuras 3.10 e 3.11 respectivamente. O eixo da abcissas indica um instante da execução e o das ordenadas o estado do ator. Nota−se o ativamento constante e repetido do ator para quatro conjuntos de valores de entrada no caso da especificação da figura 3.10. Já para a especificação da figura 3.11, após o segundo conjunto de dados, o ator permanece um longo período bloqueado (sem dados). Nesse período, dois conjuntos de dados foram aplicados e que não necessitavam do cálculo da segunda expressão condicional. Figura 3.16 − Exemplo da evolução do estado do ator Comp2 para a especificação da figura 3.10 no MoC DDF. Figura 3.17 − Exemplo da evolução do estado do ator Comp2 para a especificação da figura 3.11 no MoC DDF. Embora a especificação da figura 3.11 seja mais bem elaborada, notamos que a especificação da figura 3.10 teve um tempo de execução menor (100ms em média) que a especificação da figura 3.11 (220ms em média). Isso pode ser explicado pelo maior número de atores na segunda especificação, com
  • 58. 43 conseqüente aumento do overhead imposto pelo escalonamento dinâmico. Outro ponto relevante à diferença no tempo de execução é a simplicidade do código de cada ator, principalmente do código dos atores Comp1 e Comp2. Caso tais atores envolvessem funções mais complexas, a especificação da figura 3.11 passaria a ser mais vantajosa. 3.3.3.3 PN Conforme foi mencionado no capítulo 2, uma característica que diferencia esse MoC com relação ao MoC DDF é a utilização de processos executando de forma contínua e ininterrupta ao invés de ativamentos atômicos de atores. Isso pode ser observado comparando−se a figura 3.18 com a figura 3.17: no caso da figura 3.18, a execução foi fragmentada em vários instantes. O máximo intervalo contínuo em que o ator esteve executando foi por 6 instantes, mas a maioria foi por 1 instante. Figura 3.18 − Exemplo da evolução do estado do ator Comp2 para a especificação da figura 3.11 no MoC PN. Nenhuma observação adicional pôde ser feita quando o parâmetro do tamanho inicial das filas de comunicação foi alterado. 3.3.3.4 CSP Observamos novamente o problema de deadlock no MoC CSP. O ator D da figura 3.11 tinha sido originalmente implementado de forma a obter o primeiro token de controle e em seguida o dado a ser roteado. No caso dos modelos data flow e no modelo PN, isso não acarretou problemas. No modelo CSP, ocorreu um deadlock, pois o ator que gerava os valores (X,Y) estava bloqueado tentando enviar o valor X1 para um ator D, este esperando o valor de controle gerado pelo ator Comp1, que não podia produzir um resultado enquanto não obtivesse o valor X2. Modificamos a implementação do ator D, e a especificação não apresentou mais deadlocks. 3.3.3.5 DE Como no exemplo da primitiva seqüência de expressões, apenas o Clock gera eventos com atraso. Nenhuma característica particular pôde ser observada, apenas verificamos o comportamento adequado da especificação da figura 3.13. 3.3.3.6 SR Nenhuma característica relevante foi observada. O escalonamento é trivial (ordem topológica) e a especificação é válida. Como foi destacado no item 3.3.2.6, observou−se que os sinais a, b e c do ator MuxND nunca possuem eventos simultaneamente.
  • 59. 44 3.4 Iteração com Duração Fixa A primitiva comportamental de iteração com duração fixa é a repetição sucessiva de um bloco de atores. O número de repetições é determinado no momento da criação da especificação. Essa primitiva é baseada em comandos de repetição (laços). 3.4.1 Exemplo de Sistema Escolhemos como exemplo de sistema a multiplicação de duas matrizes de ordem N. A figura 3.19 mostra o trecho de código utilizado. 1− for(int i = 0;i N;i++) { 2− for(int j = 0;j N;j++) { 3− for(int k = 0;k N;k++) { 4− res[i][j] += data[i][k] * cT[k][j]; 5− } 6− } 7− } Figura 3.19 − Multiplicação de duas matrizes de ordem N. O exemplo utiliza três iterações encadeadas (linha 1 até 3), cada uma com um contador (i, j, k) diferente. Desta forma, o corpo da iteração (linha 4) é repetido N3 vezes. Utilizamos no estudo da primitiva matrizes de ordem 8. 3.4.2 Especificação Executável A primeira especificação que desenvolvemos pôde ser utilizada com todos os seis modelos computacionais, pois a exemplo da especificação da figura 3.2, utilizamos somente atores que consomem e produzem um token por cada porto de entrada/saída. A figura 3.20 apresenta a topologia desta especificação. Figura 3.20 − A primeira especificação para a primitiva de iteração fixa. A função de cada ator da especificação é: Ramp: gera uma seqüência crescente de valores inteiros, com incremento igual à 1; Modulo: calcula o módulo do valor de entrada por um valor especificado como parâmetro; Divide1 e Divide2: calcula o módulo da divisão do valor de entrada por um valor especificado como parâmetro. O valor do módulo é o mesmo do divisor utilizado; Ct e Data: são atores que armazenam uma matriz (especificada como um parâmetro). Dado um valor de linha (R) e coluna (C), produz o valor da posição correspondente na matriz; Mult: multiplica o valor de dois tokens; AccumAdd: calcula a soma de N (parâmetro) números. A cada valor de entrada, gera o resultado parcial; Res: utilizado para armazenar um novo valor em uma matriz. i j kRamp Divide1 Modulo Divide2 AccumAdd ResMult R R C C CT Data
  • 60. 45 A geração dos contadores da iteração da figura 3.16 é implementada pelos atores Ramp, Divide1, Divide2 e Modulo. Os outros atores representam o corpo da iteração. Cada iteração é iniciada com o disparo do ator Ramp. Como o exemplo utiliza três laços encadeados, o valor do contador j deve permanecer o mesmo enquanto o contador k vai de 0 até N. O mesmo deve ocorrer com o contador i em relação ao contador j. Essa é a razão da utilização dos atores Divide1 e Divide2. O primeiro é configurado com valor N = 8 e o segundo com valor N = 64. Outra possível implementação para a geração dos contadores da figura 3.20 é mostrada na figura 3.21. Figura 3.21 − Implementação alternativa para a geração dos índices da iteração. Neste caso, cada índice é gerado utilizando−se dois atores: um ator de atraso (D) e o ator IndexGen. O ator IndexGen é responsável por calcular os novos valores dos contadores com base nos valores anteriores. O valor inicial para cada ator de atraso é 0. A função desse ator é armazenar o valor anterior do contador, ou seja, a topologia da figura 3.21 está implementando o conceito de estado de maneira explícita. O principal problema com a especificação da figura 3.20 é a produção desnecessária do valor dos contadores (por exemplo, o contador i é desnecessariamente repetido por 63 vezes), ou seja, atores são desnecessariamente ativados. Utilizamos características específicas dos diferentes modelos computacionais para aprimorar a especificação. Descreveremos os resultados obtidos. 3.4.2.1 SDF O modelo computacional SDF permite uma iteração implícita de atores através da especificação de valores de consumo e produção de tokens variados. Considere o exemplo: um ator A produz dados para um ator B. Caso seja especificado que o ator A produz um token a cada ativação e que o ator B consome dez, então o ator A será ativado dez vezes mais que o ator B. A figura 3.22 mostra a especificação executável utilizando o modelo SDF. Figura 3.22 − A iteração com duração fixa no MoC SDF. Com relação à figura 3.20, apenas a geração dos contadores foi modificada. Ela é feita através de três atores Sequencer. Esse tipo de ator gera uma seqüência crescente de inteiros até um valor especificado (parâmetro) e então reinicia a seqüência a partir de 0. Os números próximos a cada conexão são as taxas de amostragem dos respectivos portos. Por exemplo, foi especificado que para cada token IndexGen i k j D D D 1 Sequencer2 Sequencer1 Sequencer3 AccumAdd ResMult R R C C CT Data 1 1 8 1 1 64 1 1 81 1 8 8 1 8 64
  • 61. 46 que o ator Data consome proveniente do ator Sequencer1, 64 tokens devem ser consumidos provenientes do ator Sequencer1. Nessa especificação, não há produção redundante de tokens. 3.4.2.2 DDF Implementamos outra solução permitindo que alguns valores de consumo/produção não fossem especificados, como no caso dos atores CT e Data. Neles, a utilização da leitura bloqueante através do comando waitFor() pode ser empregada como forma de garantir o consumo da quantidade correta de tokens em cada porto. A figura 3.23 apresenta um trecho de código de um desses atores para ilustrar essa solução. 1− .... 2− public void fire() throws IllegalActionException { 3− 4− if(_state == 0) { 5− if(_rowCount == 1) { 6− _Port0Token = (IntToken) row.get(0); 7− waitFor(col, 1); 8− } 9− else { 10− _Port0Token = (IntToken) col.get(0); 11− waitFor(row, 1); 12− } 14− _state = 1; 15− } 16− else { 17− IntToken it; 18− if(_rowCount == 1) { 19− it = (IntToken) col.get(0); 20− data.broadcast(new IntToken( 21− _data[_Port0Token.intValue()][it.intValue()])); 22− 23− _state++; 24− if(_state == _colCount) { 25− waitFor(row, 1); 26− _state = 0; 27− } 28− else { 29− waitFor(col, 1); 30− } 31− } 32− else { 33− it = (IntToken) row.get(0); 34− data.broadcast(new IntToken( 35− _data[it.intValue()][_Port0Token.intValue()])); 36− 37− _state++; 38− if(_state == _rowCount) { 39− waitFor(col, 1); 40− _state = 0; 41− } 42− else { 43− waitFor(row, 1); 44− } 45− } 46− } 47− } 48− .... Figura 3.23 − Trecho de código dos atores CT e Data. As variáveis _rowCount e _colCount armazenam a quantidade de tokens a serem consumidos nos portos row e col, respectivamente. Uma dessas variáveis deve ter o valor igual à 1, enquanto que a outra pode apresentar qualquer valor maior ou igual a 1. A variável _state é utilizada para determinar de qual porto o próximo token deve ser lido. 3.4.2.3 PN A especificação empregada no MoC DDF também foi utilizada nesse MoC. Como não é um
  • 62. 47 modelo data flow, nenhuma taxa de amostragem é especificada. Desta forma, o código dos atores CT e Data foram adaptados. A figura 3.24 mostra essa modificação. 1− if(_rowCount == 1) { 2− r = (IntToken) row.get(0); 3− for(int i = 0;i _colCount;i++) { 4− c = (IntToken) col.get(0); 5− data.broadcast(new IntToken(_data[r.intValue()][c.intValue()])); 6− } 7− } 8− else { 9− c = (IntToken) col.get(0); 10− for(int i = 0;i _rowCount;i++) { 11− r = (IntToken) row.get(0); 12− data.broadcast(new IntToken(_data[r.intValue()][c.intValue()])); 13− } 14− } Figura 3.24 − Código dos atores Data e CT no MoC PN. 3.4.2.4 CSP A especificação empregada no MoC PN também é válida nesse modelo. Nenhuma modificação foi necessária. 3.4.2.5 DE A topologia da especificação utilizada nesse modelo foi basicamente a mesma da figura 3.22, com apenas a inclusão de um ator tipo Clock, como nos exemplos das primitivas anteriores, conectado à cada ator Sequencer. Entretanto, o código dos atores Sequencer, MatrixHolder, AccumAdd e Res teve que ser alterado. Isso se deve à semântica de comunicação no MoC DE, diferente dos MoCs anteriores. Utilizaremos o ator Sequencer como exemplo. A figura 3.25 mostra o código desse ator no MoC PN e a figura 3.26 no modelo DE. 1− .... 2− public void fire() throws IllegalActionException { 3− output.broadcast(new IntToken(_state)); 4− _state = (_state + 1) % _limit; 5− } 6− .... Figura 3.25 − Trecho do ator Sequencer no MoC PN. 1− .... 2− public void fire() throws IllegalActionException { 3− 4− if(input.hasToken(0)) { 5− input.get(0); 6− _counter++; 7− if(_counter == _limit) { 8− output.broadcast(new IntToken(_state)); 9− _state = (_state + 1) % _to; 10− _counter = 0; 11− } 12− } 13− } 14− .... Figura 3.26 − Trecho do ator Sequencer no MoC DE. No código da figura 3.25, o comando na linha 3 cria um novo token com o valor atual da seqüência e envia tal token para os atores conectados. A atualização do valor da seqüência é feita na linha 4. Note a utilização de uma variável de estado interna. Esse código é válido no MoC PN devido à utilização de filas nas conexões, o que armazena os valores produzidos, e a semântica de escrita bloqueante. Não é possível utilizar esse trecho no MoC DE, pois a cada execução do ator um novo dado é gerado. No código da figura 3.26, o comando na linha 4 verifica se há um token no porto input. Este
  • 63. 48 porto é utilizado para ativar o ator. O valor lido não é relevante (linha 5). Um contador (linha 6) é utilizado para determinar quando um novo dado deve ser produzido. Esse limite (linha 7) é determinado pelo usuário. Desta forma, é possível que diferentes atores Sequencer gerem dados a taxas diferentes. Note que uma outra alternativa para a implementação de um contador seria como na figura 3.21, ao invés de utilizar uma variável interna. É interessante notar que o trecho da figura 3.26 é valido nos modelos PN, CSP e DDF, pois nesses casos é possível que um ator não produza nenhum dado em determinados momentos. Um aprimoramento que pode ser feito é que ao invés de conectar o ator Clock a todos os atores Sequencer, fazê−lo apenas com o ator Sequencer1 e modificar tais atores para que quando a seqüência atingir o valor máximo, gerar um evento em um novo porto de saída. Desta forma, conectando os atores Sequencer de maneira encadeada, teremos uma especificação válida e que não escalona nenhum ator de forma desnecessária. 3.4.2.6 SR A especificação utilizada é igual à do modelo DE, a menos do ator Clock. As modificações feitas nos atores Sequencer, MatrixHolder, AccumAdd e Res para o modelo DE também foram necessárias. Esses modificações utilizaram os comandos próprios desse MoC, como o comando makeAbsent() para tornar um valor de saída ausente em um determinado instante. 3.4.3 Análise da Execução 3.4.3.1 SDF A única observação feita durante a execução das duas especificações é que a segunda (figura 3.22) utilizou filas de capacidade maior. Por exemplo, a conexão Sequencer1 para o ator Data necessita de uma fila com 64 posições. Como esse MoC é escalonado de forma estática, isso já era previsível. O aumento do tamanho das filas está ligado à duração de cada iteração e ao nível de encadeamento de uma iteração. 3.4.3.2 DDF Conforme descrito no item 3.3.2.2, utilizamos a leitura bloqueante através do comando waitFor() para controlar a capacidade máxima necessária das filas de comunicação. A figura 3.27 apresenta a evolução do estado do ator Data e do ator Accum. Figura 3.27 − Evolução do estado dos atores Data (em azul) e Accum (em verde). É possível observar que após o ator Data obter o valor do contador i (primeiro pico) , ele obtêm um valor do contador k e produz um novo valor de saída, para cada ativação. Quando oito valores são criados, o ator Accum é ativado. Utilizando essa especificação, a capacidade máxima necessária da fila entre os atores Sequencer1 e Data caiu de 64 para 1. Em contrapartida, o ator foi escalonado por um maior número de vezes, o que
  • 64. 49 pode acarretar um overhead adicional. Entretanto, é possível explorar outras configurações dos parâmetros capacidade máxima das filas/número de ativamentos do ator. Isso pode ser feito modificando−se o segundo parâmetro do comando waitFor(). A figura 3.28 apresenta um exemplo onde o valor do segundo parâmetro foi aumentado de 1 para 4 (linhas 29 e 43 do código da figura 3.23). Figura 3.28 − Evolução do estado dos atores Data e Accum com a alteração do parâmetro do método waitFor(). Observando−se a figura 3.28, o ator Data foi escalonado três vezes, ao invés de nove. Entretanto, a capacidade máxima da fila entre os atores Sequencer1 e Data subiu de 1 para 4. 3.4.3.3 PN Variamos o valor do tamanho inicial de cada fila de comunicação. Aumentado esse valor, a porcentagem de instantes em que cada ator esteve em cada um dos três possíveis estados permaneceu muito similar. Notamos que a partir de um determinado instante, vários atores estiveram bloqueados devido à falta de espaço na fila de comunicação. O aumento do valor do tamanho inicial de cada fila apenas retardou o instante em que esses atores começaram a ficar bloqueados. A figura 3.29 ilustra a evolução do estado do ator Data quando o tamanho inicial das filas é 1, e a figura 3.30 quando o tamanho inicial é 8. Figura 3.29 − Evolução do estado do ator Data quando o tamanho inicial das filas é 1. Figura 3.30 − Evolução do estado do ator Data quando o tamanho inicial das filas é 8. 3.4.3.4 CSP
  • 65. 50 Notamos novamente que a porcentagem de instantes em que os atores estiveram bloqueados aumentou, com relação ao MoC PN. Nenhum deadlock foi detectado. 3.4.3.5 DE O escalonamento obtido foi trivial (ordem topológica), uma vez que as especificações utilizadas são sem atraso. Essa situação também ocorreu nas especificações para as outras duas primitivas. Observamos uma situação que possibilita otimizar o escalonador. Quando existirem vários eventos com um mesmo timestamp, a ordem dos eventos é decidida com base no valor de prioridade, calculado através da ordenação topológica do grafo da especificação. Isto significa que nesta situação, a ordem de ativação é conhecida antes da execução. Desenvolvemos então o seguinte escalonador : 1. caso a fila de eventos esteja vazia ou o tempo final foi superado, vá para o passo 9; 2. remova o próximo evento da fila; 3. atualize o valor de tempo atual para o do evento obtido no passo 2; 4. determine para qual ator o evento é destinado e envie o respectivo token; 5. remova da fila todos os eventos que tenha o timestamp igual ao tempo atual, enviando os respectivos tokens aos atores correspondentes; 6. respeitando uma ordem topológica, ative cada ator que tenha algum token em um porto de entrada até que ele tenha consumido dos os tokens. Envie os eventos produzidos com timestamp maior que o tempo atual para a fila de eventos. Envie os eventos produzidos sem atraso para os respectivos atores; 7. caso existam atores com tokens nos portos de entrada, vá para o passo 6; 8. vá para o passo 1. 9. fim. O escalonador modificado ativa os atores em um dado timestamp de forma até que nenhum ator esteja produzindo mais eventos. Os eventos para um tempo futuro são introduzidos na fila de eventos, enquanto que os eventos sem atraso são enviados diretamente para os portos dos atores. A validade desse escalonador é garantida pelo respeito à ordenação topológica. A principal vantagem dessa modificação é reduzir o número de eventos da fila central do escalonador. A tabela 3.1 apresenta alguns dados comparativos. Tabela 3.1 − Comparação dos escalonadores do MoC DE. Atores com atraso Atores sem Atraso Eventos na Fila Escalonador Original Eventos na fila Escalonador Modificado Tempo de Execução (ms) Escalonador Original Tempo de Execução (ms) Escalonador Modificado 6 28 27131 8662 5272 4666 1 14 20021 1001 4468 3150 3 3 9884 1945 5317 4620 A tabela 3.1 mostra uma redução considerável do tráfego de eventos na fila do escalonador. Consequentemente, há uma redução no tempo de execução, pois algum overhead relacionado à manipulação da fila é removido. É interessante notar que o escalonador modificado é bastante similar ao escalonador do MoC SR. Isso ocorre pois podemos relacionar um instante do MoC SR com um valor de timestamp do MoC DE. 3.4.36 SR Nenhuma característica adicional foi observada. O escalonamento é trivial. 3.5 Outras Primitivas Comportamentais
  • 66. 51 Além das três primitivas descritas e analisadas nos itens anteriores, enumeramos outras primitivas comportamentais relevantes à captura de sistemas. Iremos descrever o comportamento de cada primitiva e apresentar um toy benchmark. 3.5.1 Sincronismo A primitiva comportamental de sincronismo corresponde à especificação em que determinado bloco de atores deve ser ativado antes ou depois de outro bloco, ou seja, impor uma restrição na ordem da ativação de atores em determinadas situações. Essa primitiva é amplamente encontrada em especificações de sistemas concorrentes. Em linguagens de programação seqüenciais, ou em modelos computacionais não concorrentes, essa primitiva é utilizada de forma implícita, pois ao criar a especificação, a ordem da execução entre os comandos (atores) é fixada. A figura 3.31 apresenta uma implementação do algoritmo Discrete Cosine Transform (DCT) que pode ser utilizado para estudar a primitiva. A entrada do sistema é uma matriz (data, linha 6) e a saída outra matriz (outv, linha 16). 1− for(i = 0; i _N; i++) { 2− for(j = 0; j _N; j++) { 3− temp[i][j] = 0.0; 4− for(k = 0; k _N; k++) { 5− temp[i][j] += (((int)(data[i][k]) − 128) * _cT[k][j]); 6− } 7− } 8− } 9− 10− for(i = 0; i _N; i++) { 11− for(j = 0; j _N; j++) { 12− temp1 = 0.0; 13− for(k = 0; k _N; k++) { 14− temp1 += (_c[i][k] * temp[k][j]); 15− } 16− outv[i][j] = (int) Math.round(temp1); 17− } 18− } Figura 3.31 − Discrete Cosine Transform: exemplo para a primitiva de sincronismo. O algoritmo DCT é composto por duas multiplicações de matrizes de ordem N (linhas 1 à 8 e 10 à 18), ou seja, por dois blocos de atores que implementam a primitiva do item 3.3. A primitiva de sincronismo se manifesta no exemplo da figura 3.31, pois a segunda multiplicação utiliza o resultado da primeira, ou seja, existe uma dependência de dados entre a primeira e a segunda multiplicação. A captura do exemplo da figura 3.31 implica em sincronizar as duas multiplicações. Nesse exemplo, isso pode ser feito de maneira implícita gerando os contadores da segunda multiplicação atrelados aos contadores da primeira multiplicação, ou de forma explícita através de novos atores para controlar as ativações/geração de eventos. 3.5.2 Compartilhamento de Recursos Um dos fatores que limita a implementação de um sistema é a disponibilidade de um recurso, como por exemplo área no caso de um circuito integrado ou a quantidade de memória em um sistema microprocessado. Desta forma, o compartilhamento de recursos é amplamente utilizado e deve ser capturado por uma especificação executável. Um exemplo para capturar essa primitiva é o problema dos Dinning Philosophers13 [HOA78]. Neste exemplo, o recurso a ser compartilhado são os garfos disponíveis. É um exemplo clássico de programação concorrente e foi criado inicialmente para o estudo de propriedades como liveness e fairness. Além disso, esse exemplo foi utilizado para o estudo de primitivas de sincronismo. Podemos considerar essa primitiva como sendo uma outra forma de sincronismo (neste caso, não temos 13 Um fator para essa escolha é que, além da relevância e adequação do problema, uma especificação utilizando o MoC CSP que implementa uma solução está disponível no ambiente Ptolemy II.
  • 67. 52 dependências entre os processos disputando os recursos). 3.5.3 Concorrência A concorrência é uma característica básica de qualquer sistema embutido. Todas as especificações que foram criadas nesse trabalho utilizam concorrência de forma implícita, uma vez que todos os MoCs empregados são concorrentes. Entretanto, é bastante importante testar a captura de um algoritmo concorrente, ou seja, utilizar essa primitiva de maneira explícita. Podemos mencionar o cálculo da integral de uma função f(x) no intervalo [a,b] utilizando a regra do trapézio14 , dada pela fórmula: n Σ (f(xi) + f(xi − 1)) * h/2 i = 1 Nessa fórmula, o intervalo [a,b] é dividido em n intervalos de tamanho h. A concorrência está no fato que o cálculo do valor nos n intervalos pode ser feita de forma independente, propiciando um paralelismo trivial. 3.5.4 Preempção A primitiva comportamental de preempção é a interrupção de um cálculo ou tarefa para o início de outra tarefa, quando uma determinada condição for verdadeira. Esse tipo de primitiva é fundamental para a captura de sistemas a serem implementados em hardware [GUP97]. Um exemplo simples desse tipo de primitiva é a reinicialização do sistema. A figura 3.32 apresenta um trecho de código15 na linguagem Esterel para exemplificar essa situação. 1− module ABRO: 2− input A, B, R; 3− output O; 4− loop 5− [ await A || await B ]; 6− emit O; 7− each R 8− end module Figura 3.32 − Um exemplo para a primitiva de preempção. A linha 1 declara um novo módulo do sistema (ator), e os portos de entrada e saída são declarados nas linhas 2 e 3. O corpo do sistema é constituído por um laço infinito (linhas 4 a 7) que espera em paralelo por dois eventos (linha 5), um no porto A e outro no porto B, para em seguida gerar um novo evento no porto O (linha 6). Em seguida, o sistema espera por um sinal no porto R. Caso, durante a espera dos eventos nos portos A e B, ocorra um evento no porto R, o laço é reinicializado. A figura 3.33 apresenta uma máquina de estados de Mealy que descreve o comportamento do código da figura 3.32. 14 Esse exemplo foi retirado de [ARV82]. 15 Esse exemplo foi extraído de [BER98].
  • 68. 53 Figura 3.33 − Máquina de Mealy para o código da figura 3.32. 3.5.5 Recursão A recursão é a especificação de uma função com base nela mesma. Embora seja sempre possível transformar um processo recursivo em uma iteração, essa primitiva é relevante pois torna a especificação de uma função mais concisa e simples. Dentre inúmeros exemplos, temos o cálculo da sequência de Fibonacci como um possível toy benchamark. Outro possível exemplo, este mais significativo, é o algoritmo da transformada discreta de Fourier, que pode ser descrito de forma recursiva [LEE95]. 3.6 Discussão O principal objetivo desse trabalho é avaliar a eficiência de diferentes modelos computacionais para representar e validar um sistema digital. Para alcançar tal objetivo, fundamentamos nossa metodologia na definição de primitivas comportamentais e no estudo da eficiência dos diferentes MoCs para representá−las. A partir das características semânticas de cada modelo computacional e do estudo das três primitivas comportamentais descritas nas seções 3.2, 3.3 e 3.4, podemos obter as seguintes conclusões: SDF: Esse MoC foi desenvolvido visando a captura eficiente de algoritmos para processamento digital de sinais. Esses algoritmos são caracterizados pela transformação de um fluxo (stream) de dados através de várias operações matemáticas. Isso justifica a escolha da semântica data flow. As três primeiras primitivas que analisamos comprovaram essa observação. No caso da primeira e da terceira primitivas, uma descrição eficiente pôde ser efetuada, pois ambas as primitivas envolvem a transformação de uma seqüência de dados. A captura da primeira primitiva foi extremamente simples, pois se trata de um grafo homogêneo. A terceira primitiva demanda o uso de diferentes taxas de amostragem, todos elas constantes. Entretanto, a terceira primitiva expôs uma situação relevante: como as taxas de amostragem devem ser valores inteiros, é possível que uma especificação apresente taxas com valores altos, como no caso da figura 3.22. Isso implica diretamente no tamanho necessário para cada fila de comunicação, isto é, na quantidade necessária de memória. A alternativa seria utilizar uma especificação que produza valores repetidos nos portos com uma taxa de consumo menor, como foi feito na especificação da figura 3.20. A fraqueza do modelo SDF foi demostrada na captura da segunda primitiva, que envolvia o ativamento de um ator condicionado ao resultado de outro ator. Neste caso, não foi possível criar uma especificação satisfatória. Utilizamos o recurso de função high−order para obter uma especificação válida. Isso já era previsível, pois o MoC SDF necessita que um ator seja sempre ativado pelo menos uma vez a cada iteração. A partir desses resultados, podemos concluir que é favorável utilizar o MoC SDF quando: R R R AB/O A B B A
  • 69. 54   desejamos capturar um sistema transformacional, em que as taxas de amostragem são conhecidas a priori e constantes;   é imprescindível que características da execução sejam conhecidas a priori (escalonamento estático). É importante notar que a utilização de composição de atores pode solucionar ou gerar problemas para a captura de uma especificação no MoC SDF. Isto ocorre porque esse MoC não preserva suas propriedades através da composição. A segunda primitiva pode ser utilizada como exemplo: a composição dos atores Cte1, Cte2, Coef, e Mux em um único ator permite a criação de uma especificação válida. DDF: O modelo DDF procura eliminar as limitações presentes no MoC SDF, permitindo variação das taxas de amostragem. A primeira e a terceira primitivas foram capturadas com sucesso, com as mesmas especificações utilizadas para o MoC SDF. Neste caso, é mais vantajoso utilizar o MoC SDF, devido ao escalonamento estático. A captura da segunda primitiva exigiu a utilização das características específicas do modelo. Duas especificações válidas foram criadas, mais eficientes com relação ao MoC SDF. Entretanto, pudemos demonstrar que a característica de leitura bloqueante, presente em modelos data flow, atrapalhou a obtenção de uma especificação totalmente satisfatória. Desta forma, podemos concluir que o modelo DDF deve ser usado principalmente para capturar sistemas transformacionais que não possam ser descritos pelo modelo SDF. PN: O modelo PN é bastante similar ao modelo DDF. A principal diferença está no fato do modelo DDF utilizar ativamentos atômicos e o modelo PN processos concorrentes. A vantagem da solução do modelo DDF é que o overhead devido ao escalonamento de processos é eliminado. Em contrapartida, é mais simples descrever o código fonte de um ator no MoC PN, pois não é necessário manter informações referentes ao estado do ator (vide 3.4.2.2 e 3.4.2.3). Podemos afirmar que esse MoC deve ser empregado nas mesmas situações que o MoC DDF. Determinar qual é mais vantajoso irá depender da implementação da especificação. CSP: Para as três primitivas que estudamos, o modelo CSP utilizou as mesmas especificações que o modelo PN. Na captura da segunda primitiva, foi feita uma tentativa de utilizar o rendezvous não determinístico, mas a especificação resultante não era válida. Não foi possível chegar à conclusões específicas para esse modelo. Devido à comunicação por rendezvous, esse modelo apresenta as mesmas limitações que os modelos data flow. Um ponto negativo do modelo CSP são as situações de deadlock. Embora seja possível detectar tais situações [FDR], essa tarefa demanda algum esforço, o que não ocorre com outros modelos. SR: O modelo SR foi bastante eficiente na captura das três primitivas, inclusive na primitiva de desvio condicional. Isso se deve à flexibilidade do modelo em permitir a ausência/indeterminação de eventos em alguns sinais. O escalonamento estático é outro ponto favorável desse modelo. Podemos enumerar três pontos desfavoráveis desse modelo. Primeiramente, embora seja possível [EDW97], as conexões entre atores não apresentam armazenamento de dados. Desta forma, sistemas
  • 70. 55 transformacionais com taxas de amostragem não unitárias não podem ser capturados eficientemente por esse modelo. O segundo ponto desfavorável desse modelo pode ser observado nas figuras 3.25 e 3.2616 : a não utilização de leitura bloqueante torna o código fonte de um ator um pouco mais complexo. A hipótese de sincronismo também pode ser um ponto problemático do modelo, caso a implementação do sistema não seja capaz de satisfaze−la. A menos das observações acima, o MoC SR pode ser empregado na captura de diversos sistemas. Sistemas reativos, caracterizados pela operação baseada na iteração com o ambiente através de eventos, são forte candidatos, uma vez que os modelos data flow e CSP são menos eficientes (vide primitiva comportamental de desvio condicional). DE: Assim como o modelo SR, todas as primitivas foram capturadas eficientemente no modelo DE. Esse modelo também permite a ausência/indeterminação de eventos em alguns sinais. Ao contrário do MoC SR, sistemas transformacionais podem ser capturados satisfatoriamente por esse modelo, pois quando múltiplos eventos com um mesmo timestamp são enviados para um porto, uma fila é utilizada. O modelo DE é o mais flexível dos seis modelos que estudamos. É possível utilizar esse modelo para simular a semântica dos outros cinco modelos. Desta forma, para as três primitivas que estudamos, esse modelo se mostrou o mais adequado. 16 O código da figura 3.26 está no MoC DE mas é basicamente o mesmo para o MoC SR.
  • 71. 56 Capítulo 4 Estudo de Caso 4.1 Introdução Neste capítulo apresentaremos o desenvolvimento de especificações executáveis para capturar o comportamento de um subconjunto de um modem ADSL [ADSL]. Ao contrário dos exemplos do capítulo 3, o sistema em questão representa uma aplicação real e o desenvolvimento foi feito a partir da especificação em língua inglesa para a tecnologia. Inicialmente iremos apresentar um resumo dessa especificação, mencionando apenas a funcionalidade que foi capturada. Em seguida, a criação das especificações executáveis no ambiente Ptolemy será detalhada. Concluiremos esse capítulo com comentários à respeito do estudo de caso. 4.2 O Modem ADSL 4.2.1 Introdução Asymmetric Digital Subscriber Line (ADSL) é uma tecnologia de modem baseada no uso de modulação Discrete Multitone (DMT), utilizada para atingir altas taxas de transmissão de dados em linhas telefônicas convencionais. O princípio básico da modulação DMT é dividir a banda disponível em um grande número de subcanais (tons). A modulação é capaz de alocar uma certa quantidade de dados de forma que cada subcanal seja utilizado ao máximo. Caso algum subcanal não apresente uma qualidade de transmissão razoável, ele não é utilizado. A figura 4.1 apresenta o modelo de referência para sistemas ADSL, destacando os principais blocos. Figura 4.1 − Modelo de referência de sistemas ADSL Os dados provenientes da rede broadband são recebidos pelo modem ATU−C (ADSL Transceiver Unit − Central Office) e convertidos em sinais analógicos. Os sinais analógicos são transmitidos conjuntamente com sinais de telefone convencional (POTS), até o modem remoto (ATU− R). O modem ATU−C também recebe e decodifica dados provenientes do modem remoto. A figura 4.2 mostra o diagrama de blocos funcionais do transmissor do modem ATU−C. ATU−C ATU−R SplitterSplitter Telefone ou Modem Voiceband Rede Customer Rede Narrowband Rede Broadband V−C T−R U−C2 U−R2
  • 72. 57 Figura 4.2 − Diagrama funcional do transmissor do modem ATU−C. Até quatro canais seriais simplex (ASx) e até três canais seriais duplex (LSx) são sincronizados à uma taxa de transmissão de 4 kHz. Os dados provenientes dos canais seriais são multiplexados em dois caminhos de dados diferentes: fast e interleaved. Um código de redundância cíclica (CRC), embaralhamento e uma codificação do tipo forward error correction (FEC) devem ser aplicados aos dados dos dois buffers separadamente. Os dados do buffer interleaved devem então ser submetidos à uma função de intercalação. Em seguida, dados provenientes dos dois buffers devem passar por uma função de ordenamento de tons e formar um frame. Esse frame é enviado à um módulo de codificação de constelações. Após essa codificação, o frame deve ser modulado em um símbolo DMT para finalmente gerar um sinal analógico pronto para a transmissão. Devido à adição de bytes redundantes pelo módulo FEC e à função de intercalamento, os pacotes de dados apresentam estruturas diferentes nos três pontos de referência indicados na figura 4.2. Os três pontos de referência são:   A (mux data frame): nesse ponto os dados já foram multiplexados, sincronizados e o CRC foi adicionado. Mux data frames devem ser gerados à uma taxa média de 4.0 kHz;   B (FEC output data frame): nesse ponto foi adicionado os bytes referentes à correção de erros;   C (constellation encoder input data frame): nesse ponto um frame é a entrada para o codificador de constelações. O transmissor do modem ATU−R é similar ao do ATU−C, exceto pela ausência dos canais seriais simplex (ASx). Isso implica que uma quantidade menor de dados podem ser enviados por esse transmissor, e seu respectivo data path é mais simples. Por exemplo, o bloco IDFT do modem ATU−C possui 256 entradas, enquanto o do modem ATU−R 64. 4.2.2 Framing O modem ADSL utiliza a estrutura de superframe apresentada na figura 4.3. Cada superframe é composto por 68 ADSL frames, codificados e modulados em símbolos DMT. A nível do padrão de bits, a taxa de transmissão de um símbolo DMT é de 4000 baud (período = 250 µs). Devido ao símbolo de sincronismo introduzido ao final de cada superframe, a taxa real de dados transmitidos é de 68/69 * 4000 baud. O símbolo de sincronização é utilizado para recuperar o posicionamento dos frames quando ocorrerem micro−interrupções na transmissão. Do contrário, seria necessário reinicializar os modems. Controle Mux/Sync CRCF CRCI Scram FEC Scram FEC Interleaver Ordenamento deTons Codificador de Constelações IDFT Buffer Paralelo/Serial DAC e geração do sinal AS0 AS1 AS2 AS3 LS0 LS1 LS2 V−C U−C2 A mux data frame B FEC data frame C CE input frame Zi i = 0 to 255 xn n = 0 to 511
  • 73. 58 Figure 4.3 − A estrutura de superframe e frame do modem ADSL. O byte denominado de fast do frame para os dados provenientes do buffer fast contém o resultado do cálculo do CRC ou informações de controle. O frame possui um byte similar para os dados provenientes do interleaved buffer, denominado de synchronization bytes Cada canal serial ASx ou LSx é associado à um dos dois buffers de dados, durante a inicialização do modem. A estrutura de superframe e frame não utiliza um padrão de bits para determinar os intervalos entre cada frame. Essa função é feita por um prefixo cíclico introduzido no símbolo DMT pelo modulador. Os intervalos entre superframes são determinados pelo símbolo de sincronismo, também introduzido pelo modulador, e que não carrega dados do usuário. Figura 4.4 − A estrutura de um frame de dados do buffer fast. A figura 4.4 apresenta a estrutura do frame com dados extraídos do buffer fast. No ponto de referência A (mux data frame), o frame construído a partir dos dados do buffer fast deve sempre conter pelo menos o byte fast. Este pode ser seguido por BF((ASx) byte para cada canal ASx e por BF(LSx) para cada canal LSx. Caso algum dos valores BF(ASx) seja maior que zero, então um byte AEX e um byte LEX devem ser incluídos após os dados do último canal LSx. Caso algum dos valores Bf(LSx) seja maior que zero, então apenas o byte LEX deve ser incluído. O frame no ponto de referência B é obtido acrescentando−se ao mux data frame RF bytes, utilizados para detecção e correção de erros. Frame 0 Frame 1 Frame 2 Frame 34 Frame 35 Frame 66 Frame 67 Sync frame Byte fast Bytes de dados FEC Buffer fast Buffer interleaved Buffer de frames de dadosr (68/68 x 250 µsec ) Bytes de dados Superframe (17 µsec) 1 byte KF bytes RF bytes NF bytes NI bytes Byte fast AS0 AS1 AS2 AS3 LS0 LS1 LS2 AEX LEX FEC bytes NF bytes KF bytes 1 byte BF (AS0) bytes BF (AS1) bytes BF (AS2) bytes BF (AS3) bytes BF (LS0) bytes BF (LS1) bytes BF (LS2) bytes AF bytes LF bytes RF bytes
  • 74. 59 Figura 4.5 − A estrutura de um frame de dados do buffer interleaved. A figura 4.5 ilustra a estrutura de um frame construído a partir de dados do buffer interleaved. No ponto de referência A, o frame da figura 4.5 deve conter pelo menos o byte de sincronização. O restante desse frame é construído de maneira similar ao caso do buffer fast, substituindo BF por BI. O tamanho de um mux data frame é de KI bytes. Para os dados do buffer interleaved, o codificador FEC deve obter S mux data frames e adicionar RI FEC bytes de redundância para produzir uma palavra código. Esta palavra possui comprimento igual à N − FECI = S x KI + RI bytes. Os frames resultantes devem conter NI = N −FECI/S bytes, onde NI é um número inteiro. Quando S 1, para S frames em uma palavra código FEC, o frame resultante (ponto de referência B) deve conter mais que um mux data frame em todos os casos a menos do último frame. Esse frame deve conter os RI bytes de redundância, além de uma fração de mux data frame 4.2.3 Código de Redundância Cíclica O principio para o código de redundância cíclica (CRC) é interpretar os dados a serem transmitidos como uma palavra binária M. Essa palavra é então dividida por uma chave k (polinômio gerador), conhecido pelo transmissor e receptor. O resto da divisão constitui a ¨palavra de verificação¨ para o conjunto de dados. O transmissor deve enviar os dados juntamente com a palavra de verificação para que o receptor possa refazer a divisão e comparar com a palavra de verificação recebida. Para um modem ADSL, o código de redundância cíclica é aplicado para os dados do buffer fast e interleaved, independentemente. A palavras de verificação obtidas para cada superframe devem ser enviadas no primeiro frame do próximo superframe. O bits cobertos pela palavra de verificação incluem:   buffer fast:   frame 0 : bytes ASx (x = 0, 1, 2, 3), bytes LSx (x = 0, 1, 2) e qualquer byte AEX e LEX;   todos os outros frames: byte fast, bytes ASx (x = 0, 1, 2, 3), bytes LSx (x = 0, 1, 2) e qualquer byte AEX e LEX.   buffer interleaved   frame 0: bytes ASx (x = 0, 1, 2, 3), bytes LSx(x = 0, 1, 2) e qualquer byte AEX e LEX;   todos os outros frames: byte sync, bytes ASx (x = 0, 1, 2,3), bytes LSx (x = 0, 1, 2) e qualquer byte AEX e LEX. O polinômio gerador utilizado para modems ADSL é: : x8 + x4 + x3 + x2 + 1. Byte Sync AS0 AS1 AS2 AS3 LS0 LS1 LS2 AEX LEX KI bytes 1 byte BI (AS0) bytes BI (AS1) bytes BI (AS2) bytes BI (AS3) bytes BI (LS0) bytes BI (LS1) bytes BI (LS2) bytes AI bytes LI bytes Mux data frame 0 Mux data frame 1 Mux data frame S −1 FEC bytes FEC data frame 0 FEC data frame 1 FEC data frame S − 1 KI bytes KI bytes KI bytes RI bytes NI bytes NI bytes NI bytes
  • 75. 60 4.2.4 Embaralhamento Os dados presentes nos buffers fast e interleaved devem ser embaralhados bit a bit separadamente utilizando a seguinte fórmula: dn’ = dn ⊕ dn−18’⊕ dn−23’ ,onde dn é a n−éssima saída do buffer fast ou interleaved e dn’ é a n−éssima saída do correspondente embaralhador. O embaralhamento é feito no nível de bits, independentemente do símbolo de sincronismo e da estrutura dos frames. 4.2.5 Correção de Erros A correção de erros do tipo Forward Error Correction (FEC) é utilizada para assegurar o desempenho da transmissão. A correção é baseada no algoritmo de codificação Reed−Solomon. O tipo de código empregado por esse algoritmo é denominado de código de bloco cíclico, pois utiliza bits redundantes adicionados ao final dos dados. O algoritmo RS particiona os dados em símbolos contendo m bits. Cada símbolo é processado como sendo uma unidade pelo codificador e pelo decodificador. O código RS pode ser descrito pelo par (n, k), onde k é o tamanho da palavra de dados não codificada e n é o tamanho da palavra de dados com os bits redundantes. Os (n − k) símbolos redundantes são chamados de símbolos de verificação de paridade. O código RS deve satisfazer: n ≤ 2m − 1 e n − k ≥ 2t, onde t é a quantidade possível de símbolos corrigíveis. Para o modem ADSL, m é fixo em 8 bits. O valor de n e k irá depender do número de bytes transmitidos por cada canal serial ASx e LSx. O número de bytes de paridade por cada palavra é limitado entre 0 e 16. 4.2.6 Interleaver A maioria dos algoritmos utilizados para correção de erros são otimizados para situações onde os erros aparecem randomicamente na palavra de dados. Interleaving é uma técnica utilizada para rearranjar os bits da palavra de dados tal que os erros apareçam em posições randômicas e distribuídos por várias palavras de dados, ao invés de poucas palavras. Para o modem ADSL, os frames FEC devem ser sofrer um interleaving convolucional, à uma dada profundidade. O processo de interleaving irá atrasar cada byte do frame FEC por um tempo diferente, tal que o codificador de constelações recebe bytes pertencentes a vários frames diferentes. A regra utilizada para o interleaving é: Cada um dos N bytes B0, B1, Bn−1 de uma palavra código RS é atrasada por uma quantidade de tempo que varia linearmente com a posição do byte na palavra. Desta forma, o byte Bi é atrasado por (D−1) * i bytes, onde D é a profundidade do interleaving. Para modems ADSL, A profundidade de interleaving deve ser uma potência de dois menor ou igual a 64. 4.2.7 Ordenamento de Tons Devido as características do sinal ADSL e do conversor digital/analógico, as maiores probabilidades de erros de transmissão estão associados aos subcanais com maior número de bits. Dessa forma, para que o sistema de codificação FEC consiga eficientemente corrigir tais erros, é mais favorável que os tons com maior número de bits recebam dados provenientes do buffer interleaved.
  • 76. 61 O ordenamento de tons do modem ADSL associa os primeiros BF bytes para os tons com menor número de bits e os restantes BI bytes são associados aos tons restantes. 4.2.8 Codificação de Constelações Codificação de constelações é um processo que recebe uma palavra de bits e transforma−a em um número complexo. Para um dado subcanal ADSL, o codificador de constelações seleciona um par de número inteiros ímpares (X, Y) a partir de um mapa numérico, baseado no vetor b de bits (vb−1, vb−2, ..., v1, vb−0). Para uma palavra b com comprimento par, os valores inteiros X e Y do ponto da constelação é determinado pela regra: os números inteiros ímpares X e Y são formados tomando a representação complemento de dois dos vetores vb−1, vb−3, ..., v1, 1 e vb−2, vb−4, ...., v0, 1, respectivamente. Quando o comprimento de b for igual a 3, a constelação da figura 4.6 é utilizada. Figura 4.6 − Constelação quando o comprimento de b for igual à 3. Quando o comprimento for ímpar maior que 3, o ponto da constelação é determinado da seguinte maneira: os dois bits mais significativos de X e de Y são determinados com base nos 5 bits mais significativos de b. Seja c = (b+1)/2, então X e Y tomam a representação complemento de dois dos vetores Xc, Xc−1, vb−4, vb−6, ...., v3, v1, 1 e Yc, Yc−1, vb−5, vb−7, vb−9, ..., v2, v0, 1, respectivamente. A relação entre Xc, Xc−1, Yc, Yc−1 e vb−1, vb−2, .., vb−5 é dada por uma tabela presente no padrão ADSL. 4.2.9 Modulação Após o codificador de constelações, os dados são modulados através da transformada discreta inversa de Fourier (IDFT). Essa operação define uma relação entre os 512 valores reais xk e 256 números complexos Zi. Como o codificador de constelação gera apenas 255 valores complexos, a fim de obter os 512 valores reais xk, o vetor Z deve ser aumentado em 256 valores, tal que: Zi = conj(Z512 − i) para i = 257 to 511. 4.2.10 A Inicialização A inicialização é o processo utilizado para estabelecer uma conexão entre o par de modems ATU−C e ATU−R. Para que a qualidade da transmissão e a quantidade de dados transmitidos seja maximizada, o transmissor/receptor ADSL deve determinar certos parâmetros do canal de conexão e estabelecer as características de processamento de cada modem. A inicialização é composta por quatro partes: ativação e acknowledgment, treinamento do transmissor/receptor, análise do canal e troca de Y X 0 1 2 3 4 5 6 7
  • 77. 62 informações. A determinação das características do canal de comunicação e o estabelecimento das características da transmissão exigem que cada transmissor/receptor gere, e responda apropriadamente, um conjunto de sinais pré−definidos e temporizados. Regras são definidas determinando o início e fim de cada um desses sinais. Essas regras são descritas através da definição dos estados de inicialização do transmissor/receptor, e através da definição dos sinais que eles irão gerar. Devido a complexidade do procedimento de inicialização, descreveremos somente a parte referente a ativação e acknowledgment. A seqüência de estados/sinais para a fase de ativação e acknowledgment é apresentada na figura 4.7. Figura 4.7 − Ativação e acknowledgment. Os sinais para o modem ATU−C são:   C−QUIET1: após o power−up e um auto teste opcional, o modem ATU−C deve entrar no estado C−QUIET1. Quando o modem está no estado C−QUIET1, um comando do controlador central do modem ou a detecção correta do sinal R−ACT−REQ faz com que ocorra uma transição para o estado C−ACT. Para assegurar a compatibilidade entre diferentes implementações do modem, o transmissor ATU−C deve permanecer no estado C−QUIET1 até que o sinal R−ACT−REQ não seja mais detectado por 128 símbolos consecutivos;   C−ACT: o modem ATU−C deve transmitir o sinal C−Active a fim de iniciar uma conexão com o modem ATU−R. Quatro diferentes sinais C−Active são definidos. Isso ocorre para distinguir diferentes requisições do sistema referentes à temporização da conexão e o uso ou não de um tom piloto. Na implementação que desenvolvemos, utilizamos o sinal C−ACT2, definido por: 0, i ≠ 44, 0 ≤ i ≤ 256 Zi = AC−ACT2 , i = 44 AC−ACT2 deve possuir um nível de potência de transmissão igual à −3.65 dBm para os primeiros 64 símbolos, e ser 24 dB menor para os restantes 64 símbolos. Esse sinal deve ser transmitido por 128 símbolos consecutivos e nenhum prefixo cíclico deve ser adicionado;   C−QUIET2: a função do estado C−QUIET2 é permitir a detecção do sinal R−ACK1 ou R− ACK2, sem a necessidade de treinar o cancelador de echo do modem ATU−C. A duração do estado C−QUIET2 é de 128 símbolos. Após o estado C−QUIET2, o modem ATU−C deve entrar em um de três estados:   C−REVEILLE: caso o ATU−C tenha detectado o sinal R−ACK, ele deverá entrar no estado C−REIVELLE. Mesmo que o ATU−C venha a detectar o sinal R−ACK em menos que 128 símbolos, este deverá esperar pela duração completa do sinal, mantendo−se no estado C−QUIET2;   C−ACT: caso o modem ATU−C falhe em detectar o sinal R−ACK, e ele não tenha C−IDLE/ C−QUIET1/ C−TONE C−ACT 1−2−3−4 C−QUIET2 R−ACT−REQ/ R−QUIET1 R−ACK 1−2 ATU−R ATU−C
  • 78. 63 entrado no estado C−ACT por mais que duas vezes, então o ATU−C deve entrar no estado C−ACT;   C−QUIET1: caso o modem ATU−C não detecte o sinal R−ACK após ter entrado no estado C−ACT pela segunda vez, ele deverá retornar ao estado C−QUIET1. Os sinais para o modem ATU−R são:   R−ACT−REQ: o sinal R−ACT−REQ é utilizado quando o modem ATU−R deve iniciar o estabelecimento de uma conexão com o modem ATU−C. O sinal R−ACT−REQ é transmitido após o power−up e um auto teste opcional. O sinal R−ACT−REQ é definido tal como o sinal C−ACT2, mas com i = 8. O modem ATU−R deve permanecer no estado R− ACT−REQ indefinidamente (isto é, transmitir o sinal pela duração de 128 símbolo para em seguida permanecer quieto por 896 símbolos, e então repetir o processo) até que o sinal C− ACT2 seja detectado com sucesso;   R−Acknowledge: esse sinal é transmitido com um acknowledgment da detecção correta do sinal C−ACT2. Três variações do sinal são definidas. Utilizamos nesse estudo a versão R− ACK2. Esse sinal é definido tal qual o sinal C−ACT2, mas com i = 12 e com o nível de potência de transmissão igual à −1.65 dBm para os primeiros 64 símbolos e 20 db menor para os 64 símbolos restantes. 4.3 As Especificações Executáveis 4.3.1 O Sistema A figura 4.8 apresenta a topologia do nível hierárquico mais alto da especificação. Neste nível existem quatro atores: duas instâncias da especificação do modem (ATU−C e ATU−R) e dois blocos representando o ambiente (rede broadband e customer). As especificações para os modems serão descritas mais adiante neste texto. Figura 4.8 − Topologia da especificação do sistema. Os atores modelando o ambiente são responsáveis por enviar e receber dados para os modems através dos sinais de comunicação serial (ASx e LSx)17 . A fim de tornar a especificação mais simples, sem perder a representatividade, implementamos apenas dois sinais (um de cada tipo): AS0 e LS0. Como a especificação determina que esses sinais estejam sincronizados à uma taxa de 4 kHz, o MoC utilizado deve ter uma noção de tempo. Escolhemos o modelo Discrete Event para a captura da especificação do sistema. As razões para tal escolha são a flexibilidade desse MoC e a presença de uma noção de tempo. Também tentamos utilizar o modelo Process Networks, uma vez que a implementação do modelo no ambiente Ptolemy possui uma noção de tempo e pelas características do sistema (a grande quantidade de blocos transformacionais). Entretanto, encontramos diversas situações em que esse modelo não se mostrou adequado. A primeira situação problemática ocorreu quando tentamos criar os atores representando o ambiente: devido à semântica de leitura bloqueante, não é possível criar apenas um ator que faça as duas operações de envio e recebimento de dados. A semântica de leitura bloqueante também foi um empecilho para a captura da especificação do modem: como não é possível verificar se um porto contém dados, os 17 Os atores modelando o ambiente são utilizados para simular e validar o comportamento do modem. Rede Broadband ATU−C ATU−R Rede Customer Tx TxRx Rx AS0 AS0 LS0 LS0 DataTx DataRx
  • 79. 64 sinais DataTx e DataRx devem ser utilizados para indicar qual é o porto que contém dados. Esses sinais são dispensáveis na especificação com o MoC DE. A figura 4.9 apresenta um trecho do código do ator BroadBandNet no modelo DE. 1− public class BroadBandNet extends DEActor { 2− 3− public TypedIOPort AS0; 4− public TypedIOPort dataTx; 5− public TypedIOPort LS0; 6− 7− public Parameter AS0_Rate; 8− public Parameter LS0_Rate; 9− 10− public BroadBandNet(TypedCompositeActor container, String name) throws 11− NameDuplicationException, IllegalActionException { 12− 13− .... 14− } 15− 16− public void fire() throws IllegalActionException { 17− 18− if(_nextFire == getCurrentTime()) { 19− if(_nextFireAS0 == 0) { 20− ObjectToken ot = 21− new ObjectToken(new Integer(_data[_counter])); 22− AS0.broadcast(ot); 23− dataTx.broadcast(new IntToken(0)); 24− 25− _counter = (_counter + 1) % _data.length; 26− _nextFireAS0 = 1000.0 / _AS0_Rate; 27− } 28− .... 29− _nextFire(); 30− } 31− 32− ObjectToken ot; 33− int [] tmp = null; 34− while(LS0.hasToken(1)) { 35− ot = (ObjectToken) LS0.get(1); 36− tmp = (int []) ot.getValue(); 37− for(int i = 0;i tmp.length;i++) { 38− _LS0Data.insertElementAt(new Byte((byte)tmp[i]),_LS0Counter); 39− _LS0Counter++; 40− } 41− } 42− } 43− .... 44− 45− private void _nextFire() throws IllegalActionException { 46− 47− double min = 1000.0; 48− boolean sched = false; 49− 50− if(_nextFireAS0 min) min = _nextFireAS0; 51− if(_nextFireLS0 min) min = _nextFireLS0; 52− 53− if(_nextFireAS0 − min == 0) { 54− fireAt(getCurrentTime() + _nextFireAS0); 55− _nextFire = getCurrentTime() + _nextFireAS0; 56− sched = true; 57− } 58− _nextFireAS0 −= min; 59− 60− if((sched == false) (_nextFireLS0 − min == 0)) { 61− fireAt(getCurrentTime() + _nextFireLS0); 62− _nextFire = getCurrentTime() + _nextFireLS0; 63− } 64− _nextFireLS0 −= min; 65− } 66− .... Figura 4.9 − Trecho de código do ator BroadBandNet no MoC DE. Dois parâmetros (linhas 7 e 8) são utilizados para indicar a taxa de transmissão de cada sinal serial. Da linha 18 à linha 30 está mostrado o código para a transmissão de dados através do sinal AS0 (igual para o sinal LS0). A variável _nextFireAS0 contém o valor do próximo momento em que um novo
  • 80. 65 dado deve ser enviado através do sinal AS0. Essa variável é atualizada pelo método _nextFire() (linha 45 à 65). Esse método é responsável por escalonar o ativamento futuro do ator, baseado no valor dos parâmetros AS0_Rate e LS0_Rate, utilizando o método fireAt() (linhas 54 e 61). O trecho de código da linha 32 à linha 41 é utilizado para receber um dado proveniente do sinal LS0. Note que o ator BroadBandNet é ativado por duas condições (e que podem ser simultâneas): quando um novo token é recebido ou quando o instante de tempo indicado em uma das chamadas do método fireAt() for alcançado. Não é possível implementar tal comportamento com o modelo PN, bem como em qualquer outro modelo data flow. O ator para a rede customer apresenta uma implementação similar à da figura 4.9. A diferença desse ator é que apenas o sinal LS0 é utilizado para transmitir dados e ambos os sinais são verificados para determinar a presença de dados. 4.3.2 Framing Como mencionado no capítulo 2, o ambiente Ptolemy permite a associação de um tipo de dado à um porto de entrada/saída. Na especificação executável para o modem ADSL, utilizamos amplamente o tipo ObjectToken por dois motivos: é possível criar uma especificação mais genérica e próxima da especificação em linguagem natural, e devido ao menor tempo de execução da especificação. Duas classes auxiliares implementado a estrutura de framming, descritas no item 4.2.2, foram criadas: uma para o Mux Data Frame e outra para o Fast Data Frame. Não existe uma classe específica para o Interleaved Data Frame, pois os dados são misturados após o bloco de Interleaver, sendo visto apenas como um vetor de bytes. O trecho de código da figura 4.10 apresenta a classe MuxDataFrame. 1− public class MuxDataFrame { 2− 3− public MuxDataFrame(int nbAS0, int nbAS1, int nbAS2, int nbAS3, 4− int nbLS0, int nbLS1, int nbLS2) { 5− 6− _fastByte = 0; 7− _AS0 = new int[nbAS0]; 8− ..... 9− _AEXcond = (nbAS0 != 0) || (nbAS1 != 0) || 10− (nbAS2 != 0) || (nbAS3 != 0); 11− _AEX = 0; 12− 13− _LEXcond = _AEXcond || (nbLS0 != 0) || 14− (nbLS1 != 0) || (nbLS2 != 0); 15− _LEX = 0; 16− } 17− 18− public void setAS0Byte(int b, int nb) { 19− _AS0[nb] = b; 20− } 21− 22− public int getAS0Byte(int nb) { 23− return _AS0[nb]; 24− } 25− 26− public int [] getAS0() { 27− return _AS0; 28− } 29− .... 30− protected int _fastByte; 31− protected int _AS0[]; 32− .... 33− protected int _AEX; 34− protected int _LEX; 35− protected boolean _AEXcond; 36− protected boolean _LEXcond; 37− } Figura 4.10 − Trecho de código da classe MuxDataFrame. Da linha 30 à linha 36 estão declaradas as variáveis representando os campos de um mux data frame. Essas variáveis são inicializadas no construtor (linha 3 à 16), que possui parâmetros indicando a quantidade de bytes por cada canal serial ASx e LSx. As condições para a utilização dos bytes AEX e
  • 81. 66 LEX são calculadas nas linhas 9, 10, 13 e 14. A classe MuxDataFrame possui uma série de métodos (linha 18 à linha 27 para o caso do sinal AS0) para acessar e modificar as variáveis internas. A classe auxiliar para o Fast Data Frame está apresentada na figura 4.11. Trata−se de uma classes derivada da classe MuxDataFrame (linha 1), adicionando um campo (linha 18) para armazenar o valor da paridade do frame, além dos métodos utilizados para manipular o novo campo (linhas 10 à 16). 1− public class FastFECDataFrame extends MuxDataFrame { 2− 3− public FastFECDataFrame(int nbAS0, int nbAS1, int nbAS2, int nbAS3, 4− int nbLS0, int nbLS1, int nbLS2, int npar) { 5− 6− super(nbAS0, nbAS1, nbAS2, nbAS3, nbLS0, nbLS1, nbLS2); 7− _parity = new int[npar]; 8− } 9− .... 10− public void setParityByte(int b, int nb) { 11− _parity[nb] = b; 12− } 13− 14− public int getParityByte(int nb) { 15− return _parity[nb]; 16− } 17− .... 18− private int _parity[]; Figura 4.11 − Trecho de código da classe FastFECDataFrame. 4.3.3 A especificação dos Modems ATU−R e ATU−C A especificação do sistema da figura 4.8 possui duas instâncias da especificação executável do modem ADSL: uma para o ATU−C e outra para o ATU−R. Ambos os modems são similares: a diferença está no número de sinais seriais e consequentemente, a configuração do data path. Durante o restante desse capítulo, todos os exemplos serão dados com base no modem ATU−C. A figura 4.12 apresenta a topologia para o modem ATU−C utilizando o MoC DE. Ela é composta por atores implementando o data path (MuxSyncTx/Rx, CRCTx/Rx, ScramblerTx/Rx, FECTx/Rx, InterleaverTx/Rx, Map, Demap, IDFT, DFT), um ator implementando a máquina de estados para a inicialização do modem, e um ator gerador de eventos. Descreveremos a seguir a implementação de cada um desses atores. Figura 4.12 − A topologia da especificação para o modem ATU−C. As setas em azul indicam a transmissão de dados e as em vermelho sinais de controle. 4.3.3.1 MuxSyncTx AS0 LS0 DataTx MuxSyncTx CRCTx ScramblerTx FECTx InterleaverTx Map IDFT InitializerTimer Reset Start Start Stop Event Mode Tx MuxSyncRx CRCRx ScramblerRx FECRx InterleaverRx DeMap DFT LS0 Rx Datapath
  • 82. 67 O ator MuxSyncTx é responsável por obter os bytes provenientes dos sinais seriais e construir mux data frames para o caminho de dados utilizando o buffer fast e interleaved. Ele corresponde ao bloco Mux/Sync da figura 4.2, embora o sincronismo não tenha sido implementado na especificação executável. O porto de entrada Start é utilizado para indicar quando o ator deve iniciar o processamento dos dados provenientes do ambiente. A figura 4.13 apresenta um trecho do código desse ator. 1− .... 2− public Parameter AS0Bytes; 3− public Parameter LS0Bytes; 4− 5− public Parameter AS0Map; 6− public Parameter LS0Map; 7− 8− public Parameter S; 9− 10− .... 11− 12− public void fire() throws IllegalActionException { 13− 14− if(start.hasToken(0)) { 15− BooleanToken bt = (BooleanToken) start.get(0); 16− _mode = bt.booleanValue(); 17− } 18− 19− if(_mode) { 20− sendData(); 21− } 22− else { 23− if(dataTx.hasToken(0)) dataTx.get(0); 24− if(AS0.hasToken(0)) AS0.get(0); 25− if(LS0.hasToken(0)) LS0.get(0); 26− } 27− } 28− .... 29− private void _sendData() throws IllegalActionException { 30− 31− if(!dataTx.hasToken(0)) return; 32− 33− IntToken it = (IntToken) dataTx.get(0); 34− switch(it.intValue()) { 35− case 0: { 36− _ot = (ObjectToken) AS0.get(0); 37− Integer b = (Integer) _ot.getValue(); 38− .... 39− if(_AS0Wrote == _AS0Bytes) { 40− _AS0Wrote = 0; 41− if(_AS0Map) { 42− _currentAS0Frame = _getNextInterFrame(_currentAS0Frame); 43− else { 44− _currentAS0Frame = _getNextFastFrame(_currentAS0Frame); 45− } 46− } 47− _currentAS0Frame.setAS0Byte(b.intValue(), _AS0Wrote); 48− _AS0Wrote++; 49− } break; 50− .... 51− } 52− _checkFullFrames(); 53− } 54− 55− private MuxDataFrame _getNextFastFrame(MuxDataFrame frame) { 56− 57− int i = _fastFrames.firstIndexOf(frame); 58− if(i == _fastFrames.size() − 1) { 59− _mf = _newFastFrame(); 60− _fastFrames.insertLast(_mf); 61− } 62− else { 63− _mf = (MuxDataFrame) _fastFrames.at(i + 1); 64− } 65− return _mf; 66− } 67− .... 68− private void _checkFullFrames() throws IllegalActionException { 69−
  • 83. 68 70− while(true) { 71− if(_sentFrame == 0) { 72− _mf = (MuxDataFrame) _fastFrames.at(0); 73− if(_currentAS0Frame != _mf _currentLS0Frame != _mf) { 74− _fastFrames.removeAt(0); 75− _ot = new ObjectToken(_mf); 76− output.broadcast(_ot); 77− _sentFrame = 1; 78− 79− if(_AS0Map _LS0Map) { 80− _mf = _newFastFrame(); 81− _fastFrames.insertLast(_mf); 82− } 83− } 84− else { 85− return; 86− } 87− } 88− else { 89− .... 90− } 91− else { 92− return; 93− } 94− } 95− if(_sentFrame _S) _sentFrame = 0; 96− } 97− } 98− 99− private MuxDataFrame _newFastFrame() { 100− _mf = new MuxDataFrame(_AS0Map ? 0 : _AS0Bytes, 0, 0, 0, 101− _LS0Map ? 0 : _LS0Bytes, 0, 0); 102− return _mf; 103− } 104− .... 105− private boolean _mode; 106− private LinkedList _fastFrames; 107− private LinkedList _interFrames; 108− private MuxDataFrame _currentAS0Frame; 109− private int _AS0Wrote; 110− .... 111− private int _sentFrame; 112− private ObjectToken _ot; 113− private MuxDataFrame _mf; 114− .... Figura 4.13 − Trecho de código do ator MuxSyncTx no MoC DE. Cinco parâmetros são declarados da linha 2 à linha 8: ?0Bytes indica o número de bytes de cada sinal serial em um mux data frame; ?S0Map determina para qual buffer de dados o respectivo sinal serial está associado; S é o número de mux data frame por cada interleaved data frame. Esses parâmetros também são utilizados por vários outros atores do data path (dos atores MuxSyncTx/Rx até os atores Map/Demap). O corpo do método fire() (linha 12 à 27) determina se os dados devem ser processados ou não, dependendo de eventos no porto Start. Esses eventos são gerados pela máquina de estados de inicialização. Durante o começo da execução (etapa de inicialização) a máquina de estados instrui o ator para descartar os dados recebidos. Uma vez que a inicialização tenha sido concluída, um evento é enviado ao porto Start, indicando o início da transmissão de dados. O método _sendData() (linha 29 à 53) implementa a funcionalidade do ator. Inicialmente, a existência de dados nos canais seriais é verificada (linha 31). Caso existam dados, primeiramente eles são lidos (linhas 36 e 37). Em seguida, é verificado se o campo do frame para o buffer de dados do respectivo sinal serial já está cheio (linha 39 para o sinal AS0). Caso negativo, os dados são armazenados no mux data frame disponível (linha 69−70 para o sinal AS0, LS0 não é mostrado). Caso positivo, um novo mux data frame é criado (linhas 40 à 45). Finalmente, o método determina se existe algum mux data frame que esteja completo (linha 52), isto é, todos os campos referentes aos sinais seriais já estão preenchidos. Isso é implementado pelo método _checkFullFrames(). 4.3.3.2 MuxSyncRx
  • 84. 69 Esse ator é bastante simples. Sua única função é obter mux data frames provenientes do ator CRCRx e enviar os dados aos respectivos sinais seriais. 4.3.3.3 CRCTx Esse ator obtêm mux data frames e aplica o algoritmo de CRC descrito no item 4.2.3. É necessário determinar os limites de um superframe, tal que o ator seja capaz se armazenar o valor de CRC calculado no começo do próximo superframe. A figura 4.14 apresenta um trecho do código desse ator. 1− .... 2− public void fire() throws IllegalActionException { 3− 4− ObjectToken ot = (ObjectToken) input.get(0); 5− MuxDataFrame mf = (MuxDataFrame) ot.getValue(); 6− 7− if(_frameCounter == 0) { 8− if(_iteration == 0) { 9− _fastCRC(mf, false); 10− _iteration = 1; 11− } 12− else { 13− _CRC(0, _fastReg); 14− mf.setFastByte(_fastReg); 15− _fastReg = 0; 16− _fastCRC(mf, true); 17− } 18− } 19− else 20− if(_frameCounter == 1) { 21− if(_iteration == 1) { 22− _interCRC(mf, false); 23− _iteration = 2; 24− } 25− else { 26− _CRC(0, _interReg); 27− mf.setFastByte(_interReg); 28− _interReg = 0; 29− _interCRC(mf, true); 30− } 31− } 32− else { 33− if(_frameCounter % (_S + 1) == 0) { 34− _fastCRC(mf, true); 35− } 36− else { 37− _interCRC(mf, true); 38− } 39− } 40− 41− _frameCounter++; 42− if(_frameCounter == ATUC.framesPerSuperframe + 43− ATUC.framesPerSuperframe*_S) 44− _frameCounter = 0; 45− 46− ot = new ObjectToken(mf); 47− output.broadcast(ot); 48− } 49− 50− public void initialize() throws IllegalActionException { 51− .... 52− _iteration = 0; 53− _frameCounter = 0; 54− _interReg = 0; 55− _fastReg = 0; 56− } 57− 58− private void _fastCRC(MuxDataFrame mf, boolean first) { 59− if(first) { 60− _fastReg = _CRC(mf.getFastByte(), _fastReg); 61− } 62− 63− if(!_AS0Map) {
  • 85. 70 64− for(int i = 0;i _AS0Bytes;i++) 65− _fastReg = _CRC(mf.getAS0Byte(i), _fastReg); 66− } 67− .... 68− if(mf.hasAEX()) { 69− _fastReg = _CRC(mf.getAEXByte(), _fastReg); 70− } 71− ..... 72− } 73− 74− private int _CRC(int b, int reg) { 75− int out; 76− for(int i = 0;i 8;i++) { 77− out = reg 0x80; 78− reg = (reg 1) | ((b i) 0x01); 79− if(out != 0) reg = reg ^ _poly; 80− } 81− return reg; 82− } 83− .... 84− private int _frameCounter; 85− private int _fastReg; 86− private int _interReg; 87− private final static int _poly = 0x1d; 88− private byte _iteration; 89− .... Figura 4.14 − Trecho de código do ator CRCTx. O trecho de código das linhas 7 à 39 verifica as diferentes condições para o cálculo do CRC. A variável _frameCounter (linha 84) é utilizada para identificar o início de um novo superframe. Quando esta variável for igual à zero, significa que o ator esta processando o primeiro frame para o buffer fast. Desta forma, o CRC calculado anteriormente deve ser armazenado (linhas 13 à 16). A mesma situação se aplica ao primeiro frame para o buffer interleaved (linhas 20 à 31). A variável _frameCounter é atualizada nas linhas 41 e 44. A constante ATUC.framesPerSuperframe indica a quantidade de frames em um superframe. Embora o padrão ADSL determine o valor 67 para essa constante, utilizamos um valor menor a fim de aumentar o desempenho da execução. O método _fastCRC() (linhas 52 à 72) calcula o CRC correspondente aos dados do buffer fast. Para tal, é aplicado o algoritmo do CRC (linhas 74 à 82) para cada campo do frame. Um método similar é utilizado para os dados do buffer interleaved. 4.3.3.4 CRCRx A funcionalidade do ator CRCRx é bastante parecida com a de seu irmão transmissor. A única diferença é que quando o CRC é calculado, ocorre uma comparação com o valor do CRC recebido. Isso é feito nas linhas 11 à 16 e 29 à 34 do código da figura 4.15, para os dados buffer fast e interleaved, respectivamente. 1− .... 2− public void fire() throws IllegalActionException { 3− .... 4− if(_frameCounter == 0) { 5− if(_iteration == 0) { 6− .... 7− } 8− else { 9− _CRC(0, _fastReg); 10− int check = mf.getFastByte(); 11− if((byte) check != (byte) _fastReg) { 12− System.out.println(ttt !! ATUC−CRCRx : FAST CRC FAILED !!); 13− } 14− else { 15− System.out.println(ttt ## ATUC−CRCRx : FAST CRC OK ##); 16− } 17− _fastReg = 0; 18− _fastCRC(mf, true); 19− } 20− } 21− else
  • 86. 71 22− if(_frameCounter == 1) { 23− if(_iteration == 1) { 24− .... 25− } 26− else { 27− _CRC(0, _interReg); 28− int check = mf.getFastByte(); 29− if((byte) check != (byte) _interReg) { 30− System.out.println(tt!! ATUC−CRCRx:INTER CRC FAILED !!); 31− } 32− else { 33− System.out.println(tt ## ATUC−CRCRx:INTER CRC OK ##); 34− } 35− _interReg = 0; 36− _interCRC(mf, true); 37− } 38− } 39− else { 40− .... 41− } 42− .... 43− } 44− .... Figura 4.15 − Trecho de código do ator CRCRx. 4.3.3.5 ScramblerTx Esse ator implementa a função de embaralhamento dos dados a serem transmitidos. A figura 4.16 apresenta um trecho de seu código. Inicialmente um novo mux data frame é obtido (linhas 4 e 5) e é determinado se os dados são destinados ao buffer fast ou interleaved, dependendo do valor da variável _frameCounter. Das linhas 9 à 20 esta implementado o processamento dos dados para o buffer fast. Neste caso, o método _processFast() (linhas 31 à 43) é responsável por embaralhar os dados. Os dados para o buffer interleaved são transformados de maneira similar. Este ator também pode ser implementado utilizando qualquer um dos seis MoCs. 1− .... 2− public void fire() throws IllegalActionException { 3− 4− ObjectToken ot = (ObjectToken) input.get(0); 5− MuxDataFrame mf = (MuxDataFrame) ot.getValue(); 6− int b; 7− 8− if(_frameCounter == 0) { 9− b = _processFast(mf.getFastByte()); 10− mf.setFastByte(b); 11− 12− if(!_AS0Map) { 13− for(int i = 0;i _AS0Bytes;i++) { 14− b = _processFast(mf.getAS0Byte(i)); 15− mf.setAS0Byte(b, i); 16− } 16− } 17− .... 18− if(mf.hasAEX()) { 19− b = _processFast(mf.getAEXByte()); 19− mf.setAEXByte(b); 20− } 21− .... 22− } 23− else { 24− .... 25− } 26− _frameCounter++; 27− if(_frameCounter == 1 + _S) _frameCounter = 0; 28− ..... 29− } 30− .... 31− private int _processFast(int b) { 32− int ret = 0; 33− int b1, b2, b3; 34− 35− for(int i = 0;i 8;i++) { 36− b1 = (_fastReg 23) 0x01;
  • 87. 72 37− b2 = (_fastReg 18) 0x01; 38− b3 = (b i) 0x01; 39− ret |= ((b1 ^ b2 ^ b3) i); 40− _fastReg = (_fastReg 1) | (b1 ^ b2 ^ b3); 41− } 42− return ret; 43− } 44− .... 45− private int _fastReg; 46− private int _interReg; 47− private int _frameCounter; 48− .... Figura 4.16 − Trecho de código do ator ScramblerTx. 4.3.3.6 ScramblerRx Esse ator implementa o desembaralhamento e seu comportamento é idêntico ao do ator ScramblerTx. 4.3.3.7 FECTx Esse ator é responsável por calcular os bytes de paridade com base no algoritmo RS, conforme mencionado no item 4.2.5. A figura 4.17 apresenta parte do código desse ator. Novamente, a variável _frameCounter é utilizada para determinar quando o mux data frame recebido está alocado no buffer fast ou interleaved. Dois novos parâmetros foram introduzidos, _parityFast e _parityInter (linha 2). Esses parâmetros especificam o número de bytes de paridade que devem ser utilizados para os dados do buffer fast e interleaved. 1− .... 2− public Parameter parityFast, parityInter; 3− .... 4− public void fire() throws IllegalActionException { 5− 6− if(_frameCounter == 0) { 7− ObjectToken ot = (ObjectToken) input.get(0); 8− MuxDataFrame mf = (MuxDataFrame) ot.getValue(); 9− int [] data = mf.getAsArray(); 10− if(_nnFast != _kkFast) { 11− int [] parity = _rsEncode(data, _nnFast, _kkFast, Gg_polyFast); 12− FastFECDataFrame df = _newFastFECDataFrame(data, parity); 13− fast.send(0, new ObjectToken(df)); 14− } 15− else { 16− ot = new ObjectToken(_newFastFECDataFrame(data, new int[0])); 17− fast.send(0, ot); 18− } 19− _frameCounter = 1; 20− } 21− else 22− if(_frameCounter _S + 1) { 23− ObjectToken ot = (ObjectToken) input.get(0); 24− MuxDataFrame mf = (MuxDataFrame) ot.getValue(); 25− int [] w = mf.getAsArray(); 26− for(int j = 0;j w.length;j++) { 27− _interdata[_intercount] = w[j]; 28− _intercount++; 29− } 30− _frameCounter++; 31− } 32− else { 33− if(_nnInter != _kkInter) { 34− int [] parity = _rsEncode(_interdata, _nnInter, 35− _kkInter, Gg_polyInter); 36− for(int i = 0;i (_nnInter − _kkInter);i++) 37− _interdata[_kkInter + i] = parity[i]; 38− } 39− inter.broadcast(new ObjectToken(_interdata)); 40− _frameCounter = 0; 41− _intercount = 0; 42− }
  • 88. 73 43− } 44− 45− public void initialize() throws IllegalActionException { 46− .... 47− _kkFast = _kkInter = 1; 48− if(!_AS0Map) { 49− _kkFast += _AS0Bytes; 50− } 51− else { 52− _kkInter += _AS0Bytes; 53− } 54− if(!_LS0Map) { 55− _kkFast += _LS0Bytes; 56− } 57− else { 58− _kkInter += _LS0Bytes; 59− } 60− if(!_AS0Map /* || !_AS1Map || ... */) { 61− _kkFast += 1; 62− } 63− if(!_AS0Map || !_LS0Map) { 64− _kkFast += 1; 65− } 66− if(_AS0Map) { 67− _kkInter += 1; 68− } 69− if(_AS0Map || _LS0Map) { 70− _kkInter += 1; 71− } 72− _kkInter *= _S; 73− 74− it = (IntToken) parityFast.getToken(); 75− _nnFast = _kkFast + it.intValue(); 76− 77− it = (IntToken) parityInter.getToken(); 78− _nnInter = _kkInter + it.intValue(); 79− .... 80− } 81− .... 82− private int _frameCounter; 83− private int _interdata[]; 84− private int _intercount; 85− 86− private int _nnFast, _kkFast; 87− private int _nnInter, _kkInter; 88− .... Figura 4.17 − Trecho de código do ator FECTx. Os dados destinados ao buffer fast são processados nas linhas 7 à 19. Os dados são obtidos do mux data frame (linhas 7 à 9) e utilizados pelo método _rsEncode() (linha 11) para gerar os bytes de paridade. Em seguida, um novo FastFECDataFrame é construído (linha 12) e enviado (linha 13) ao próximo ator. Os dados destinados ao buffer interleaved são processados nas linhas 22 à 42. Como os dados de um frame destinados à esse buffer podem estar divididos em vários mux data frame (parâmetro S), o ator FECTx deve primeiramente obter a quantidade necessária de mux data frame. Isto está implementado entre as linhas 22 e 30. Uma vez que todos os dados sejam obtidos, os bytes de paridade são calculados (linha 34 e 35) e um novo frame é enviado (linha 39). O método initialize() (linhas 45 à 80) calcula, entre outras informações, o valor das variáveis _nnFast, _kkFast, _nnInter e_kkInter. As variáveis _kkFast e _kkInter armazenam o número de dados destinados ao buffer fast e interleaved em um frame. As variáveis _nnFast e _nnInter armazenam o número total de bytes. 4.3.3.8 FECRx Esse ator é responsável por receber frames do buffer fast e interleaved e verificar a integridade dos dados baseado no algoritmo RS. A figura 4.18 apresenta um trecho do código desse ator. 1− ....
  • 89. 74 2− public void fire() throws IllegalActionException { 3− 4− ObjectToken ot; 5− if(_frame == 0 fast.hasToken(0)) { 6− ot = (ObjectToken) fast.get(0); 7− FastFECDataFrame fastf = (FastFECDataFrame) ot.getValue(); 8− int [] data = fastf.getAsArray(); 9− if(_nnFast != _kkFast) { 10− if(_syndromeFast(data) == −1) { 11− System.out.println(ATUC−FECRx : Errors with fast data... + 12− trying to fix....); 13− data = _rsdecode(data, _synFast, _nnFast, _kkFast); 14− } 15− else { 16− System.out.println(ATUC−FECRx : No errors with fast data); 17− } 18− } 19− MuxDataFrame mf = _newMuxDataFrame(data); 20− output.send(0, new ObjectToken(mf)); 21− _frame = 1; 22− } 23− if(_frame == 1 inter.hasToken(0)) { 24− ot = (ObjectToken) inter.get(0); 25− int [] data = (int []) ot.getValue(); 26− if(_nnInter != _kkInter) { 27− if(_syndromeInter(data) == −1) { 28− System.out.println(ATUC−FECRx : Errors with interleaved + 29− data...trying to fix....); 30− data = _rsdecode(data, _synInter, _nnInter, _kkInter); 31− } 32− else { 33− System.out.println(ATUC−FECRx : No errors with + 34− interleaved data); 35− } 36− } 37− int k = 0; 38− for(int i = 0;i _S;i++) { 39− MuxDataFrame ret = new MuxDataFrame(0, 0, 0, 0, 40− _LS0Map ? _LS0Bytes : 0, 0, 0); 41− ret.setFastByte(data[k]); 42− k++; 43− 44− if(_LS0Map) { 45− for(int j = 0;j _LS0Bytes;j++) { 46− ret.setLS0Byte(data[k], j); 47− k++; 48− } 49− } 50− if(ret.hasLEX()) { 51− ret.setLEXByte(data[k]); 52− k++; 53− } 54− output.send(0, new ObjectToken(ret)); 55− } 56− _frame = 0; 57− } 58− } 59− .... Figura 4.18 − Trecho de código do ator FECRx. Entre as linhas 5 e 22 os dados provenientes do buffer fast são processados. Após os dados serem obtidos (linha 6 à 8), o método _syndrome() verifica a consistência dos dados (linha 10). Caso algum erro seja encontrado, o método _rsDecode() (linha 13) tenta corrigi−los. O dados resultantes são processados pelo método _newMuxDataFrame() (linha 19) para a construção de um novo mux data frame. Os dados provenientes do buffer interleaver sofrem um tratamento similar (linha 23 à 36). A diferença é que ao invés de um, _S mux data frame devem ser criados. Isto é implementado entre as linhas 37 e 57. 4.3.3.9 InterleaverTx Esse ator implementa a função de interleave dos dados. O buffer de dados é implementado como
  • 90. 75 um vetor grande o suficiente para armazenar a quantidade necessária de dados. Esse tamanho depende da profundidade D de interleave (linha 2). A figura 4.19 apresenta um trecho do código desse ator. 1− .... 2− public Parameter D; 3− .... 4− public void fire() throws IllegalActionException { 5− int j; 6− ObjectToken ot = (ObjectToken) input.get(0); 7− int [] d = (int []) ot.getValue(); 8− 9− int index = 0; 10− if(_isEven) { 11− index = 1; 12− } 13− for(j = 0;j d.length;j++) { 14− _buffer[(_writePos + index + (_D − 1) * index) % _bufSize] = d[j]; 15− index++; 16− } 17− if(_isEven) { 18− _readPos = (_readPos + 1) % _bufSize; 19− } 20− int out[] = new int[_nnInter]; 21− for(j = 0;j _nnInter;j++) { 22− out[j] = _buffer[_readPos]; 23− _readPos = (_readPos + 1) % _bufSize; 24− } 25− _writePos = _readPos; 26− output.send(0, new ObjectToken(out)); 27− } 28− 29− public void initialize() throws IllegalActionException { 30− .... 31− if(_nnInter % 2 == 0) { 32− _isEven = true; 33− _bufSize = _nnInter + 1 + (_D − 1) * (_nnInter + 1); 34− } 35− else { 36− _isEven = false; 37− _bufSize = _D * _nnInter; 38− } 39− _buffer = new int[_bufSize]; 40− _readPos = 0; 41− _writePos = 0; 42− } 43− 44− private int _buffer[]; 45− private int _bufSize; 46− private int _readPos; 47− private int _writePos; 48− private boolean _isEven; 49− private int _nnInter, _kkInter; 50− .... Figura 4.19 − Trecho de código do ator InterleaverTx. Uma vez que o frame é obtido(linha 6 e 7), cada um de seus bytes é armazenado na respectiva posição do buffer interleaved (linha 13 à 16). Um novo frame é criado extraindo seqüencialmente os dados do buffer (linhas 20 à 26). Os primeiros D frames enviados pelo ator irão conter alguma quantidade de dados inválidos e deverão ser descartados pelo receptor do outro modem. O método initialize() (linhas 29 à 42) calcula (linha 33 à 37) o tamanho mínimo do buffer interleaved para suportar a profundidade especificada. 4.3.3.10 InterleaverRx Esse ator executa o comportamento inverso ao do ator InterleaverTx: os dados são armazenados no buffer de forma seqüencial (linhas 6 à 13) e os tokens são produzidos extraindo dados de várias posições do buffer (linha 19 à 31). 1− ....
  • 91. 76 2− public void fire() throws IllegalActionException { 3− ObjectToken ot = (ObjectToken) input.get(0); 4− int d[] = (int []) ot.getValue(); 5− 6− if(_isEven) { 7− _writePos = (_writePos + 1) % _bufSize; 8− } 9− for(int j = 0;j _nnInter;j++) { 10− _buffer[_writePos] = d[j]; 11− _writePos = (_writePos + 1) % _bufSize; 13− } 14− 15− int index = 0; 16− if(_isEven) { 17− index = 1; 18− } 19− if(_iteration == _D − 1) { 20− int [] out = new int[_nnInter]; 21− for(int j = 0;j _nnInter;j++) { 22− out[j] = _buffer[(_readPos + index + (_D − 1) * index) % _bufSize]; 23− index++; 24− } 25− if(_isEven) { 26− _readPos = (_readPos + _nnInter + 1) % _bufSize; 27− } 28− else { 29− _readPos = (_readPos + _nnInter) % _bufSize; 30− } 31− output.send(0, new ObjectToken(out)); 32− } 33− else { 34− _iteration++; 35− } 36− .... Figura 4.20 − Trecho do código do ator InterleaverRx. A variável _iteration é utilizada para contar as D primeiras iterações, uma vez que nesse período não há dados válidos suficientes para criar um frame. 4.3.3.11 Map O ator Map obtêm frames fast do ator FECTx e frames interleaved do ator InterleaverTx, e com base nesses dados, produz 256 números complexos através da codificação de constelações. A figura 4.21 apresenta um trecho do código desse ator. Implementamos duas formas para a transmissão dos 256 valores: através de 256 portos individuais, um para cada valor, ou através de um porto que transmite os dados como um vetor. Inicialmente utilizamos a primeira versão, mas devido a velocidade da execução, a segunda opção foi adotada. 1− .... 2− public TypedIOPort fast; 3− public TypedIOPort inter; 4− 5− public TypedIOPort subChannel[]; 6− .... 7− public Map(TypedCompositeActor container, String name, int outputMode) 8− throws NameDuplicationException, IllegalActionException { 9− super(container, name); 10− _createInterface(outputMode); 11− } 12− 13− public void fire() throws IllegalActionException { 14− 15− switch(_func) { 16− case 0 : { 17− if(fast.hasToken(0)) { 18− ObjectToken ot = (ObjectToken) fast.get(0); 19− FastFECDataFrame fastf = (FastFECDataFrame) ot.getValue(); 20− int [] data = fastf.getAsArray(); 21− for(int i = 0;i data.length;i++) { 22− _dataBuffer[_dataCounter] = data[i]; 23− _dataCounter++; 24− } 25− _func = 1;
  • 92. 77 26− } 27− } break; 28− 29− case 1 : { 30− if(inter.hasToken(0)) { 31− ObjectToken ot = (ObjectToken) inter.get(0); 32− int [] data = (int []) ot.getValue(); 33− for(int j = 0;j data.length;j++) { 34− _dataBuffer[_dataCounter] = data[j]; 35− _dataCounter++; 36− } 37− _func = 2; 38− } 39− } break; 40− 41− case 2 : { 42− if(_outputMode == 0) { 43− int k = 0; 44− int w = 0; 45− for(int i = 0;i 255;i++) { 46− if(i == 63) { 47− subChannel[63].send(0, new 48− DoubleToken(_gainTable[63].real)); 49− subChannel[63].send(0, new 50− DoubleToken(_gainTable[63].imag)); 51− continue; 52− } 53− int b = 0; 54− for(int j = 0;j _bitTable[i];j++) { 55− b |= (((_dataBuffer[w] k) 0x01) j); 56− k++; 57− if(k == 8) { 58− k = 0; 59− w++; 60− } 61− } 62− Complex Z = _constellation(b, _bitTable[i]); 63− subChannel[_subChannelTable[i]].send(0, 64− new DoubleToken(Z.real)); 65− subChannel[_subChannelTable[i]].send(0, 66− new DoubleToken(Z.imag)); 67− } 68− } 69− else { 70− int k = 0; 71− int w = 0; 72− double symbol[] = new double[510]; 73− for(int i = 0;i 255;i++) { 74− if(i == 63) { 75− symbol[2*i] = _gainTable[63].real; 76− symbol[2*i + 1] = _gainTable[63].imag; 77− } 78− int b = 0; 79− for(int j = 0;j _bitTable[i];j++) { 80− b |= (((_dataBuffer[w] k) 0x01) j); 81− k++; 82− if(k == 8) { 83− k = 0; 84− w++; 85− } 86− } 87− Complex Z = _constellation(b, _bitTable[i]); 88− symbol[2*i] = Z.real; 89− symbol[2*i + 1] = Z.imag; 90− } 91− subChannel[0].send(0, new ObjectToken(symbol)); 92− } 93− _dataCounter = 0; 94− _func = 0; 95− } break; 96− } 97− } 98− 99− public void initialize() throws IllegalActionException { 100− .... 101− _bitTable = new int[255]; 102− _subChannelTable = new int[255]; 103− _gainTable = new Complex[255]; 104−
  • 93. 78 105− int tot = (_nnFast + _nnInter) * 8; 106− for(int i = 0;i 255;i++) { 107− _subChannelTable[i] = i; 108− if(i == 63) { 109− _bitTable[i] = 0; 110− _gainTable[i] = new Complex(1,1); 111− continue; 112− } 113− _gainTable[i] = new Complex(1, 1); 114− 115− if(tot 4) { 116− _bitTable[i] = 5; 117− tot −= 5; 118− } 119− else 120− if(tot 0) { 121− _bitTable[i] = tot; 122− tot = 0; 123− } 124− else { 125− _bitTable[i] = 0; 126− } 127− } 128− _dataBuffer = new int[_nnInter + _nnFast]; 129− _dataCounter = 0; 130− _func = 0; 131− } 132− 133− private void _createInterface(int outputMode) throws 134− IllegalActionException, NameDuplicationException { 135− .... 136− if(_outputMode == 0) { 137− subChannel = new TypedIOPort[255]; 138− for(int i = 0;i 255;i++) { 139− subChannel[i] = new TypedIOPort(this, subChannel_ + i, 140− false, true); 141− subChannel[i].setTypeEquals(DoubleToken.class); 142− } 143− } 144− else { 145− subChannel = new TypedIOPort[1]; 146− subChannel[0] = new TypedIOPort(this, subChannel0, false, true); 147− subChannel[0].setTypeEquals(ObjectToken.class); 148− } 149− .... 150− } 151− private int _bitTable[]; 152− private int _subChannelTable[]; 153− private Complex _gainTable[]; 154− 155− private int _dataBuffer[]; 156− private int _dataCounter; 157− private int _func; 158− private int _outputMode; 159− .... Figura 4.21 − Trecho de código do ator Map. Entre as linhas 16 e 26 os dados do frame fast são obtidos, e entre as linha 30 e 38 os dados do frame interleaved. Entre as linhas 43 e 67 (utilizando a versão com 256 portos de saída) ou entre as linha 70 e 91 (utilizando a versão com um porto de saída), os dados são divididos para cada subcanal (linha 53 à 61, ou 78 à 86) baseados em uma tabela (o vetor _bitTable da linha 151) que especifica os números de bits por subcanal. Essa tabela já deve estar ordenada por tom. Como não implementamos toda a máquina de inicialização, determinamos o número de bits por subcanal de maneira aleatória (método initialize()). O método _constellation() (linhas 62 ou 87) implementa a codificação de constelações descrita no item 4.2.8. O método _createInterface() (linhas 133 à 150) é responsável por construir o ator com a versão correta dos portos de saída. 4.3.3.12 Demap O ator Demap executa a função inversa do ator Map: recebe uma certa quantidade de valores complexos e produz um frame fast e um frame interleave. A figura 4.22 apresenta um trecho de código
  • 94. 79 do ator. Como este ator é utilizado pelo modem ATU−C, ele recebe 32 valores complexos. Assim como no caso do ator Map, duas versão para a quantidade de portos de saída foram empregadas: uma com 32 portos individuais e outra com apenas um porto que recebe um vetor de números complexos. 1− .... 2− public void fire() throws IllegalActionException { 3− 4− int bc = 0, wc = 0; 5− int data[] = new int[_nnInter + _nnFast]; 6− 7− if(_inputMode == 0) { 8− for(int i = 0;i 31;i++) { 9− if(i == 15) { 10− subChannel[15].get(0); 11− subChannel[15].get(0); 12− continue; 13− } 14− DoubleToken dtr = 15− (DoubleToken) subChannel[_subChannelTable[i]].get(0); 16− DoubleToken dti = 17− (DoubleToken) subChannel[_subChannelTable[i]].get(0); 18− 19− Complex Z = new Complex(dtr.doubleValue(), dti.doubleValue()); 20− int b = _constellation(Z, _bitTable[i]); 21− for(int j = 0;j _bitTable[i];j++) { 22− data[wc] |= (((b j) 0x01) bc); 23− bc++; 24− if(bc == 8) { 25− bc = 0; 26− wc++; 27− } 28− } 29− } 30− } 31− else { 32− ObjectToken ot = (ObjectToken) subChannel[0].get(0); 33− double [] indata = (double []) ot.getValue(); 34− for(int i = 0;i 31;i++) { 35− if(i == 15) { 36− continue; 37− } 38− Complex Z = new Complex(indata[2*i], indata[2*i + 1]); 39− int b = _constellation(Z, _bitTable[i]); 40− for(int j = 0;j _bitTable[i];j++) { 41− data[wc] |= (((b j) 0x01) bc); 42− bc++; 43− if(bc == 8) { 44− bc = 0; 45− wc++; 46− } 47− } 48− } 49− } 50− wc = 0; 51− FastFECDataFrame fastf = new FastFECDataFrame(0, 0, 0, 0, 52− _LS0Map ? 0 : _LS0Bytes, 0, 0, _nnFast − _kkFast); 53− 54− fastf.setFastByte(data[wc]); 55− wc++; 56− if(!_LS0Map) { 57− for(int i = 0;i _LS0Bytes;i++) { 58− fastf.setLS0Byte(data[wc], i); 59− wc++; 60− } 61− } 62− if(fastf.hasLEX()) { 63− fastf.setLEXByte(data[wc]); 64− wc++; 65− } 66− for(int i = 0;i (_nnFast − _kkFast);i++) { 67− fastf.setParityByte(data[wc], i); 68− wc++; 69− } 70− fast.send(0, new ObjectToken(fastf)); 71− 72− int [] out = new int[_nnInter]; 73− for(int i = 0;i _nnInter;i++) {
  • 95. 80 74− out[i] = data[wc]; 75− wc++; 76− } 77− inter.broadcast(new ObjectToken(out)); 78− } 79− .... Figura 4.22 − Trecho de código do ator Demap. Entre as linha 8 e 29 ou 32 e 48, o conjunto de valores complexos são obtidos, transformados em um vetor de bits e armazenados em um buffer. Esse bloco utiliza a mesma tabela que o ator Map e utiliza−a para construir os diferentes frames (linha 50 à 77). 4.3.3.13 IDFT Esse ator é responsável por implementar a transformada discreta inversa de Fourier. A entrada é composta por 256 valores complexos, que são estendido para 512 valores (linhas 25 à 29 ou 36 à 39). Esse ator também utiliza as duas versões para os portos de saída. 1− .... 2− public void fire() throws IllegalActionException { 3− int index; 4− 5− if(mode.hasToken(0)) { 6− BooleanToken bt = (BooleanToken) mode.get(0); 7− _mode = bt.booleanValue(); 8− } 9− else { 10− if(_mode) { 11− index = 0; 12− } 13− else { 14− index = 1; 15− } 16− 17− _data[0] = 0; 18− _data[1] = 0; 19− _data[512] = 0; 20− _data[513] = 0; 21− 22− if(_inputMode == 0) { 23− for(int i = 1;i 256;i++) { 24− DoubleToken dt = (DoubleToken) subChannel[i − 1].get(index); 25− _data[2*i] = dt.doubleValue(); 26− _data[1024 − 2*i] = _data[2*i]; 27− dt = (DoubleToken) subChannel[i − 1].get(index); 28− _data[2*i + 1] = dt.doubleValue(); 29− _data[1025 − 2*i] = −1 * _data[2*i + 1]; 30− } 31− } 32− else { 33− ObjectToken ot = (ObjectToken) subChannel[0].get(index); 34− double [] tmp = (double []) ot.getValue(); 35− for(int i = 1;i 256;i++) { 36− _data[2*i] = tmp[2*i − 2]; 37− _data[1024 − 2*i] = _data[2*i]; 38− _data[2*i + 1] = tmp[2*i − 1]; 39− _data[1025 − 2*i] = −1 * _data[2*i + 1]; 40− } 41− } 42− _idft(); 43− 44− if(_outputMode == 0) { 45− for(int i = 0;i 512;i++) { 46− output.send(0, new DoubleToken(_data[2*i])); 47− } 48− } 49− else { 50− double [] symbol = new double[512]; 51− for(int i = 0;i 512;i++) symbol[i] = _data[2*i]; 52− output.send(0, new ObjectToken(symbol)); 53− } 54− }
  • 96. 81 55− } 56− .... Figura 4.23 − Trecho de código do ator IDFT no MoC DE. Conforme pode ser observado na figura 4.12, o ator IDFT possui duas conexões nos seus portos de entrada: uma proveniente do ator Map e outra do ator Initializer. A conexão com o ator Map é referente ao processamento normal dos dados a serem transmitidos. A conexão com o ator Initializer é necessária para enviar os sinais de inicialização. Dessa forma, esse ator deve ser capaz de a qualquer momento, obter os dados de diferentes portos, conforme um modo de operação especificado (linhas 5 à 15). Isso implica que os modelos data flow e o modelo PN não são adequados para capturar tal ator. Utilizamos o modelo DE. 4.3.3.14 DFT Esse ator implementa a transformada discreta de Fourier. Ele recebe 64 valores reais (linhas 12 ou 18) e produz 32 valores complexos (linhas 34 à 37 ou 40 à 44). Assim como o ator IDFT, é necessário que esse ator possua duas conexões diferentes para seus portos de saída. 1− .... 2− public void fire() throws IllegalActionException { 3− 4− int index; 5− if(mode.hasToken(0)) { 6− BooleanToken bt = (BooleanToken) mode.get(0); 7− _mode = bt.booleanValue(); 8− } 9− else { 10− if(_inputMode == 0) { 11− for(int i = 0;i 128;i += 2) { 12− DoubleToken dt = (DoubleToken) input.get(0); 13− _data[i] = dt.doubleValue(); 14− _data[i+1] = 0; 15− } 16− } 17− else { 18− ObjectToken ot = (ObjectToken) input.get(0); 19− double [] tmp = (double []) ot.getValue(); 20− for(int i = 0;i 64;i++) { 21− _data[2*i] = tmp[i]; 22− _data[2*i + 1] = 0; 23− } 24− } 25− _dft(); 26− 27− if(_mode) { 28− index = 0; 29− } 30− else { 31− index = 1; 32− } 33− if(_outputMode == 0) { 34− for(int i = 1;i 32;i++) { 35− subChannel[i − 1].send(index, new 36− DoubleToken(Math.round(_data[2*i])), 1.0); 37− subChannel[i − 1].send(index, new 38− DoubleToken(Math.round(_data[2*i + 1])), 1.0); 39− } 40− } 41− else { 42− double [] out = new double[62]; 43− for(int i = 2;i 64;i++) { 44− out[i − 2] = Math.round(_data[i]); 45− } 46− subChannel[0].send(index, new ObjectToken(out), 1.0); 47− } 48− } 49− } 50− .... Figura 4.24 − Trecho de código do ator DFT no MoC DE.
  • 97. 82 4.3.3.15 Initializer Esse ator implementa a máquina de estados responsável pela inicialização do modem. Conforme o item 4.2.10, a inicialização corresponde a troca de símbolos pré−definidos durante durações exatas, e a execução de operações de configuração e treinamento de blocos do data path. A fim de tornar a implementação desse ator mais clara, criamos a classe auxiliar InitSymbols que contém constantes representando os diferentes estados e sinais de inicialização para ambos os modems. A figura 4.25 apresenta um trecho de código do ator. 1− public class InitSymbols { 2− 3− public static final int C_QUIET1 = 0; 4− public static final int MY_CSILENT1 = 1; 5− public static final int C_IDLE = 2; 6− public static final int C_TONE = 3; 7− public static final int C_ACT = 4; 8− public static final int C_QUIET2 = 5; 9− 10− public static final int R_ACT_REQ = 0; 11− public static final int MY_DETECT_C_ACT = 1; 12− public static final int R_QUIET1 = 2; 13− public static final int R_ACK = 3; 14− 12− public static final int SHOWTIME = 60000; 13− 14− public static final int C_ACT1_S1 = 0; 15− public static final int C_ACT1_S2 = 1; 16− .... 17− public static final int R_ACT_REQ_S1 = 0; 18− public static final int R_ACT_REQ_S2 = 1; 19− .... 20− 21− public InitSymbols() { 22− 23− _ATUCSymbolTable = new double[12][510]; 24− 25− _ATUCSymbolTable[C_ACT1_S1][96] = 1; 26− _ATUCSymbolTable[C_ACT1_S1][97] = 1; 27− _ATUCSymbolTable[C_ACT1_S2][96] = 2; 28− _ATUCSymbolTable[C_ACT1_S2][97] = 2; 29− .... 30− 31− _ATURSymbolTable = new double[9][62]; 32− 33− _ATURSymbolTable[R_ACT_REQ_S1][16] = 1; 34− _ATURSymbolTable[R_ACT_REQ_S1][17] = 1; 35− .... 36− } 37− 38− public boolean checkATURSymbol(int symbol, double [] value) { 39− for(int i = 0;i 62;i++) { 40− if(value[i] != _ATURSymbolTable[symbol][i]) { 41− return false; 42− } 43− } 44− return true; 45− } 46− .... 47− public double [] getATURSymbol(int symbol) { 48− return _ATURSymbolTable[symbol]; 49− } 50− .... 51− private double _ATUCSymbolTable[][]; 52− private double _ATURSymbolTable[][]; 53− } Figura 4.25 − Classe auxiliar com os símbolos de inicialização. Os estados para o modem ATU−C estão representados entre as linhas 3 e 8 e para o modem ATU−R entre as linhas 10 e 13. A potência de transmissão de cada sinal é codificada por um valor real armazenado em uma matriz (linhas 51 e 52), onde a primeira dimensão indica um sinal e a segunda uma freqüência. A codificação do sinal C−ACT1 é feita nas linhas 14 e 15, e é dividida em duas constantes: C_ACT1_S1 e C_ACT1_S2. As duas constantes representam os dois níveis de potência do sinal C−
  • 98. 83 ACT1. Isso é feito similarmente para os outros sinais de inicialização. O método _checkATURSymbol() (linhas 38 à 45) é utilizado para obter o código de um sinal do modem ATU−R, dado um vetor de números reais. O método _getATURSymbol() (linhas 47 à 49) é utilizado para a função inversa. Métodos similares também existem para o modem ATU−C. A partir da definição do processo de inicialização, é possível observar que o momento da ocorrência e o tempo de duração de um sinal é de fundamental importância para a correta inicialização do modem. Dessa forma, é necessário utilizar um modelo computacional flexível e com um modelo de tempo. Como os atores do data path do modem utilizam o modelo DE, e este MoC apresenta as características mencionadas, decidimos utilizá−lo também na captura da máquina de inicialização. Entretanto, deparamos com uma dificuldade na utilização do método fireAt() para o escalonamento de ativamentos futuros. Por exemplo, o sinal R−ACT−REQ é definido como 128 símbolos consecutivos seguidos por um período sem transmissão. Poderíamos modelar essa situação com uma chamada do tipo fireAt(currentTime + quietTime), onde quietTime é o tempo sem atividade de transmissão. Entretanto, caso o modem ATU−C tenha detectado o sinal R−ACT−REQ e respondido apropriadamente, o modem ATU−R deve mudar para outro estado muito provavelmente antes do tempo quietTime. Entretanto, o ativamento futuro devido à chamada do método fireAt() continuaria válido e iria escalonar o ator. Isso pode levar à um comportamento indevido. Uma solução para o problema mencionado seria introduzir a capacidade de eliminar eventos futuros da fila do modelo DE. Entretanto, tal solução parece complexa. Adotamos outra solução que utiliza o ator Timer. A figura 4.26 apresenta o método fire() desse ator. Esse ator é responsável por gerar eventos em momentos requisitados através de um evento no porto StartTimer. A requisição fica ativa até o momento da geração do evento ou até que um evento no porto StopTimer() seja detectado. Neste caso, embora o ator seja ativado (divido ao método fireAt()), nenhum evento é produzido. 1− .... 2− public void fire() throws IllegalActionException { 3− 4− if(stopTimer.hasToken(0)) { 5− if(_active == 0) { 6− throw new IllegalActionException(this, Trying to stop when + 7− it was not started); 8− } 9− stopTimer.get(0); 10− _active = 0; 11− } 12− if(startTimer.hasToken(0)) { 13− if(_active != 0) { 14− throw new IllegalActionException(this, Trying to start when + 15− it was not stoped); 16− } 17− DoubleToken dt = (DoubleToken) startTimer.get(0); 18− double t = dt.doubleValue() + getCurrentTime(); 19− fireAt(t); 20− _active = t; 21− } 22− if(getCurrentTime() == _active) { 23− _active = 0; 24− timerEvent.send(0, new Token()); 25− } 26− } 27− private double _active; 28− .... Figura 4.26 − Método fire() do ator Timer. Entre as linhas 4 e 11 está implementada a anulação de uma requisição. Isso ocorre quando o valor da variável _active é igualado à zero. Entre as linhas 12 e 21 uma nova requisição é processada. A variável _active armazena o instante de tempo em que um evento deve ser produzido. Entre as linhas 22 e 25 um evento é produzido. Note que apenas uma requisição pode ser processada ao mesmo tempo. Além da classe auxiliar InitSymbols e do ator Timer, desenvolvemos alguns métodos auxiliares para o ator Initializer. O método _getSymbol() (figura 4.27) é utilizado para receber e armazenar símbolos. Devido as conexões entre o ator Initializer e os atores IDFT e DFT, o ator Initializer deve possuir as duas versões (vários portos recebendo valores numéricos ou um porto recebendo um vetor) de portos de comunicação.
  • 99. 84 1− private void _getSymbol() throws IllegalActionException { 2− 3− _lastFire = _currentFire; 4− _currentFire = getCurrentTime(); 5− 6− if(fromATUR[0].hasToken(0)) { 7− for(int i = 0;i _symbol.length;i++) { 8− _lastsymbol[i] = _symbol[i]; 9− } 10− if(_fromATURMode == 0) { 11− DoubleToken dt; 12− for(int i = 0;i 31;i++) { 13− dt = (DoubleToken) fromATUR[i].get(0); 14− _symbol[2*i] = dt.doubleValue(); 15− dt = (DoubleToken) fromATUR[i].get(0); 16− _symbol[2*i + 1] = dt.doubleValue(); 17− } 18− } 19− else { 20− ObjectToken ot = (ObjectToken) fromATUR[0].get(0); 21− _symbol = (double []) ot.getValue(); 22− } 23− _hassymbol = true; 24− } 25− else { 26− for(int i = 0;i _symbol.length;i++) { 27− _lastsymbol[i] = _symbol[i]; 28− _symbol[i] = 0; 29− } 30− _hassymbol = false; 31− } 32− } Figura 4. 27 − Método _getSymbol() do ator Initializer. Na linha 4 o momento da recepção do símbolo é armazenado. Entre as linha 7 e 9 o valor do último símbolo é armazenado e entre as linhas 10 e 18 ou 19 e 22 o valor do símbolo recebido é armazenado. A variável _hassymbol (linha 30) indica se um símbolo foi recebido. O método auxiliar _sendSymbol() é utilizado para enviar um símbolo. A figura 4.28 apresenta o código. 1− private void _sendSymbol(int symbol) throws IllegalActionException { 2− double [] s = _initSymbols.getATUCSymbol(symbol); 3− if(_toATURMode == 0) { 4− for(int i = 0;i 255;i++) { 5− toATUR[i].send(0, new DoubleToken(s[2*i])); 6− toATUR[i].send(0, new DoubleToken(s[2*i + 1])); 7− } 8− } 9− else { 10− toATUR[0].send(0, new ObjectToken(s)); 11− } 12− } Figura 4.28 − Método _sendSymbol() do ator Initializer. Na linha 2, o vetor de sinais necessário para enviar o símbolo especificado é obtido com o auxílio da classe InitSymbols. A figura 4.29 apresenta o método _detectSignal(). Esse método foi desenvolvido a fim de simplificar a implementação da máquina de estados. Sua função é determinar quando um sinal foi recebido com sucesso. 1− private byte _detectSignal(int S1, int S2) throws IllegalActionException { 2− 3− if(_rxCounter == 0) { 4− if(_hassymbol) { 5− if(_initSymbols.checkATURSymbol(S1, _symbol)) { 6− _rxCounter++; 7− } 8− else { 9− _rxCounter = 0; 10− return 0; 11− } 12− } 13− else {
  • 100. 85 14− _rxCounter = 0; 15− return 0; 16− } 17− } 18− else 19− if(_rxCounter 64/_BASE) { 20− if(_hassymbol ((_currentFire − _lastFire) == 250)) { 21− if(_initSymbols.checkATURSymbol(S1, _symbol)) { 22− _rxCounter++; 23− } 24− else { 25− _rxCounter = 0; 26− return 0; 27− } 28− } 29− else { 30− _rxCounter = 0; 31− return 0; 32− } 33− } 34− else 35− if(_rxCounter 128/_BASE) { 36− if(_hassymbol ((_currentFire − _lastFire) == 250)) { 37− if(_initSymbols.checkATURSymbol(S2, _symbol)) { 38− _rxCounter++; 39− if(_rxCounter == 128/_BASE) { 40− _rxCounter = 0; 41− return 2; 42− } 43− } 44− else { 45− _rxCounter = 0; 46− return 0; 47− } 48− } 49− else { 50− _rxCounter = 0; 51− return 0; 52− } 53− } 54− return 1; 55− } Figura 4.29 − Método _detectSignal() do ator Initializer. O código da figura 4.29 é dividido em três situações. Entre as linhas 3 e 17 ocorre a recepção do primeiro símbolo do sinal. Apenas o valor do símbolo é verificado (linha 5). Quando alguma condição de recepção for violada, a variável _rxCounter, responsável por contar a quantidade de símbolos recebidos, é igualada à zero. A segunda situação está implementada entre as linha 19 e 33. Neste trecho ocorre a verificação do primeiro nível de potência do sinal. Na linha 20 é verificado se o símbolo foi recebido no instante adequado e na linha 21 o valor do símbolo é verificado. O mesmo ocorre para o segundo nível de potência do sinal entre as linhas 35 e 53. O método _detectSignal() retorna três valores: 0 caso ocorra um erro, 1 caso o ator tenha recebido um símbolo válido mas não é o último do sinal e 2 caso seja o último símbolo. O método _transmitSignal() é apresentado na figura 4.30. Sua função é transmitir um sinal. 1− private byte _transmitSignal(int S1, int S2) throws IllegalActionException { 2− if(_txCounter 64/_BASE) { 4− _sendSymbol(S1); 5− startTimer.send(0, new DoubleToken(249)); 6− _txCounter++; 7− } 8− else 9− if(_txCounter 128/_BASE) { 10− _sendSymbol(S2); 11− _txCounter++; 12− if(_txCounter == 128/_BASE) return 1; 13− startTimer.send(0, new DoubleToken(249)); 14− } 15− return 0; 16− } Figura 4.30 − Método _transmitSignal() do ator Initializer. Entre as linhas 2 e 7 o primeiro nível de potência do sinal é enviado e entre as linhas 9 e 14 o
  • 101. 86 segundo nível de potência. O método retorna 0 enquanto o sinal não tenha sido enviado e 1 quando a transmissão for completada. Finalmente, o código para a máquina de estados ATU−C é apresentado na figura 4.31. 1− .... 1− public void fire() throws IllegalActionException { 2− 3− if(timerEvent.hasToken(0)) { 4− timerEvent.get(0); 5− _timerEvent = true; 6− } 7− else { 8− _timerEvent = false; 9− } 10− _getSymbol(); 11− 12− if(reset.hasToken(0)) { 13− .... 14− } 15− else { 16− switch(_state) { 17− case _initSymbols.C_QUIET1: { 18− _ret = _detectSignal(_initSymbols.R_ACT_REQ_S1, 19− _initSymbols.R_ACT_REQ_S2); 20− if(_ret == 2) { 21− _state = _initSymbols.MY_CSILENT1; 22− startTimer.send(0, new DoubleToken(251)); 23− } 24− } break; 25− 26− case _initSymbols.MY_CSILENT1: { 27− if(!_hassymbol) { 28− _state = _initSymbols.C_ACT; 29− _C_ACTCounter++; 30− } 31− else { 32− _state = _initSymbols.C_QUIET1; 33− _C_ACTCounter = 0; 34− } 35− startTimer.send(0, new DoubleToken(250)); 36− _rxCounter = 0; 37− } break; 38− 39− case _initSymbols.C_ACT: { 40− _ret = _transmitSignal(_initSymbols.C_ACT2_S1, 41 _initSymbols.C_ACT2_S2); 42− if(_ret == 1) { 43− _txCounter = 0; 44− _state = _initSymbols.C_QUIET2; 45− startTimer.send(0, new DoubleToken(250 * 129)); 46− } 47− } break; 48− 49− 50− case _initSymbols.C_QUIET2: { 51− _ret = _detectSignal(_initSymbols.R_ACK2_S1, 52− _initSymbols.R_ACK2_S2); 53− if(_ret == 2) { 54− _state = _initSymbols.SHOWTIME; 55− _rxCounter = 0; 56− startTimer.send(0, new DoubleToken(250)); 57− if(!_timerEvent) stopTimer.send(0, new Token()); 58− _C_ACTCounter = 0; 59− } 60− else 61− if(_ret == 0) { 62− if(_C_ACTCounter 2) { 63− _state = _initSymbols.C_ACT; 64− _C_ACTCounter++; 65− } 66− else { 67− _state = _initSymbols.C_QUIET1; 68− _C_ACTCounter = 0; 69− } 70− _rxCounter = 0; 71− if(!_timerEvent) stopTimer.send(0, new Token()); 72− } 73− else
  • 102. 87 74− if(_ret == 1) { 75− if(_timerEvent) { 76− if(_C_ACTCounter 2) { 77− _state = _initSymbols.C_ACT; 78− _C_ACTCounter++; 79− } 80− else { 81− _state = _initSymbols.C_QUIET1; 82− _C_ACTCounter = 0; 83− } 84− _rxCounter = 0; 85− } 86− } 87− } break; 88− .... 89− } 90− } 91− } 92− .... Figura 4.31 − Método fire() do ator Initializer. Entre as linhas 3 e 9 é verificado a presença de um evento enviado pelo ator Timer. Na linha 10 a presença de um símbolo é testada. Entre as linhas 12 e 14 a condição de reinicialização é verificada. O estado C−QUIET1 está implementado entre as linhas 17 e 23. Caso um sinal válido seja detectado (linha 18), ocorre a transição para o estado MY−CSILENT1 (linha 20 e 21). Esse estado (linhas 26 à 37) é utilizado para verificar a ausência do sinal R−ACT−REQ. O estado C−ACT (linhas 39 à 47) é utilizado para transmitir o respectivo sinal (linha 40 e 41). Na linha 45 está implementado a máxima duração permitida para a espera do sinal de acknowledgment do modem ATU−R. Caso esse sinal seja detectado, o código da linha 54 à 58 é executado e a fase de ativação está encerrada. Caso contrário, o máquina de estados irá esperar até atingir o limite máximo ou continuar recebendo o sinal. A máquina de estados para o modem ATU−R é implementada de maneira similar, mas respeitando os estados e transições descritos no item 4.2.10. 4.4 Discussão Esse capítulo apresentou o estudo de caso que desenvolvemos. Criamos uma especificação executável para um subconjunto de um modem ADSL. O principal objetivo desse esforço é identificar alguma estratégia para utilizar os resultados obtidos através do estudo das primitivas comportamentais na escolha de um modelo computacional. Após termos analisado a especificação textual do modem (item 4.2), determinamos que o MoC PN parecia ser o mais adequado, devido à grande quantidade de blocos transformacionais (primitiva comportamental ¨seqüência de expressões¨). Além disso, esse modelo também apresentava uma noção de tempo (ao contrário dos modelos data flow), o que era necessário para capturar o comportamento da inicialização. De fato, foi possível criar uma especificação eficiente para todo o data path do modem (vide figura 4.12). Entretanto, quando tentamos introduzir o bloco de inicialização, não foi possível implementar eficientemente os atores IDFT e DFT. Conforme foi mostrado no item 4.3.3.15, é necessário que esses atores se comuniquem com a máquina de inicialização, além dos atores do data path. Alem disso, o padrão de comunicação não é conhecido. Dessa forma, temos a primitiva comportamental de desvio condicional. Como foi demonstrado no capítulo 2, o modelo PN não é o mais adequado para capturar essa primitiva. Para terminar a descrição do modem, tivemos então que escolher entre dois MoCs: SR e DE. Escolhemos o modelo DE devido ao modelo explícito de tempo. Entretanto, a utilização do modelo SR é possível e deve ser considerada como um trabalho futuro. O modelo DE se mostrou adequado na captura do restante do modem. O único problema encontrado foi referente à necessidade de anular eventos já escalonados, conforme foi mencionado no item 4.3.3.15. Desenvolvemos uma solução eficiente que emprega um ator temporizador. Podemos considerar a captura da máquina de inicialização como um exemplo de uma nova primitiva comportamental: protocolo de comunicação. Essa primitiva se caracteriza pela definição precisa da troca de eventos entre dois ou mais atores, incluindo restrições temporais. Como trabalho futuro, utilizaremos nossa ferramenta para depuração/profiling na especificação executável do modem, a
  • 103. 88 fim de estudar tal primitiva.
  • 104. 89 Capítulo 5 Conclusões e Trabalhos Futuros 5.1 Considerações Finais Nessa dissertação abordamos o problema da adequação/eficiência de um modelo computacional para a criação de especificações executáveis. O ambiente Ptolemy II foi utilizado como ferramenta de software para o desenvolvimento do trabalho, uma vez que além de possuir implementado alguns modelos computacionais, ele também provê uma infra−estrutura eficiente para a implementação de outros MoCs. Seis modelos computacionais foram comparados. Nossa metodologia fundamenta−se no conceito de primitiva comportamental, conforme definida no capítulo 3. Desenvolvemos especificações para capturar três primitivas e analisamos a adequação de cada uma com relação aos seis modelos. Outras cinco primitivas comportamentais foram enumeradas. Uma ferramenta para depurar a execução de uma especificação foi desenvolvida para auxiliar a análise de cada primitiva. Essa ferramenta também pode ser utilizada para outras finalidades, como melhorar a eficiência de uma especificação ou comparar diferentes implementações de um mesmo modelo computacional. Finalmente, desenvolvemos um estudo de caso de um sistema real, com o intuito de aplicar os resultados da análise das três primitivas comportamentais. Como resultado, identificamos como cada parte do sistema utilizava uma primitiva comportamental. Além disso, uma nova primitiva foi identificada. 5.2 Trabalhos Futuros Podemos enumerar as seguintes contribuições pretendidas por este trabalho: 1. uma biblioteca de critérios para auxiliar o projetista nas tarefas de escolher os modelos computacionais mais adequados e construir as especificações executáveis. Duas métricas devem ser consideradas: captura/validação e implementação física (síntese); 2. ferramentas de análise de especificações executáveis; 3. uma estratégia de como aplicar os resultados das contribuições anteriores em um sistema de grande porte. Neste trabalho ainda não alcançamos tais objetivos. Analisamos três primitivas comportamentas considerando o critério de captura/validações e desenvolvemos o protótipo de uma ferramenta para análise de especificações executáveis. As seguintes tarefas futuras devem ser efetuadas para alcançarmos tais objetivos: 1. ampliar o número de primitivas comportamentais analisadas; 2. avaliar outros modelos computacionais que não estão implementados no ambiente Ptolemy; 3. criar variantes de modelos computacionais implementados no ambiente Ptolemy; 4. analisar a utilização de combinações (pares, triplas, etc.) de MoCs para descrever sistemas (toy benchmarks) compostos por uma ou mais primitivas comportamentais; 5. incorporar à análise uma métrica que permita estimar a eficiência da utilização de um determinado MoC para a implementação física de uma primitiva comportamental; 6. desenvolver estudos de caso de sistemas de grande porte. A primeira tarefa consiste em realizar a análise de primitivas comportamentais que já identificamos como relevantes. São seis primitivas ao total: sincronismo, compartilhamento de recursos, concorrência, preempção, recursão e protocolo de comunicação. Outras primitivas poderão ser incluídas
  • 105. 90 durante o desenvolvimento do trabalho. As tarefas 2 e 3 procuram ampliar o leque de alternativas para a análise das primitivas. Para tal, pretendemos introduzir novos modelos computacionais ao ambiente Ptolemy e modificar modelos já implementados através da variação de alguma de suas características, como por exemplo o escalonador. O ambiente Ptolemy é implementado de tal forma que é possível utilizar, em uma mesma especificação executável, vários modelos computacionais distintos, por exemplo modelos data flow, discrete event, synchronous reactive, redes de Petri etc. O principal argumento para a necessidade dessa característica é a heterogeneidade das aplicações, ou seja, em um único sistema existem subsistemas de hardware dominados pelo fluxo de dados, software de tempo real, circuitos assíncronos, circuitos analógicos, hardware dominado pelo fluxo de controle, etc. Dessa forma, um único modelo computacional não é capaz de capturar eficientemente tais aplicações. A tarefa 4 visa estudar a aplicação de mais de um modelo computacional em uma única aplicação. Com base no resultado da adequação do par primitiva/MoC, iremos utilizar especificações executáveis de sistema reais, ou parte desses sistemas, para verificar se o particionamento da especificação em vários MoCs é vantajosa. É necessário considerar a implementação dos sistemas a partir de especificações executáveis. Isto implica em analisar as metodologias de síntese, ou seja, uma seqüência de tarefas (e ferramentas CAD associadas) que refinam a especificação inicial até atingir um grau de abstração conhecido e dominado. Por exemplo, no caso da síntese de circuitos, o objetivo é criar uma descrição em nível comportamental [MIC94], ou uma descrição RTL para posterior síntese [MIC94]. No caso de software o objetivo é obter um programa em alguma linguagem seqüencial (por exemplo C), mais o sistema operacional. Na tarefa 5, pretendemos escolher um número reduzido de MoCs adequados para síntese, e estudar a viabilidade de tradução/refinamento dos outros modelos para os modelos de síntese. Na tarefa 6, iremos aplicar os resultados e conclusões obtidos nas 5 tarefas anteriores. A fim de evitar o tempo (longo) necessário para estudar as especificações de um sistema de grande porte e gerar uma descrição inicial, adotaremos a estratégia de interagir com grupos de pesquisa que já realizaram essa tarefa e com os quais estamos em contato. Possíveis exemplos18 para estudo de caso são:   sistema para tradução de textos: em [FRA00], foi descrito um sistema embutido para a tradução de textos utilizando uma solução com microprocessadores e ASIC. Tal sistema foi desenvolvido por colegas da Universidade Federal do Rio Grande do Sul;   codificador/Decodificador de Voz: em [GAJ00] está descrito detalhadamente em vários níveis de abstração um sistema vocoder para telefones celulares;   sistemas para comunicação sem fio: nosso grupo de pesquisa possui um conjunto de pesquisadores desenvolvendo soluções relacionadas à comunicação sem fio. Já foi desenvolvido em [GIU98] um módulo codificador/decodificador de canal utilizando códigos convolucionaiss. Atualmente um aluno de mestrado e outro de doutorado estão aprimorando tal módulo utilizando técnicas iterativas [GIU00]. Outro aluno de doutorado esta desenvolvendo como parte de seu trabalho a implementação de alguns módulos do sistema Bluetooth [BLUE], um padrão de comunicação em radio freqüência para curtas distâncias;   modem ADSL: podemos ampliar o estudo de caso desenvolvido nesse trabalho ampliando a funcionalidade capturada, implementando novos módulos e utilizando outros MoCs. 18 Oportunamente, um ou mais destes exemplos, ou outros que possam surgir, serão usados para esta tarefa.
  • 106. 91 Referências Bibliográficas [ADSL] ANSI/T1E1.413, Asymmetric Digital Subscriber Line (ADSL) Mettalic Interface, June 12, 1998. [ARV82] Arvind, K. P. Gostelow, The U−Interpreter, Computer, February, 1982. [BEN91] A. Benveniste, G. Berry, The Synchronous Approach to Reactive and Real−Time Systems, Proceedings of the IEEE, Vol. 79, No. 9, September 1991. [BEC92] D. Becker, R. K. Singh, S. G. Tell, An Engineering Environment for HW/SW Co− Simulation, Proceedings of the Design Automation Conference, 1992. [BER98] G. Berry, The Esterel v5 Language Primer, Centre de Mathématiques Appliquées, Ecole des Mines and INRIA, March, 1998. [BHA94] S. S. Bhattacharyya, Compiling Dataflow Programs for Digital Signal Processing, PhD Dissertation, Dept. EECS, University of California, Berkeley, July, 1994. [BIL96] G. Bilsen, M. Engels, R. Lauwereins, J. Peperstraete, Cyclo−Static Dataflow, IEEE Transactions on Signal Processing, Vol. 44, No. 2, February, 1996. [BLUE] Especificação do Padrão Bluetooth: http://www.bluetooth.com [BRE98] J. E. Brewer, A New and Improved Roadmap, IEEE Circuits Devices, March, 1998. [BRO81] J. D. Brock, W. B. Ackerman, Scenarios, a model of nondeterminate computation¨, Conf. on Formal Definition of Programming Concepts, LNCS 107, 1981. [BUC93] J. T. Buck, Scheduling Dynamic Dataflow Graphs with Bounded Memory using the Token Flow Model, PhD Dissertation, Dept. EECS, University of California, Berkeley, 1993. [CHA99] H. Chang, L. Cooke, M. Hunt, G. Martin, A. McNelly, L. Todd, Surviving the SOC Revolution : A Guide to Platform−Based Design, Kluwer Academic Publishers, ISBN 0−7923− 8679−5, November, 1999. [COS99] P. Coste, et al, Multilanguage Systems Codesign, 7th International Workshop on Hardware/Software Codesign, May, 1999.l [DAV82] A. L. Davis, R. M. Keller, Data Flow Program Graphs, Computer, February, 1982. [DAV99] J. Davis II, et al, Ptolemy II: Heterogeneous Concurrent Modeling and Design in Java, Report ERL No. M99/63, Dept. EECS, University of California, Berkeley, December, 1999. [DEN80] J. B. Dennis, Data Flow Supercomputers, Computer, November, 1980. [EDW94] S. Edwards, An Esterel Compiler for a Synchronous/Reactive Development System, Report ERL No. M94/43, Dept,. EECS, University of California, Berkeley, December, 1994. [EDW97] S. Edwards, The Specification and Execution of Synchronous Reactive Systems, PhD Dissertation, Report ERL, No. M97/31, Dept. EECS, University of California, Berkeley, 1997. [FDR] Ferramenta FDR2, www.formal.demon.co.uk/FDR2.html , Formal Systems.
  • 107. 92 [FRA00] D. T. Franco, L. Carro, Printed Text Translation System in FPGA, XV International Conference on Microelectronics and Packaging (SBMicro), September, 2000. [GAJ00] D. D. Gajski, J. Zhu, R. Dömer, A. Gerstlauer, S. Zhao, SPECC: Specification Language and Methodology, Kluwer Academic Publishers, 2000. [GIR99] A. Girault, B. Lee, E. A. Lee, Hierarchical Finite State Machines with Multiple Concurrency Models, IEEE Transactions on Computer−Aided Design of Integrated Circuits and Systems, Vol. 18., No. 6, June, 1999. [GIU98] A. Giulietti, Projeto de um Par Codificador/Decodificador Convolucional Integrado, Dissertação de Mestrado, Departamento de Engenharia Elétrica, Escola Politécnica, Universidade de São Paulo, 1998. [GIU00] A. Giulietti, et al., A Trade−off Study on Concatenated Channel Coding Techniques for High Data Rate Satellite Communications, 2nd International Symposium on Turbo Codes Related Topics (BREST), September 2000. [GOE98] M. Goel, Process Networks in Ptolemy II, Report ERL M98/69, Dept. EECS, University of California, Berkeley, December, 1998. [GUP97] R. K. Gupta, S. Y. Liao, Using a Programming Language for Digital System Design, IEEE Design Test of Computers, April−June, 1997. [HAL93] N. Halbwachs, Synchronous programming of reactive systems, Kluwer Academic Publishers, ISBN 0−7923−9311−2, 1993. [HAR87] D. Harel, StateCharts: A visual formalism for complex systems, Sci. Comput. Prog., vol 8, pp. 231−274, 1987. [HUD89] P. Hudak, Conception, Evolution, and Application of Functional Programming Languages, ACM Computing Surveys, Vol. 21, No. 3, September 1989. [HOA78] C. A. R. Hoare, Communicating Sequential Processes, Communications of the ACM, Vol. 21, No. 8, August, 1978. [HOA85] C. A. R. Hoare, Communicating Sequential Processes, Prentice Hall International, ISBN 0− 13−153271−5, 1985. [KAH74] G. Kahn, The Semantics of a Simple Language for Parallel Programming, Information Processing, 1974. [KAH77] G. Kahn, D. B. MacQueen, Coroutines and Networks of Parallel Processes, Information Processing, 1977. [KAR66] R. M. Karp, R. E. Miller, Properties of a Model for Parallel Computations: Determinacy, Termination, Queueing, SIAM J. Appl. Math, Vol. 14, No. 6, November, 1966. [LAV99] L. Lavagno, E. Sentovich, ECL: A Specification Environment for System−Level Design, Proceedings of the ACM Design Automation Conference, 1999. [LAV00] L. Lavagno, A. Sangiovanni−Vincentelli, E. Sentovich, Models of Computation for System Design, em: Egon Boerger (ed.), Architecture Design and Validation Methods, Springer−Verlag, January 2000, pp. 243−296.
  • 108. 93 [LEE87a] E. A. Lee, D. G. Messerschmitt, Static Scheduling of Synchronous Data Flow Programs of Digital Signal Processing, IEEE Transactions on Computer, Vol. C−36, No. 1, January, 1997. [LEE87b] E. A. Lee, D. G. Messerschmitt, Synchronous Data Flow, Proceedings of the IEEE, Vol. 75, No. 9, September, 1987. [LEE95] E. A. Lee, T. M. Parks, Dataflow Process Networks, Proceedings of the IEEE, Vol 83., No. 5, May, 1995. [LEE98] E. A. Lee, A. Sangiovanni−Vincentelli, A Framework for Comparing Models of Computation, IEEE Transactions on Computer−Aided Design of Integrated Circuits and Systems, Vol. 17, No. 12, December, 1998. [LIU98] J. Liu, Continuous Time and Mixed−Signal Simulaiton in Ptolemy II, Report ERL M98/74, Dept. EECS, University of California, Berkeley, December, 1998. [MAR98] J. Martin, et al, Modeling Reactive Systems in Java, ACM Transaction on Design Automation of Electronic Systems, Vol 3, No. 4, October 1998. [MIC94] G. De Micheli, Synthesis and Optimization of Digital Circuits, McGraw−Hill International Editions, ISBN 0−07−016333−2, 1994. [MUL99] L. Muliadi, Discrete Event Modeling in Ptolemy II, MS Report, Dept. EECS, University of California, Berkeley, May, 1999. [PAN92] P. Panagaden, V. Shanbhogue, The expressive power of indeterminate dataflow primitives, Inf. And Computation, Vol. 98, No. 1, May, 1992. [PAR95] T. M. Parks, Bounded Scheduling of Process Networks, PhD Dissertation, Dept. EECS, University of California, Berkeley, December, 1995. [PET81] J. L. Peterson, Petri Net Theory and the Modeling of Systems, Pretince−Hall, ISBN 0−13− 661983−5, 1981. [REE95] H. J. Reekie, Realtime Signal Processing, PhD Dissertation, School of Electrical Engineering, University of Technology at Sydney, September, 1995. [SMY98] N. Smyth, Communicating Sequential Processes Domain in Ptolemy II, Report ERL M98/70, Dept. EECS, University of California, Berkeley, December, 1998. [SYSC] System C User’s Guide, Version 1.0. http://www.systemc.org.