Table of Contents

Lab. Microprocessadohres PCS3732 - Curso Cooperativo prof. Jorge Kinoshita.

  1. quadrimestre 2021

turma: Quinta 13:30-17:10H turma: Terca 13:30-17:10H

Aulas:

1 <2021-05-06 qui> . E1: Introducao a microprocessadores, com ênfase ao ARMv7.

1.1 Explicar o curso, sequencia das aulas e avaliacao.

1.2 video sobre gnuarm, OPCIONAL

O seguinte video explique como criar codigo usando o gnuarm. Nao eh obrigado assisti-lo, mas se o fizer, nao gaste muito tempo com isso pois lembre-se que voce deve entregar o relatorio ateh o final da aula. Alem disso, diversos topicos serao esclarecidos durante o curso.

1.3 Instale o docker:

O docker estah instalado em sua maquina? Se sim, pule isso.

Siga: https://phoenixnap.com/kb/how-to-install-docker-on-ubuntu-18-04

Dependendo de como vc. instalar as coisas vao dar errado… por exemplo, para mim, a instalacao com snap nao funcionou; assim siga algum site como o acima, voltado para o ubuntu 18.4

Se acabou de instalar faca:

docker run hello-world

Se nao rodou, voce ainda nao deve ter permissoes. Experimente:

sudo docker run hello-world

Se nao rodou, vc nao instalou nada direito; mas se com "sudo" rodou veja: ( https://www.digitalocean.com/community/questions/how-to-fix-docker-got-permission-denied-while-trying-to-connect-to-the-docker-daemon-socket )

sudo groupadd docker
sudo usermod -aG docker ${USER}

Voce terah que carregar o ambiente atraves de um reboot ou de um

su -s ${USER}

Seu usuario pertence ao grupo docker? Veja com:

groups

Teste se tudo estah certo atraves de:

docker run hello-world

1.3.1 Instalacao no Windows

Raramente dah certo. Verifique se tem o WSL instalado. Se tiver problemas, provavelmente o kernel linux que roda no WSL não é binário-compatível com o gcc da imagem do Eric. Resolva isso usando uma VM ou rodando no linux diretamente. Outro problema: quando vc. for instalar algo no ubuntu / WSL, usando apt-get install, pode nao funcionar bem. A propria interface grafica dentro do WSL parece ter problemas. Enfim, nao se entuasiasme com o WSL e aproveite para aprender o Ubuntu sem sofrer com coisas desnecessarias.

1.4 gnuarm e código "hello world" seguinte post no linux-kernel-lab.

Faça com que o ARM imprima hello world de acordo com: http://linux-kernel-lab.blogspot.com.br/2018/04/basics-on-arm-processor.html Nesse post, vc. deve basicamente:

  • instalar a imagem do docker que contém todo o ambiente de desenvolvimento a ser usado em nosso curso: 16.2
  • rodar hello.c

Veja o seguinte video para a criacao da imagem do docker a ser usada em nosso curso:

Este video apresenta como rodar um "hello-world.c" dentro do gnuarm:

Se tiver interesse, veja as referencias 16.7, 16.8, 16.9, 16.10.

Ao rodar o gdb, veja os registradores:

(gdb) înfo registers
(gdb) p $pc   -> apresenta os $pc como numero decimal
(gdb) p/x $r0 -> apresenta $r0 como numero hexadecimal
(gdb) p/x $cpsr -> apresenta o $cpsr como numero hexadecimal.

1.4.1 Possiveis problemas ao seguir o post

  1. Problema de permissao ao rodar ./builddocker.sh

    Este erro acontece nas maquinas do lab micro, mas nao costuma acontecer em seu computador pessoal. Para corrigir, na maquina do lab, faca:

    ~/gcc-arm$ sudo ./build_docker.sh
    
  2. Nao tem permissao para criar arquivo hello.c em src

    Este erro acontece nas maquinas do lab micro, mas nao costuma acontecer em seu computador pessoal. Para corrigir, na maquina do lab, faca:

    ~/gcc-arm$ chmod 777 src
    

1.5 Usando o gnuarm, rode o programa da pagina 2-3 da apostila ARM Lab (item 2-2).

Observe que vc. deverá colocar o programa no formato GNU. Uma forma de fazer isso é observar o hello.s e fazer as modificacoes. Em caso de dúvidas sobre as diretivas, consulte os manuais em https://sourcery.mentor.com/sgpp/lite/arm/portal/release830; em particular consule o manual do GNU Assembler. A modificação já feita do codigo assembly do item 2-2 fica assim no gnumarm:

---------------------------------------------------

	.text
	.globl main
main:
	MOV	r0, #15	     @ comentarios vem depois de @ ou entre /* ... */		
	MOV	r1, #20
	BL	firstfunc    @ desvia para funcao, coloca o enderenco de retorno em R14 ou LR (link register).		
	MOV	r0, #0x18		
	LDR	r1, =0x20026		
	SWI	0x123456		
firstfunc:
	ADD	r0, r0, r1		
	MOV	pc, lr	     @ retorna da funcao		
--------------------------------------------------

Para isso vc. pode fazer:

gedit item-2-2.s e fazer o copy and paste.

Experimente trocar ADD por ADDS para ver os registradores no CPSR

---------------------------------------------------

	.text
	.globl main
main:
	MOV	r0, #15	     @ comentarios vem depois de @ ou entre /* ... */		
	MOV	r1, #20
	BL	firstfunc    @ desvia para funcao, coloca o enderenco de retorno em R14 ou LR (link register).		
	MOV	r0, #0x18		
	LDR	r1, =0x20026		
	SWI	0x123456     @ no simulador (target sim) nao existe codigo a ser rodado quando se faz uma SWI sw. interrupt e portanto, gera erro.		
firstfunc:
	ADDS	r0, r0, r1   @ O S em ADDS coloca as flags N Z C V no CPSR		
	MOV	pc, lr	     @ retorna da funcao		
--------------------------------------------------

Vamos mostrar passo a passo como compilar/montar e rodar item-2-2.s .

  • Rode a image do docker. O jeito mais fácil é:
~/Downloads/gcc-arm$ ./run_docker.sh 
  • Compile/monte o código item-2-2.s ; o jeito mais fácil é aproveitar o alias gcc que já existe dentro da imagem:
student:~/src$ ls
alo  hello  hello.c  hello.s  item-2-2.s
student:~/src$ alias gcc
alias gcc='arm-elf-gcc -Wall -Wextra -g'
student:~/src$ gcc item-2-2.s 
  • Ao rodar, o arquivo a.out é gerado como saida:
student:~/src$ ls -alt |more
total 500
-rwxrwxr-x 1 student student 126239 Apr 16 00:40 a.out
drwxrwxrwx 2 student student   4096 Apr 16 00:40 .
-rw-rw-r-- 1 student student     39 Mar 20 19:38 hello.c
-rwxrwxr-x 1 student student 221628 Mar 20 19:34 alo
-rw-rw-r-- 1 student student    396 Mar 20 19:31 hello.s
-rwxrwxr-x 1 student student 221868 Mar 20 19:25 hello
drwxr-xr-x 1 student student   4096 Mar 20 19:17 ..
-rw-rw-rw- 1 student student    147 Mar 20 19:11 item-2-2.s
  • Para entrar no gdb, o jeito mais fácil é usar o alias gdb:
student:~/src$ alias gdb
alias gdb='arm-elf-gdb -tui --command=/home/student/.gdbinit/default'
student:~/src$ gdb a.out 
  • Uma veze dentro do gdb,

Tecle "return" para continue até ver algo como:


   │7               MOV     r0, #0x18                                          │
   │8               LDR     r1, =0x20026                                       │
   └───────────────────────────────────────────────────────────────────────────┘
sim No process In:                                           Line: ??   PC: 0x0 
Loading section .data, size 0x8a8 vma 0xa118
Loading section .eh_frame, size 0x4 vma 0xa9c0
Loading section .ctors, size 0x8 vma 0xa9c4
Loading section .dtors, size 0x8 vma 0xa9cc
Loading section .jcr, size 0x4 vma 0xa9d4
Start address 0x8110
Transfer rate: 27882 bits/sec.
(gdb) 
  • vamos nos posicionar para rodar a primeira instrucao; para isso, "break main" seguido de "run" ou de forma mais sucinta "b main" , "r" como vemos abaixo:
(gdb) b main
Breakpoint 1 at 0x8218: file item-2-2.s, line 4.
(gdb) r
Starting program: /home/student/src/a.out 

Breakpoint 1, main () at item-2-2.s:4
Current language:  auto; currently asm
(gdb) 
  • agora voce já pode rodar passo a passo! Experimente usar "step" ou "next" ; de forma mais sucinta "s" ou "n". Veja que os registradores vão se modificando.
  • Para sair do debug "quit" ou "q".

Veja isso ocorrendo no video:

1.5.1 Uma software interrupt

No código temos:

LDR	r1, =0x20026		
SWI	0x123456		

que se refere a uma software interrupt pedindo um servico do monitor da placa evaluator7t. Como estamos rodando de forma simulada, essa software interrupt irá travar o gdb. Assim, nao rodem o SWI (basta colocar um breakpoint). Uma pergunta interessante é:

  • qual a diferenca entre LDR e MOV? Observem que ambas as instrucoes carregaram valores imediatos nos registradores. A diferenca é que com LDR é possível carregar valores quaisquer de 32 bits enquanto que com o MOV não. Isso será importante para a questao 3.10.1

1.6 video sobre o processador ARM, OPCIONAL

Voce ainda nao tem condicoes de entender tudo o que o video explica. Muita coisa ficarah clara no decorrer do curso principalmente depois da aula sobre interrupcoes. Assim, nao gaste tempo com isso.

1.7 referência: ARM Laboratory Exercises, cap. 1

Ler o primeiro capítulo de 16.1. Fazer exercícios 1.7

1.8 Entregue o relatorio ateh o final do dia, INDIVIDUALMENTE.

  • O relatorio eh para se fazer em GRUPO. Deve conter as respostas do cap 1 da apostila, 1.7.1 e 1.7.2 ; mas a entrega eh individual, ou seja, dois alunos da mesma equipe acabam entregando o mesmo relatorio.
  • Cada aluno recebeu a atividade de entrega do relatorio do google classroom; essa entrega eh INDIVIDUAL. Isto eh, o mesmo relatorio do grupo serah entregue várias vezes: uma para cada integrante. Caso o aluno da equipe nao entregue o relatorio, vou entender que ele nao participou do relatorio.

2 <2021-05-13 qui> . E2: Programming Basics (cap2) + Data Processing Operations (cap3).

2.1 PLANEJAMENTO INDIVIDUAL (3 pontos):

Antes da aula, leia o cap2 e o cap 3 da apostila. Rode o código item-2-2.s no seu computador e acompanhe os registradores sendo modificados. Observe as flags do cspsr (apostila cap 1) ao fazer a soma. As flags são atualizadas? (Resposta: Não). Agora, no código item-2-2.s troque ADD por ADDS. As flags são atualizadas logo depois da soma (ADD)? Retorne essa tarefa com um arquivo pdf contendo dois printscreens da tela de SEU COMPUTADOR do item-2-2.s mostrando os registradores, principalmente o CPSR. quando: printscreen1) antes de rodar a instrucao ADDS printscreen2) depois de rodar a instrucao ADDS O objetivo eh observar como variam as flags do CPSR. Escreva o quanto valem as flags N,C,Z,V e se isso estah compativel com o esperado. No seu planejamento em pdf explique se as flags variaram como voce esperava que variassem.

2.2 OBJETIVO:

  • capítulo 2 e parte do 3 da apostila, mas ao invés de usarmos o codewarrior para windows estaremos usando o gnuarm no linux ubuntu.

Facam os seguintes exercicios da apostila e criem um relatorio com as respostas dos exercicios:

  • cap2: todo o item 2.4: de 2.4.1 a 2.4.3
  • cap3: Faca de 3.10.1 ateh 3.10.4 ; Veja detalhadamente as intrucoes ARM em 16.11

2.3 Fazer todos os exercícios do item 2.4 pagina 2-8 da apostila usando o gnuarm.

Observacao sobre os exercicios As perguntas do item 2.4 se referem ao codewarrior (ambiente usado pela apostila no lugar do gnuarm); por isso pensem nas questoes referindo-se ao gnuarm.

2.3.1 Exercicio 2.4.1

No ambiente codewarrior existe um make. Durante o nosso curso, nao precisaremos do make uma vez que o proprio arm-elf-gcc dispara o assembler e o linker. Para ver o que foi gerado pelo arm-elf-gcc basta fazer:


$ ls -alt|more


Esse comando coloca no topo os arquivos recentemente modificados ou criados. Apenas relatem o que foi feito - uso do arm-elf-gcc, geracao do arquivo e arm-elf-gdb para rodá-lo.

2.3.2 Exercicio 2.4.2

  1. step x next

    A pergunta 2.4.2 pergunta sobre a diferenca entre step e stepin no codewarrior . Para o gdb, a pergunta se refere a: step: passo a passo entrando na rotina next: passo a passo mas sem entrar na rotina. Existe um problema GRAVE no uso do next. O arm-elf-gdb misteriosamente se perde ao ver um label como:

      mov r0,1
    label:
      move r0,2
    

    Ao executar mov r0,1; o debugger nao pula para a instrucao seguinte usando next. Por isso, muito preferencialmente use 'step'.

  2. Problema ao executar SWI

    A instrucao SWI 0x123456 eh uma interrupcao de software que roda no programa monitor que estah na flash da placa Evaluator-7T. Nós estamos simulando a execucao do codigo no gdb (target sim). O simulador nao possui o tratamento para essa interrupcao de software, por isso, coloque um breakpoint na linha onde estah a instrucao SWI (ex: linha 9) atraves de "b 9" e rode ateh lah. Dado que estamos sem monitor ou sistema operacional, estaremos sempre fazendo dessa forma: colocando um breakpoint no final do programa para observarmos se rodou ateh o final.

2.3.3 Exercicio 2.4.3

Se quisermos ver os registradores na tela do arm-elf-gdb usando C-x 2, teremos dois formatos hexa e decimal. Porém, é possível observar memória e registradores em outros formatos. Veja o manual do gdb - http://sourceware.org/gdb/download/onlinedocs/gdb/index.html Exemplos:

p/x $pc
p/x $cpsr
x/i $pc

Usando o help help x help p

Voce deve ter observado que x - serve para ver memoria externa.

x/d $r1

apresenta o conteudo de r1 em hexadecimal e o conteudo apontado por r1 na memoria em decimal.

p/d $r1

apresenta o conteudo de r1 em decimal.

Uma forma !!!PERIGOSA!!! de ver os bits do registrador de status eh:

p/t $cpsr

porem, os primeiros zeros serao OMITIDOS e voce pode estar vendo menos que 32 bits. Tome cuidado! Compare o cpsr com o comando "info registers". A forma segura eh:

p/x $cpsr

2.4 Estude o capítulo 3 da apostila.

2.4.1 pg 3-7:

ADD r0, r1, #0xc5, ROR 10 ver desenho na pagina 3-6.

1100 0101 para a direita em um registrador de 32 bits

depois de rodar 8 vezes temos

1100 0101 0000 … e depois de 10 vezes temos:

0011 0001 0100 …

o que fornece um o resultado da apostila:

31 40 00 00 0011 0001 0100 0000 0000 0000 0000 0000

2.5 Faca os exercicios 3.10.1 a 3.10.4 da pagina 3-11, 3-12. Dicas:

2.5.1 Desvio condicional

Para fazer os exericios, voce vai precisar de desvio condicional. Veja a tabela na apostila em: 5.2 Execution conditions . Exemplo de uso:

CMP R0,R1
BGT label

onde GT estah na tabela. Assim, podemos ter varios desvios condicionais como: BEQ, BNE e ateh mesmo BAL, mas como AL eh "default", use simplesmente "B" para o desvio incondicional. Em ARM eh possivel combinar qualquer instrucao com uma condicao. Ex: MOVEQ - move caso EQ.

2.5.2 Exercicio 3.10.1 - Signed and unsigned addition

Use LDR para carregar valores de 32 bits em registradores. Ao inves de fazer:

mov r1,#0x12345678

faça:

ldr r1,=0x12345678
  1. Cuidado com as flags

    ADD nao atualiza as flags do CPSR; ADDS atualiza as flags do CPSR. Igualmente SUB x SUBS, MOV x MOVS, etc.

  2. Carry x overflow

    Se representarmos numeros em 4 bits em complemento de 2, podemos representar desde o -8 ateh o 7. Ao somarmos -1 e 1 temos carry mas nao temos overflow. Somando 5+4, temos que 9>7 e portanto temos o overflow.

  3. signed x unsigned

    Na apostila: Does their meaning change when the data values are unsigned numbers? Numa representacao de 4 bits unsigned, os numeros variam de 0 a 15. Assim, ao somarmos 7+2, obtemos 9 que pode ser representado como unsigned e nao pode ser representado em 4 bits unsigned. Assim, OVERFLOW para unsigned deve ser visto olhando a flag CARRY e a OVERFLOW em si, deixa de ter sentido.

2.5.3 Exercicio 3.10.2 - Multiplication / Multiplicacao de numeros

  • no resultado tivemos a flag N setada, embora multiplicamos dois numeros negativos (e o resultado deveria ser positivo). A instrucao MULS (tem 1 L somente, nao confunda com MULLS), multiplica 2 numeros de 32 bits e coloca o resultado em um numero de 32 bits. Isso nao funciona bem. Vamos pensar em numeros de 4 bits variando de -8 a 7 em complemento de 2. Se multiplicarmos -1 e -8 em complemento de 2, temos +8. Porem, 8 nao pode ser representado em complemento de 2, 4 bits. A apostila quer mostrar que as flags foram atualizadas erradas nessa instrucao. Na verdade, nem as flags e nem o conteudo dos registradores eh confiavel dado que nao se consegue sempre multiplicar 2 numeros de 32 bits e colocar o resultado em 32 bits. O certo eh colocar o resultado em um numero de 64 bits. Para entender melhor veja no site da ARM, a especificacao da instrucao MUL: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0068b/CIHIHGGJ.html . Se quisermos observar o bottom (32 bits menos significativos) em MULS contendo o numero todo, temos que multiplicar numeros que caibam em representacoes de 16 bits.
  1. Why is there a need for two separate long multiply instructions, UMULL and SMULL?

    Ambas as instrucoes multiplicam 2 numeros de 32 bits e colocam o resultado de 64 bits em 2 registradores: um mais signficativo e outro menos significativo. O resultado eh diferente se o numero eh signed ou unsigned. Por exemplo: pensando em multiplicar 2 numeros de 4 bits, temos no caso de 1111 = 15(unsgined) ou (-1) signed. 15*15 eh um resultado totalmente diferente de (-1) * (-1). Daih a necessidade de UMULL e SMULL. Observe que se o resultado eh colocado em 2 registradores, como R0(mais signficativo) e R1; entao apenas o bit mais significativo de R0 eh quem diz se <R0,R1> eh positivo ou negativo.

  2. Pequeno erro:

    Obs: Na apostila ARM Lab Manual temos na pg 3-5 UMULL r6, r8, r0, r1 ; {r6,r8} = r0 × r1 onde aparentemente r6 eh o mais significativo. Pelo site da ARM temos que o r6 (primeiro argumento) eh o menos significativo.

2.5.4 Exercicio 3.10.3 - Multiplication shortcuts / Multiplicacao pelo numero 32.

2.5.5 Exercicio 3.10.4 Register-swap algorithm

sem problemas, exercicio simples de ser feito.

2.6 tarefa E2 - printscreen (INDIVIDUAL)

Retorne o printscreen da tela de SEU computador (arquivo .png) referente ao exericio 3.10.4, seguindo:


Write the ARM code to implement the above algorithm, and test it with the values of A = 0xF631024C and B = 0x17539ABD . Show your instructor the contents before and after the program has run.


Nao eh necessario anexar a tela do conteudo antes de rodar o programa, apenas depois. A tela deve conter:

  • seu codigo e
  • os registradores depois de ter rodado o codigo.

Observacao: vai ter mais pontos quem entregar o printscreen no horario da aula

2.7 tarefa E2 - relatorio e codigos fontes

Sem zipar nada, entreguem os seguintes arquivos:

2.7.1 códigos fontes de todos os itens (diversos arquivos .s)

Coloquem no cabeçalho de cada arquivo: objetivo do fonte - mostrando a qual item o arquivo (.s) se refere e a forma como o código eh gerado e debugado. Coloque comentarios onde devido.

2.7.2 o relatorio feito em grupo (arquivo .pdf).

Nao me entreguem em .docx porque pode dar problemas ao abrir. Coloquem no relatorio a copia do codigo fonte e as saidas ao rodarem os itens vistos. Isso pode ser em printscreen ou mesmo em texto (fonte Courier para ficar bonito) atraves de copy & paste.

2.8 Desempenho da classe:

algumas equipes nao conseguiram terminar todos os exercicios.

3 <2021-05-20 qui> . E3: Data Processing Operations (cap3).

3.1 PLANEJAMENTO (3 pontos - apresentacao INDIVIDUAL em pdf como retorno de atividade, mas discutam com sua dupla antes da aula.):

3.1.1 A - leiam o capitulo 3 da apostila.

3.1.2 B - Respondam as seguintes perguntas

  1. 1. O que há de errado nas seguintes instrucoes:
    a. ADD r3,r7, #1023
    b. SUB r11, r12, r3, LSL #32
    

    Sugestao:

    • vejam os codigos de maquinas e observem que certos numeros nao servem para fazer o codigo de maquina.
    • coloquem no gnuarm e vejam o erro.
  2. 2. Sem usar a instrucao MUL, de as seguintes instrucoes para multiplicar o registrador R4 por:
    a. 132
    b. 255
    c. 18
    d. 16384
    

    Dicas (apenas para pensar em como resolver os itens a,b,d,e acima):

    • como vc. faria para multiplicar um valor por 4? Dica: use o MOV com o deslocamento. r1 = 4*r0
    • como vc. faria para multiplicar um valor por 5? Dica: use ADD com deslocamento - r1 = r0+4*r0
    • como vc. faria para multiplicar um valor por 3? r1 = r0*4 - r0; veja a diferenca entre SUB e RSB
    • como vc. faria para multiplicar um numero por 15? Multiplica por 3 e depois por 5.
  3. 3. Escreve uma rotina que compara 2 valores de 64-bits usando somente 2 instrucoes. (dica: a segunda instrucao é condicionalmente executada, baseada no resultado da primeira comparacao).

    No cap. 5 da apostila existe uma tabela com todas as condicoes possiveis. Exemplo de instrucao condicional: ADDEQ R0,R1,R2 = execute a soma se a flag Z no CPSR == 1. Alguns alunos reclamaram ser necessario consultar o cap. 5, mas nao dah para fazer o cap. 3 sem saber instrucoes condicionais (sorry!).

  4. 4. Escreva uma rotina que desloque um valor de 64-bits (armazenado em 2 registradores r0 e r1) de um bit para a direita.

    o arm-elf-gcc nao estah compilando uma instrucao que lide com RRX como mov … RRX #1 porem compila outras como mov … ROR #1 porem, o arm-elf-gcc compila: mov … RRX ou seja, ao usar RRX jah se supoe que desloca de 1 bit para direita rodando com o carry.

  5. 5. idem 4, para a esquerda.

3.1.3 C - prepare a solucao de 3.10.7 (individualmente no seu computador)

  • Rascunhe a solucado do exercicio de divisao 3.10.7; ou seja, como é o algoritmo da divisao. Coloque o algoritmo em codigo ARM (nao eh necessario testar). A operacao de divisao deve ser feita com shift como faz a profa. do primário e nao o algoritmo ineficiente e simples que retira um numero do outro.

Veja: http://courses.cs.vt.edu/~cs1104/Division/ShiftSubtract/Shift.Subtract.html e coloque no papel a simulacao de 1101 dividido por 10. Existe algum erro nesse algoritmo de divisao? Teste com diversos casos no proprio site antes da aula (porque existe sim um erro).

3.1.4 retorne a tarefa no google classroom

Enunciado no google classroom: Veja em http://www2.pcs.usp.br/~jkinoshi/2021/labmicro-21.html o que deve ser feito como planejamento da E3 e anexe o pdf como retorno de atividade. Discutam com sua dupla antes da aula.

3.2 OBJETIVO:

  • terminar capítulo 3 da apostila. Fazer os exercicios de 3.10.5 ateh 3.10.8

3.3 DICAS e Observacoes:

3.3.1 Desvio condicional

Para fazer os exericios, voce vai precisar de desvio condicional. Veja a tabela na apostila em: 5.2 Execution conditions . Exemplo de uso:

CMP R0,R1
BGT label

onde GT estah na tabela. Assim, podemos ter varios desvios condicionais como: BEQ, BNE e ateh mesmo BAL, mas como AL eh "default", use simplesmente "B" para o desvio incondicional. Em ARM eh possivel combinar qualquer instrucao com uma condicao. Ex: MOVEQ - move caso EQ.

3.3.2 Exercicio 3.10.5 - Signed multiplication

nao percam tempo com a restricao da apostila: usem instrucoes condicionais para facilitar. No cap. 5 da apostila existe uma tabela com todas as condicoes possiveis. De preferencia, facam primeiro o exercicio 3.10.6 . Pequeno erro do UMULL na apostila explicado no item 3.10.2

3.3.3 Exercicio 3.10.6 - Absolute value

Uma dica para se ter o absoluto de um numero eh fazer (zero - numero) caso o numero seja negativo.

3.3.4 Exercicio 3.10.7 Division

Teste que deve funcionar:

  • 1234567 por 1234

Teste que talvez falhe:

  • 123456789 por 1234

Teste com

  • 100 / 10

Carregue o numero no registrador com algo como:

LRD R0, =123456789

Nao precisa de se preocupar caso esteja falhando para codigos onde o bit de sinal do dividendo seja 1.

3.3.5 Exercicio 3.10.8 Gray codes

  • na apostila ARM Lab Manual: A sequencia b010 011 001 000 101 111 110 100 nao eh um codigo de gray de 3 bits (ex: erro ao passar de 000 para 101 alterando dois bits). O codigo pode ser

000 001 011 010 110 111 101 100. Se tiver duvidas em como se forma o codigo gray, consulte o wikipedia.

3.4 Retorne as seguinte atividades no google classroom:

3.4.1 1) Entrega de screenshot (INDIVIDUAL)

Formato: png (ou jpg, imagem). Nao me anexe um zip com varias imagens! Entregue ateh o final da aula o screenshot do exercicio 3.10.7 - sobre a Divisao:


Write ARM assembly to perform the function of division. Registers r1 and r2 contain the dividend and divisor, r3 contains the quotient, and r5 contains the remainder. For this operation, you can either use a single shift-subtract algorithm or another more complicated one.


A divisao deve ser feita entre o seu numero USP e 1000 mostrando os registradores R1 (dividendo: seu numero USP), R2 (divisor: 1000), R3 (quociente), R5 (resto). Assim, retorne o screenshot da tela do gdb com os registradores, logo apos a divisao ter sido feita. O gdb deve estar mostrando duas telas: a tela contendo os registradores e a tela contendo o codigo. Retorne a foto (geralmente um arquivo .png) do screenshot.

3.4.2 2) o relatorio em PDF feito em grupo + código fonte de todos os itens da experiencia (excluindo os do planejamento). .

Nao me entreguem em .docx porque pode dar problemas ao abrir. Coloquem no relatorio as saidas ao rodarem os itens vistos. Isso pode ser em printscreen ou mesmo em texto (atraves de copy & paste, fonte Courier para ficar bonito) da saida do gdb .

Não se esqueçam de colocar no cabeçalho a forma como o código eh gerado e debugado. NAO COLOQUE OS ARQUIVOS DE CODIGO ZIPADOS. (1 ponto a menos se o fizer).

3.5 Desempenho da classe:

metade das equipes nao conseguiu implementar o 3.10.8 (mas vale a pena implementar?). O 3.10.8 poderia ser visto como um exercicio extra ou ainda eliminado.

4 <2021-05-27 qui> . E4: Loads and Stores (cap4)

4.1 PLANEJAMENTO: (3 pontos) apresentacao individual

Responda os itens abaixo e retorne a foto do que voce fez ateh no dia anterior aa aula.

4.1.1 A Leia o capitulo 4.

4.1.2 B Respondam as questoes para apresentar ao professor no comeco da aula:

B.1) Descreva o conteúdo do registrador R13 ou sp depois que as seguintes instruções forem executadas, assumindo que a memória contenha os valores mostrados abaixo. O registrador R0 contém 0x24, e o sistema de memória é little-endian (o menos significativo é colocado no endereco mais baixo).

Endereço Conteúdo
0x24 0x06
0x25 0xFC
0x26 0x03
0x27 0xFF
LDRSB sp, [r0]
LDRSH sp, [r0]
LDR sp,[r0]
LDRB sp,[r0]

B.2) Indique se as seguintes instruções usam o modo pré ou pós indexado de endereçamento:

STR r6, [r4,#4]
LDR r3, [r12], #6
LDRB r4, [r3,r2]!
LDRSH r12, [r6]

B.3) Calcule o endereço efetivo das seguintes instruções se o registrador r3 = 0x4000 e o registrador r4 = 0x20

STRB r9, [r3,r4]
LDRB r8,[r3,r4,LSL #3]
LDR r7, [r3], r4
STRB r6, [r3], r4, ASR #2

B.4) O que há de errado na seguinte instrução? Veja "incorrect example" em: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0068b/Chdbifed.html

LDRSB r1,[r6],r3,LSL #4

4.1.3 C - escreva codigo

Escreva o código em Assembly que faça:

for (i=0; i<8; i++) {
  a[i] = b[7-i];
}

instalar o gnuarm na maquina de voces e testar. Procurem usar as seguintes instrucoes em seu código:

  • LDR ou ADR (isto é: declarem os dados na memória e leiam de lá; por exemplo, onde comeca o a array b e a array a).
  • BGE (usem instrucoes que facam o desvio condicional, nao necessariamente BGE).
  • RSB (para o 7-i)
  • STR (isto é: armazene de fato o dado na memória).

Vejam: https://stackoverflow.com/questions/42503417/arm-assembly-arrays/53391341#53391 ou informacoes aqui na aula de hoje sobre como alocar uma array na memoria.

4.2 OBJETIVO:

Fazer os exercicios do capitulo 4.

4.3 Observacoes

4.3.1 Na apostila tem um erro no item:

4.3.1 Direct loading with MOV and MVN

MOV r0, #0x1, 30 ; r0 = 1020         32 - 30 = 2; 2 ** 2 = 4 portanto pula de 4 em 4.
MOV r0, #0xFF, 28 ; r0 = 4080        32 - 28 = 4; 2 ** 4 = 16 portanto pula de 16 em 16.
MOV r0, #0x1, 26	 ; r0 = 4096   32 - 26 = 6; 2 ** 6 = 64 portanto pula de 64 em 64.

mas na realidade eh:

│0x8218 <main>           mov    r0, #4           1 *4 = 4                                 │
│0x821c <main+4>         mov    r0, #4080        255 * 16 = 4080                          │
│0x8220 <main+8>         mov    r0, #64 ; 0x40   1 * 64 = 64.           

4.3.2 Exercicio 4.5.1 Assignments with operands in memory

Assignments with operands in memory Assume an array of 25 words. A compiler associates variables x and y with registers r0 and r1, respectively. Assume that the base address for the array is located in r2. Translate this C statement/assignment using the post-indexed form:

x = array[5] + y

Now try writing it using the pre-indexed form. Apenas crie um programa em assembly que use o LDR de forma pre indexada e pos indexada. Force para que os valores nos registradores caiam na posicao array + 5*4 (array de 4 bytes para cada elemento) em ambos os caso.

Uma forma simples de se declarar dados, por exemplo, uma array, estah em http://www.coranac.com/tonc/text/asm.htm :

    mov     r2, #1
@ Byte loads
    adr     r0, bytes
    ldrb    r3, bytes       @ r3= bytes[0];     // r3= 0x000000FF= 255
    ldrsb   r3, bytes       @ r3= (s8)bytes[0]; // r3= 0xFFFFFFFF= -1
    ldrb    r3, [r0], r2    @ r3= *r0_b++;      // r3= 255, r0++;
@ Halfword loads
    adr     r0, hwords
    ldrh    r3, hwords+2    @ r3= words[1];     // r3= 0x0000FFFF= 65535
    ldrsh   r3, [r0, #2]    @ r3= (s16)r0_h[1]; // r3= 0xFFFFFFFF= -1
    ldrh    r3, [r0, r2, lsl #1]    @ r3= r0_h[1]? No! Illegal instruction :(

@ Byte array: u8 bytes[3]= { 0xFF, 1, 2 };
bytes:
    .byte   0xFF, 1, 2
@ Halfword array u16 hwords[3]= { 0xF001, 0xFFFF, 0xF112 };
    .align  1    @ align to even bytes REQUIRED!!!
hwords:
    .hword  0xF110, 0xFFFF, 0xF112

Para observar os dados na memória dentro do gdb, voce pode fazer

x/20 0x100

para ver 20 words a partir de 0x100

x/21h hwords // hwords eh o label no codigo acima

para ver 21 half words a partir do label hwords.

x/20db array // hwords eh o label no codigo acima

para ver 20 bytes em formato decimal a partir do label array

Terminando este item pule para o 4.5.2, lembrando que mais referencias estao em: http://www.coranac.com/tonc/text/asm.htm : pagina mostrando diversos codigos e dados para o gnu assembler

http://www.microcross.com/gnu-arm7t-microcross.pdf http://bel.gsi.de/scripts/gnu-arm-assy-quick-ref.pdf

e os manuais do GNU estão (por exemplo) em: https://sourcery.mentor.com/sgpp/lite/arm/portal/release830

4.3.3 Exercicio 4.5.2 Loads and stores

Translate this C statement/assignment using the post-indexed form:

array[10] = array[5] + y

Now try it using the pre-indexed form.

Pergunta: qual o significado de se ter pre-indexado ou pos-indexado nesse caso? Resposta: o objetivo é apenas didático. Não tem significado. O uso do pre-indexado ou pos-indexado faz sentido dentro de um loop.

4.3.4 Exercicio 4.5.4 Array assignment

Declare os elementos na memoria usando .byte e use o label ao inves de posicoes fixas como 0x4000, 0x4001, etc.

Suponha que ao inves de b) initPointers (int *a, esteja escrito b) initPointers (int *array,

4.3.5 Exercicio 4.5.5 Arrays and pointers, 4.5.6 The nth Fibonacci number - sao muito parecidos.

A diferenca eh que 4.5.6 nao pede para calcular a sequencia na memoria, podendo simplesmente usar registradores para isso. Nao existe de fato muita diferenca. Alguns alunos questionaram se 4.5.6 deveria calcula f(n) para qualquer n inteiro. O primeiro problema eh que o resultado deveria caber na memória do computador e portanto n sempre terah que ser limitado: nao eh isso que vamos fazer. Assuma n limitado para resultados cabendo em byte (4.5.5) ou word (4.5.6).

4.4 Retorne as tarefas

4.4.1 planejamento da exp (jah entregue no dia anterior, em pdf, com os codigos do planejamento inclusos)

O planejamento jah foi visto no comeco da aula. Se por ventura, voce nao apresentou o planejamento ao professor (porque faltou na aula, o prof. chamou varias vezes e vc. nao respondeu, etc.), sua nota jah estah zerada. Assim, nao precisa entregar o planejamento.

4.4.2 Entrega do printscreen (INDIVIDUAL)

Formato: png (ou jpg, imagem). Nao me anexe um zip com varias imagens! Entregue ateh o final da aula o screenshot do exercicio:


4.5.5 The Fibonacci sequence


com a serie de Fibonacci na memoria com o numero de termos correspondente ao ultimo digito de seu numero USP. NAO COLOQUEM OS DADOS NA POSICAO 0x4000 COMO NA APOSTILA. Tenha o cuidado de declarar os dados na memória como explicado no comentario do item: "Exercicio 4.5.1 Assignments with operands in memory".

4.4.3 codigos fontes + o relatorio em PDF feito em grupo.

  1. código fonte de todos os itens da experiencia (excluindo os do planejamento).

    Não se esqueçam de colocar no cabeçalho a forma como o código eh gerado e debugado. NAO COLOQUE OS ARQUIVOS DE CODIGO ZIPADOS. (1 ponto a menos se o fizer).

  2. relatorio

    Entreguem em pdf, nao me entreguem em .docx porque pode dar problemas ao abrir. Coloquem no relatorio as saidas ao rodarem os itens vistos. Isso pode ser em printscreen ou mesmo em texto (fonte Courier para ficar bonito) atraves de copy & paste.

5 <2021-06-10 qui> . E5: Conditional Executiong Loops (cap5).

5.1 PLANEJAMENTO (3 pontos)- individual

5.1.1 A. Leia o Capitulo 5

5.1.2 B. Responda:

  1. 1. Traduza as seguintes instrucoes em uma unica instrucao ARM:
    1. a. adicione resgistradores r3 e r6 somente se N = 0 (N estah "clear"). Armazene o resultado no registrador r7.
    2. b. adicione resgistradores r3 e r6 somente se N = 1. Armazene o resultado no registrador r7.
    3. c. Multiplique os registradores r7 e r12, colocando os resulados no registrador r3 somente se C estah setado (C = 1) e Z = 0 .
    4. d. Multiplique os registradores r7 e r12, colocando os resulados no registrador r3 somente se C clear ou Z set .
    5. e. Compare os registradores r6 e r8 somente se Z estah zerado.
    6. f. Compare os registradores r6 e r8 somente se Z set ou N ≠ V
      1. Observe a seguinte funcao em C:
      int foo(int x, int y) {
         if ((x + y) >= 0)
               return 0;
         else
               return 1;
      }
      

      Suponha que ela tenha sido compilada e traduzida no seguinte codigo:

      foo        ADDS r0,r0,r1
                   BPL PosOrZ
      done      MOV r0, #0
                   MOV pc, lr
      PosOrZ  MOV r0,#1
                   B done
      

      O compilador gerou o código corretamente? O compilador retorna 0 ou 1 em r0. Se não está bom o código, corrija. Altere o código para que ele execute a funcao em somente 4 instrucoes (dica: use execucao condicional).

5.1.3 C. IMPORTANTE: Implemente, teste, apresentando o printscreen, o codigo de 5.5.4.1

5.5.4 Finite state machines: a nonresetting sequence recognizer

  1. Exercicio 5.5.4 1. Consider an FSM with one input X and one output Z. The FSM asserts its output Z when it recognizes an input bit sequence of b1011. The machine keeps checking for the sequence and does not reset when it recognizes the sequence. Here is an example input string X and its output Z:
    X = ...0010110110...
    Z = ...0000010010...
    

    Write ARM assembly to implement the sequence recognizer. Start with the initial input X in r1. Finish with the output Z in r2 at the end of the program.

5.2 Objetivo

Fazer os exercicios do capitulo 5 da apostila em 5.5

5.3 Observacoes

5.3.1 Exercicio 5.5.2

Se vc. considerar que nao eh necessario usar MOVNE, delete essa instrucao do codigo sugerido. (mas talvez precise sim - um registrador nao pode ser origem e destino na multiplicacao).

5.3.2 Exercicio 5.5.3

  1. Find maximum value

    In this exercise, you are to find the largest integer in a series of 32-bit unsigned integers. The length of the series is determined by the value in register r5. The maximum value is stored in the memory location 0x5000 at the end of the routine. The data values begin at memory location 0x5006. Choose 11 or more integers to use. Use as much conditional execution as possible when writing the code. Demonstrate the program to your lab instructor and print out the memory space starting at 0x5000 before and after the program runs. Be sure to include enough memory space to show all of your 32-bit integer values.

    0x5006 não é múltiplo de 4 - as words devem estar alinhadas em múltiplos de 4. Apenas declare as words (valor maximo e sequencia de words) em seu codigo e deixe que o gnuarm escolha suas posicoes.

    1. Obs: A apostila foi escrita para o codewarrior e estamos usando o gnuarm e nesse ambiente é razoavelmente simples definir toda uma área de dados em uma certa posição de memória (no ldscript), mas não é simples definir que dados sejam alocados em um endereço específico - para isso podemos usar ponteiros para a posição fixa.

      Nesse laboratório, ao invés de usar 0x5000, defina uma área de dados de 100 bytes assim:

      dados: .space 100
      

      e no programa podemos fazer, por exemplo:

      LDR r0,=dados+4
      

      e dessa forma não dependemos da posição fixa 0x5000 Caso queira dados jah pre-inicializados faca:

      dados: .word 0x1, 0x2 ...
      

5.3.3 Exercicio 5.5.4

se quiser, declare um numero binario como: 0b10101 dentro do codigo.

5.4 Envie respondendo aas tarefas do google classroom.

5.4.1 Entrega dos arquivos fontes (feito em grupo, entrega individual) + relatorio.

  • Entregue no dia da aula os codigos fontes de cada exercicio anexando o arquivo um a um. NAO ZIPEM. No cabecalho de cada codigo, escreva como o arquivo deve ser gerado. Comente o codigo para voce mesmo entender na hora da prova. Caso tenha feito algum script para geracao do codigo (recomendado), anexe o script em seu codigo. Embora o codigo seja feito em grupo, a entrega eh INDIVDUAL.
  • Relatorio em Formato: pdf. Entregue o relatorio (pdf) cada exercicio por seu grupo, com o codigo e screenshot da tela do gdb. Embora o relatorio seja feito em grupo, a entrega eh INDIVDUAL.

5.4.2 Entrega de screenshot (INDIVIDUAL)

Apresente o printscreen de 5.5.4 item 2 com X (R1) = 0x5555AAAA Y (R8) = 5 (padrao em binario = 0b101) nbits do padrao (R9) = 3 Apresente em seu printscreen as posicoes onde o padrao foi encontrado Z (R2).

6 <2021-06-17 qui> . E6: Subroutines (cap6)

6.1 PLANEJAMENTO (individual)

6.1.1 A. Ler cap 6.

6.1.2 B. Responda

1 O que há de errado com as seguintes instruções:

a) STMIA r5!, {r5, r4, r9}
b) LDMDA r2, {}
    STMDB r15!, [r0-r3, r4, lr}

2 Se o registrador r6 possui 0x8000 (como ponteiro para a memória); após executar

LDMIA r6,{r7,r4,r0,lr}

o que fica em r0, r4, r7 e em lr?

3 Assuma que a memória e registradores estejam:

0x8010 0x1
0x800C 0xfeeddeaf
0x8008 0x00008888
0x8004 0x12340000
0x8000 0xbabe0000

r0=0x13; r10xffffffff; r2 = 0xeeeeeeee; r3 0x8000

Descreva a memória e conteúdos dos registradores após a instrução:

LDMIA r3!, {r0,r1,r2}

4 Suponha que a pilha esteja como o diagrama abaixo. Que instrução seria necessária para sair do estado original e ir para o estado a), depois b) e depois c)?

Endereço Original A B C
0x8010 0x1 0x1 0x1 0x1
0x800C 0xfeeddeaf 0xfeeddeaf 0xfeeddeaf 0xfeeddeaf
0x8008   0xbabe2222 0xbabe22222  
0x8004     0x12340000  
0x8000        

6.1.3 C. IMPORTANTE

Apresente o codigo assembly rodando com printscreen de: 6.5.2 Bubble sorting mostrando os dados em ordem crescente atraves de um comando como: x/10d dados

6.2 OBJETIVO

Exercicios 6.5 do 6.5.1 ateh 6.5.4.

6.3 Observacoes

6.3.1 Ex 6.5.1

transmit the arguments by way of the stack with two subroutines, func1 and func2, that demonstrate stack functionality. É importante que o endereco de retorno seja colocado na pilha em func1. Declare os elementos na memoria usando .word (veja acima) e use o label ao inves de posicoes fixas como 0x4000, 0x4001, etc.

6.3.2 Ex 6.5.2

Bubble Sort 6.5.2.1. Usem load e store multiplo envolvendo 2 posicoes consecutivas de memoria quando fizerem a "bolha" andar. Embora no exercicio da apostila esteja como byte, usar word porque LDM… usa word.

  1. Ex 6.5.2.2. Modify your code to utilize a full descending stack. Sorting must be done on the

    stack only. Once the stack is sorted, store the sorted stack back to the original array of memory locations starting at 0x4001.

    The algorithm for the bubble sort is as follows: a. Compare adjacent elements. If the first element is greater than the second, swap them.

    b. Do this for each pair of adjacent elements, starting with the first two and ending with the last two. At this point the last element should be the greatest. c. Repeat the steps for all elements except the last one. d. Repeat this process for one fewer element each time, until you have no more pairs to compare.

    Dado que eh muito confuso, nao estarei cobrando esse uso de pilha. Podem pular esse item 6.5.2.-2. Entretanto, seguem observacoes colhidas ao longo do curso. Porem, pulem esse item:

    Está confuso como utilizar o full descending stack - uma forma de organizar usando pilha somente é torre de Hannoy mas usando o bubble sort estamos mexendo em elmentos de uma array. Uma idéia é: trabalha com duas estruturas - uma array onde se descobre o maior e a pilha que vai armazenando o maior elemento em cada iteracao. Observar que a array eh de bytes enquanto que a pilha eh de words (o mais simples eh desperdicar memoria ao usar os bytes como words). Outra idéia (grupo Joao) - usar duas pilhas. A pilha eh varrida a cada comparacao jogando o maior valor para a segunda pilha. Na primeira pilha sobre o menor valor. A segunda pilha com N-1 elementos é totalmente trasportada para a primeira pilha. O processo se repete para os N-1 elementos da primeira pilha.

6.3.3 Ex 6.5.3

Quadrado Magico Nao é necessário preocupar-se em colocar o quadrado mágico em 0x4000. É mais fácil declarar na memória ao final do programa algo como .word 1,4, … colocando as words do quadrado mágico.

6.3.4 Ex 6.5.4

More stacks Write ARM assembly to implement a push operation without the use of load/store multiple instructions. Write the code to handle bytes, half-words, and words. Use r0 to indicate the data type. A value of 1 in r0 indicates that a byte is to be pushed, 2 indicates a half-word, and 4 indicates a word. Put the data to push in r1.

Lembrar que sp eh sempre um multiplo de 4. Tem que tomar um certo cuidado ao empilhar byte ou half word para que o sp permaneca multiplo de 4; ou seja, dependendo do caso a memoria eh desperdicada.

O mais facil eh sempre alocar 4 bytes mesmo que seja para um byte apenas, desperdicando memoria. Se quiser fazer dessa forma bem facil, faca.

O mais dificil eh ir alocando memoria dependendo do tipo de dado, economizando memoria. Para byte, empilha no topo da pilha sempre; para half word, empilha no endereco multiplo de 2 mais proximo do topo; para word, empilha no endereco multiplo de 4 mais proximo do topo da pilha. Nao eh necessario ir para essa solucao mais dificil pois ainda teriamos o problema de como desempilhar sem dar problemas.

Dica para olhar a pilha no gdb:

x/20 $sp

6.4 Envie respondendo aas tarefas do google classroom.

6.4.1 Entrega dos arquivos fontes (feito em grupo, entrega individual) + relatorio.

  • Entregue no dia da aula os codigos fontes de cada exercicio anexando o arquivo um a um. NAO ZIPEM. No cabecalho de cada codigo, escreva como o arquivo deve ser gerado. Comente o codigo para voce mesmo entender na hora da prova. Caso tenha feito algum script para geracao do codigo (recomendado), anexe o script em seu codigo. Embora o codigo seja feito em grupo, a entrega eh INDIVDUAL.
  • Relatorio em Formato: pdf. Entregue o relatorio (pdf) cada exercicio por seu grupo, com o codigo e screenshot da tela do gdb. Embora o relatorio seja feito em grupo, a entrega eh INDIVDUAL.

6.4.2 Entrega da saida do gdb (INDIVIDUAL)

O gdb pode gerar um arquivo contendo a saida sem que seja necessario rodar interativamente o gdb. Por exemplo:

  • Ao rodar: arm-elf-gdb –command=comandos.txt a.out :
# arquivo: comandos.txt
# gera: gdb.txt - saida do gdb 
# arm-elf-gdb  --command=comandos.txt a.out
target sim
load
b main
r
b fim # breakpoint no SWI
c
set logging on
x/16d quadrado # quadrado 4x4
x/d ehmagico # 1- magico 0 - nao eh magico
set logging off

temos a saida gravada em gdb.txt que vc. deve anexar como retorno dessa atividade.

Apresente o gdb.txt de 6.5.3 Magic squares. Ao inves de usar a posicao 0x4000, coloque o quadrado magico jah definido no seu codigo em na label "quadrado"(como jah vimos em aulas passadas). A experiencia eh sobre rotinas assim, CRIE SUBROTINAS para verificar a soma na horizontal, vertical e diagonal. Entregue:

  1. gdb.txt 1 - Teste com o quadrado magico da apostila 4x4 e apresente o printscreen.
  2. gdb.txt 2 - procure na internet um quadrado magico 3x3, teste e apresente o printscreen. Observe que ao inves de x/16d quadrado, vc deve fazer x/9d quadrado
  3. gdb.txt 3 - Preencha esse quadrado magico 3x3 com apenas o numero 5 (ou seja, quadrado .word 5,5,5,5,5…). O que voce obteve?

Se obteve como resposta quadrado magico estah errado pela definicao de quadrado magico. Assim, pense no algoritmo mais eficiente possivel para verificar se temos os numeros de 1 a N**2 e o implemente. Dica: esse algoritmo eh da ordem de NE (numero de elementos em quadrado) e nao de NE**2. Apresente o printscreen3 testando com o quadrado 3x3 contendo apenas 5's (mostrando que ele nao eh quadrado magico).

7 <2021-06-24 qui> . P1 - Subroutines (cap6)

prova a ser enviada por email. nao entrem no google meet.

8 <2021-07-01 qui> . E8: C compiler + assembler - juntar C com assembly.

8.1 PLANEJAMENTO (individual): file:///home/jk/docker-labmicro/gcc-arm/src/imprime.c

Crie um programa em C (imprime.c) que faz uma contagem de 5 a 0 usando uma funcao recursiva do tipo:

void imprime(N) {
  if (N<0) {
    return;
  }
  printf("numero = %d\n", N);
  imprime(N-1);
}


int main(void ) {
     imprime(5);
}

Crie de fato o programa em C e compile em casa antes da aula. Faca o compilador gerar o código assembly imprime.s através de

arm-elf-gcc –S imprime.c

Referencias que podem ajudar:

http://www.cl.cam.ac.uk/~fms27/teaching/2001-02/arm-project/02-sort/apcs.txt

http://stackoverflow.com/questions/15752188/arm-link-register-and-frame-pointer

Estude o código e simule a pilha e responda no planejamento:

  • Como a funcao imprime estah usando a pilha para fazer a chamada recursiva? Apresente a explicacao de como a pilha estah sendo usada (simule desenhando a pilha usando o fp, ip, sp) para fazer a chamada recursiva.
  • como o valor N eh passado como parametro para imprime() em main? via registrador ou pilha?
  • para que serve o frame pointer?
  • como o valor N eh referenciado dentro de imprime.s (vindo do compilador)? Relacione onde estah N com o fp.
  • observe que voltamos de uma instancia imprime para a instancia anterior atraves da instrucao ldmfd. Alguns alunos observaram que, durante o debug no gdb, o step (s) parece nao ter executado de forma correta e aparentemente, ele retorna de todas as instancias chamadas de uma vez. Se vc. quiser observar apenas um retorno de cada vez, noa use "s" e sim "si" ("step instruction" que executa instrucao a instrucao) como observeu o Artur/coop21.

8.2 Objetivos da aula de hoje:

  1. compilar codigo assembly e codigo C e linkar ambos.
  2. alterar o codigo C inserindo assembly no meio do código C.
  3. observar como funciona a recursao e gerar um relatorio usando o libreoffice com os printscreens da tela do arm-elf-gdb.

RELATORIO: Crie um documento (ex: usando o libreoffice) anexando os printscreens com uma explicacao do que ocorre ao seguirem os itens abaixo. Como diversas instrucoes alteram a pilha, vc. pode observar a pilha fazendo: x/16 $sp

8.2.1 compilar codigo assembly e codigo C e linkar ambos.

Adaptei de http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.100748_0606_00_en/lmi1470147220260.html o codigo seguinte:

arquivo: file:///home/jk/docker-labmicro/gcc-arm/src/add.s

	.text
	.globl   myadd
			
myadd:                     	@ Function "myadd" entry point.
	add      r0, r0, r1   	@ Function arguments are in R0 and R1. Add together and put the result in R0.
	mov	pc, lr           @  Return by branching to the address in the link register.

arquivo: file:///home/jk/docker-labmicro/gcc-arm/src/main.c

#include <stdio.h>
					
extern int myadd(int a, int b);
					
int main()
{
	int a = 4;
	int b = 5;
	printf("Adding %d and %d results in %d\n", a, b, myadd(a, b));
	return (0);
}

Observe que o codigo em C chama myadd que estah em assembly. No assembly, a funcao myadd recebe os parametros em R0 e R1 e retorna o resultado em R0. Existe uma convencao em como o C passa os parametros para uma funcao em assembly e como recebe o retorno da funcao. Veja "Chapter 2. Using the Procedure Call Standard" em http://infocenter.arm.com/help/topic/com.arm.doc.dui0056d/DUI0056.pdf

Para compilar:

student:~/src$ gcc add.s main.c

que gera o codigo a.out

Fazendo gdb a.out :

Loading section .jcr, size 0x4 vma 0x1097c
Start address 0x8110
Transfer rate: 139776 bits/sec.
(gdb) b main
Breakpoint 1 at 0x8230: file main.c, line 7.
(gdb) r
Starting program: /home/student/src/a.out 

Breakpoint 1, main () at main.c:7
(gdb) n
Adding 4 and 5 results in 9
(gdb) 

Repita o que foi feito, criando o seu primeiro codigo que mistura C e assembly.

  1. int2str

    Crie a funcao em assembly int2str(inteiro, pontstr) que transforma um inteiro em uma string. Por exemplo: o inteiro 1 deve ser transformado na sequencia de bytes 0x31 , 0x0 ; pois o zero representa o final da string. A string deve ser colocado no ponteiro apontado por pontstr.

    Use a rotina de divisao da E3 para dividir o numero por 10 e ir montando a string. Exemplo, supondo que inteiro =123 esse valor eh dividido por 10 com resto 3 ; soma 3 com 0x30 (para formar o caracter ASCII '3'). coloca o 0x33 em pontstr.

    depois dividido por 10 com resto 2 gera 0x32 ; move todos os bytes para direito dentro de pontstr e insere 0 0x33 em pontstr.

    dividido por 10 com resto 1. repete formando a sequencia de bytes 0x31, 0x32, 0x33, 0 que sao colocadas partir de pontstr.

    Crie uma funcao em C (main) impnum(int num): entrada: num - numero imprime o numero que recebeu em decimal. Para converter o numero deve usar a rotina assembly int2str. Dica: use "puts".

    Teste a sua funcao.

    PRINTSCREEN: tire um printscreen de sua tela logo depois que o numero eh impresso pela funcao em C. Teste com seu numero usp.

  2. adapte em imprime.c

    Altere a funcao imprime em imprime.c usado no planejamento trocando o printf por chamadas de puts e int2str

8.2.2 alterar o codigo C inserindo assembly no meio do código C.

  1. Estude o codigo imprime.s vindo de imprime.C

    No seu relatorio responda:


    Existe algum formato para os labels gerados automaticamente em imprime.s?


    Observe que as labels geradas automaticamente seguem um padrao como .L(\d+) ou seja ".L" seguido de digitos. Eh importante observar isso porque ao inserir qualquer codigo em assembly, voce nao poderah inserir labels desse tipo pois voce nao tem o controle sobre como o compilador funciona.


    Como o numero eh passado como parametro? Como o fp é usado para isso?


    Em uma funcao, os registradores poderiam ser salvos e recuperados chamados assim:

    BL myfunction
    myfunction
               .....
               STMFD sp!, {r4-r10, lr}; guarda os registradores
               .....
               .....
               LDMFD sp!, {r4-r10, pc}; recupera os registradores; o retorno da funcao eh feito colocando lr em pc.
    

    Para entender melhor como funciona a chamada de rotinas, veja: http://infocenter.arm.com%2fhelp%2ftopic%2fcom.arm.doc.ihi0042d%2fihi0042d_aapcs.pdf Responda no relatorio:


    Quais sao os registradores atribuidos a: fp, ip, sp, lr?

    Para que serve o fp?


    A seguinte pergunta nao precisa ir para o seu relatorio. Se vc. tiver uma boa ideia sobre a resposta (porque trabalha com compiladores) discuta a resposta comigo ou coloque no mural da disciplina.


    Para que serve o ip?


    A resposta na internet eh confusa:

    Register r12 (IP) may be used by a linker as a scratch register between a routine and any subroutine it calls (for details, see §5.3.1.1, Use of IP by the linker). It can also be used within a routine to hold intermediate values between subroutine calls. Essa resposta eh extremamente confusa, mas eh o que estah escrito em aapcs.pdf. Em: https://stackoverflow.com/questions/16120123/arm-why-do-i-need-to-push-pop-two-registers-at-function-calls temos que a ARM quer o alinhamento de 8 bytes na memoria (para o ARM 64 bits) Isso explica o motivo de se ter um registrador scratch (r12, ip) colocado na pilha - porque do ponto de vista da recursao em si, nao faz sentido… Dentre os 2 registradores fp e ip, o mais confuso eh o ip, sem duvida e eh chamdado de Intra-Procedure-call scratch register. https://community.arm.com/developer/tools-software/oss-platforms/f/dev-platforms-forum/5436/i-want-to-know-meaning-of-r12-register

  2. Inserindo assembly no meio do codigo C

    Para inserir codigo assembly no meio do codigo C (imprime.c), pode-se usar "inline assembly code" como no exemplo a seguir (ver: http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.100748_0606_00_en/ddx1471430827125.html) :

    __asm__( 
     "ldr r3, [fp, #-16]\n\t
     "mov r0,r1\n\t"
    );
    

    onde cada linha corresponde a umsa intrucao assembly. Obviamente eh MUITO ARRISCADO mexer nos registradores dessa forma, pois o C tambem estah usando-os e qualquer alteracao pode causar erros no seu codigo. Apesar dessa ressalva, vamos alterar um ou outro registrador nesse item.

    Em C temos variaveis locais e variaveis globais. As variaveis locais sao definidas na pilha e para o C qualquer funcao pode ser recursiva. As variaveis globais sao definidas em posicoes fixas na memoria.

    Assim, vamos definir a variavel global "mostra" no seu codigo em C como:

    int mostra;
    

    e atribuir o valor 1 logo no comeco do seu codigo. Veja o assembly gerado pelo c, atraves da opcao -S e procure entender como o valor 1 foi atribuido aa variavel mostra. No relatorio coloque o codigo correspondente aa essa atribuicao e explique (dica: veja str).

    Observe que o codigo em assembly gera um monte de labels que comecam com ponto (ex: .L2). Muito provavelmente, o assembly gerado usa um destes labels para atribuir 1 aa variavel mostra. Quando voce usar o "inline assembly", voce NAO DEVE usar nenhum label como .L2 pois isso iria se misturar com os labels gerados pelo C. Assim, o desafio eh:

    Escreva em assembly, um codigo referente a "mostra = 1" usando inline assembly (ou seja, usando asm) . Verifique no gdb que o valor foi devidamente escrito. Dica: Use as instrucoes em assembly LDR e STR e lembre-se de que as vezes fazemos LDR …, = (na duvida veja a aula sobre LDR e STR), para isso voce precisarah no maximo de 3 instrucoes: 2 para acertar registradores e 1 para o STR. Voce tambem nao deve fazer algo como .word…. declarando um ponteiro, copiando o que foi feito pelo compilador C. Dentro do gdb, para saber o valor de mostra basta fazer:

    p mostra
    

    Se tiver duvida de que as coisas estao funcionando, experimente variar o valor a ser colocado em "mostra" e verifique se o "p mostra" varia de acordo.

    Para saber a posicao de memoria onde foi parar a variavel "mostra" eh necessario gerarmos a tabela de simbolos. Supondo que o codigo gerado seja a.out entao podemos fazer:

    student:~/src$ arm-elf-objdump -t a.out|grep mostra
    00010ab8 g     O .bss	00000004 mostra
    
    OU
    
    student:~/src$ arm-elf-nm a.out | grep mostra
    00010ab8 B mostra
    

    A posicao 0x10ab8 corresponde ao endereco da variavel "mostra". Verifique se essa posicao foi alterada ao rodar o codigo no gdb.

8.2.3 observar como funciona a recursao.

  1. Observe como o imprime do pre-lab imprime um caracter no codigo assembly (gerado ao compilar .c com -S).

    Utilizando o gdb observe como o fp, ip e sp são utilizados em:

    stmfd sp!, {fp, ip, lr, pc}
    

    e como sao desempilhados em:

    ldmfd sp, {r3, fp, sp, pc}
    

    O arm-elf-gcc estah primeiro passando os parametros por registradores e depois empilhando-os dentro da rotina, por isso os parametros sao acessado via [fp - Numero] pois foram empilhados depois da entrada da rotina que empilhou ip, sp, fp. Supondo que uma funcao tenha 5 parametros, a funcao terah que obrigatoriamente empilhar alguns antes da chamada da rotina e esses parametros vao ser acessados via [fp + Numero]. Verifique colocando 5 parametros na funcao recursiva. Observe como eles sao empilhados e acessados.

    Observe que entre o stmfd e o ldmfd, o sp é alterado; por isso lr eh repassado para o pc.

    O grupo do Lucas,Ricardo,Gabriel,Jonatas enviaram uma foto de como a pilha se comporta quando nas chamadas recursiva em: pilha.PNG

    Perguntas: Como o parametro é passado para imprime? Na resposta explique o caso da rotina ter 5 parametros (via registrador e pilha) e da rotina ter poucos parametros (via registrador).

    Como esse parametro é empilhado (isso é necessário em caso de chamadas recursivas)? Como é aberto um espaco na pilha para o parametro de imprime? Onde isso é feito no código?

    Por que se faz fp-16 para acessar o parametro?

    Como esse parametro é desempilhado? Observe que o ip eh repassado para sp e com isso, a alteracao na pilha para abrir o espaco para o parametro de "imprime" é automaticamente refeito.

    Para o seu relatorio, gere imagens semelhantes aas apresentadas pelo grupo do Lucas,Ricardo,Gabriel,Jonatas.

    1. Declare a variavel local "int lixo" na funcao imprime.

      Dentro da funcao recursiva faca, lixo++. Observe o codigo assembly gerado pelo compilador. Responda no relatorio: Como lixo foi referenciado? Como foi aberto espaco na pilha para o lixo? Retire printscreens comparando a pilha sem o uso do int lixo e com o uso de int lixo. Variaveis locais devem ser empilhadas pois caso, a funcao seja recursiva elas fazem parte da recursao.

  2. Crie imprime.s para que imprima 7 numeros de 1 a 7

    Gere o codigo imprime.s. Estude como o fp (frame pointer) eh usado para marcar uma posicao na pilha empilhando parametros e variaveis locais. Apresente no relatorio como a pilha e o fp estao sendo usados.

    Rode essa versao de imprime no gdb. Retire printscreen antes e depois de cada instrucao que faz alteracao na pilha, em particular:

    stmfd sp!, {fp, ip, lr, pc}
    ldmfd sp, {r3, fp, sp, pc}
    

    Pergunta: Quando imprime chama recursivamente imprime é necessário que o haja um ponteiro para o fp anterior. Como isso é feito? Quando imprime retorna para uma instancia anterior é necessário ue o fp retorne para servir de base para os parametros e variaveis locais anteriores. Explique como isso é feito.

    Ao final da aula, enviar email ao professor contendo o documento gerado com os printscreens. Para ver a pilha no gdb faca x/16 $sp

8.3 Envie respondendo aas tarefas do google classroom.

8.3.1 Entrega dos arquivos fontes (feito em grupo, entrega individual) e Entrega de relatorio (feito em grupo, entrega individual)

Entregue no dia da aula os codigos fontes de cada exercicio um a um (ou seja entre arquivos .s e .c). No cabecalho de cada codigo, escreva como o arquivo deve ser gerado. Comente o codigo para voce mesmo entender na hora da prova. Caso tenha feito algum script para geracao do codigo (recomendado), anexe o script em seu codigo. Embora o codigo seja feito em grupo, a entrega eh INDIVDUAL.

Formato: pdf. Entregue ateh o final do dia aula o relatorio (pdf) cada exercicio por seu grupo, com o codigo e screenshot do tela do gdb. Embora o relatorio seja feito em grupo, a entrega eh INDIVDUAL.

8.3.2 Entrega de printscreen (INDIVIDUAL)

  • PRINTSCREEN1:

tire um printscreen de sua tela logo depois que o numero eh impresso pela funcao em C usando int2str. Teste com seu numero usp.

  • PRINTSCREEN2

mostre a variavel mostra sendo alterada pelo inline assembly.

9 <2021-07-08 qui> . E9: Hello World for bare metal

9.1 Planejamento (individual):

daqui para o final do curso, vamos utilizar a placa versatile emulada (similar ao evaluator7t mas emulada pelo qemu); comecando por uma placa sem absolutamente nada de software (bare metal), ou seja, sem o software que faz o boot.

Leia: http://balau82.wordpress.com/2010/02/14/simplest-bare-metal-program-for-arm/

Lá lemos: In order to create a bare metal program we must understand what does the processor do when it is switched on. The ARM9 architecture begins to execute code at a determined address, that could be 0 (usually allocated to RAM) or 0xFFFF0000 (usually allocated to Read Only Memory). We must put some special code at that particular address: the interrupt vector table.

Pesquise e responda como planejamento (em pdf):

  1. Como o ARM9 decide se a primeira instrucao a executar está em zero ou em 0xFFFF0000? A resposta estah no site do ARM.
  2. Compile os códigos em casa antes da aula e tente rodar usando o arm-elf-… (gcc, gdb, etc. - normal que estamos usando) ao inves de arm-none-… que estah no post simplest-bare-metal-program-for-arm. Voce conseguiu observar o código usando arm-elf-gdb em casa? Voce conseguiu executar centry?
  3. Pesquise na internet (manual do GNU) e responda, o que faz test.ld (o script para o comando ld, mais especificamente arm…ld) ?

O que significa em test.ld:

3.1) - ENTRY(_Reset)
3.2)- startup.o (INTERRUPT_VECTOR)
3.3) - stack_top = .;
3.4) - .bss : { *(.bss) }
3.5) - . = . + 0x1000; /* 4kB of stack memory */
#------------------ test.ld ----------------
ENTRY(_Reset)
SECTIONS
{
 . = 0x0;
 .text : {
 startup.o (INTERRUPT_VECTOR)
 *(.text)
 }
 .data : { *(.data) }
 .bss : { *(.bss) }
 . = . + 0x1000; /* 4kB of stack memory */
 stack_top = .;
}
#------------------ test.ld ----------------

9.2 Experiencia

Como a placa Versatile (similar ao evaluator7t do laboratorio) pode imprimir um "hello world" ao bootar? O processador ARM, ao ser ligado, passa a executar código a partir da posicao zero. Nessa posicao, vamos colocar o vetor de interrupcao. Para isso precisamos de gravar uma EPROM que contenha um jump na posicao zero para a rotina que imprime o hello world. Como não temos nenhum software, é necessário criar a rotina que faz a impressao da string via serial. Para essa experiencia, leia:

http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/ Nesse post, o Balau explica como fazer a rotina Centry que nao contem nada para ser executada como se uma placa com o processador ARM9 estivesse sendo bootada. Ele mostra como fazer isso no simulador. O vetor de interrupcao estah na posicao correta (que existe) 0x1000.

http://balau82.wordpress.com/2010/02/14/simplest-bare-metal-program-for-arm/ Nesse post, o Balau explica como se imprime "Hello World" de uma Versatile emulada pelo qemu; porém com o código rodando a partir de 0x0 e não a partir do zero como seria de se esperar do "bare metal" de fato.

http://www.embedded.com/design/mcus-processors-and-socs/4007119/Building-Bare-Metal-ARM-Systems-with-GNU-Part-1--Getting-Started

Um excelente artigo explicando como colocar software sobre o hardware usando GNU.

Realize os passos abaixo para a aula de hoje. Em paralelo, construa um relatorio para enviar no final da aula. Anexe código e printscreens do qemu, principalmente ao se constatar a mudanca de modo de processamento.

Para a aula faça:

9.2.1 Rode o Simplest Bare Metal Program

O grande objetivo da aula de hoje eh entender o vetor de interrupcao. Para a aula de hoje, ele deve obrigatoriamente ser posicionado em zero. Uma forma de declarah-lo em assembly eh:

.section INTERRUPT_VECTOR, "x"
.global _Reset
_Reset:
  B Reset_Handler /* Reset */
  B . /* Undefined */
  B . /* SWI */
  B . /* Prefetch Abort */
  B . /* Data Abort */
  B . /* reserved */
  B . /* IRQ */
  B . /* FIQ */
 
Reset_Handler:
  LDR sp, =stack_top
  BL c_entry
  B .

O vetor de interrupcao eh defindo pela ARM. Quando a placa eh resetada, o PC eh colocado na posicao zero. Quando uma instrucao indefinida for encontrada dentro de um codigo, o PC recebe 4; quando ele executar uma instrucao SWI (ou svc), o PC recebe 8 e assim por diante. Por isso, devemos ter os Branchs (B) para o tratamento de cada excessao.

Vamos rodar o programa em http://balau82.wordpress.com/2010/02/14/simplest-bare-metal-program-for-arm/ usando http://linux-kernel-lab.blogspot.com/2018/04/basics-on-arm-processor.html . Nao serah necessario instalar nada, pois temos tudo nessa imagem (qemu e arm-none).

e rode esse programa. O interessante é o startup.s que contém a inicializacao do vetor de interrupcao e faz o codigo rodar a partir da posicao zero. O problema eh que nesse post, nao existe nada impresso.

No README.md de https://github.com/EpicEric/gcc-arm.git lemos como gerar os programas e rodah-los:

### Regular program

```
eabi-gcc c_entry.c -o c_entry.o
eabi-as startup.s -o startup.o
eabi-ld -T vector_table.ld c_entry.o startup.o -o program.elf
eabi-gdb program.elf
```

### Code on emulated board with QEMU

```
eabi-gcc c_entry.c -o c_entry.o
eabi-as startup.s -o startup.o
eabi-ld -T vector_table.ld c_entry.o startup.o -o program.elf
eabi-bin program.elf program.bin
qemu program.bin
```

In another terminal, open the same container with `./run_docker.sh` (without parameters).

```
eabi-qemu -se program.elf
[gdb] break c_entry
[gdb] continue
[gdb] ...
[gdb] quit
pkill qemu

Siga os passos em http://balau82.wordpress.com/2010/02/14/simplest-bare-metal-program-for-arm/ e rode esse programa.

9.2.2 Imprima "Hello World" na placa versatile emula pelo qemu

A fim de imprimir "Hello World" usando o qemu (target remote:1234), vamos usar 2 posts do Balau:

  1. http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/
    • usa qemu e imprime string, endereco no vetor de interrupcao = 0x1000
  2. http://balau82.wordpress.com/2010/02/14/simplest-bare-metal-program-for-arm/ - usa simulador e nao imprime nada, endereco do vetor de interrupcao = zero.

O que usa o simulador faz um print e o outro que usa o qemu nao printa nada. Por isso vamos rodar basicamente o segundo post, mas fazendo com que se imprima "Hello World" como no primeiro post. Portanto, vamos seguir a risca a forma de rodar o codigo do post hello-world-for-bare-metal-arm-using-qemu passando o test.ld e startup.s (com endereco zero) do segundo post para o primeiro.

Observe que em http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/

o código passa a executar a partir de 0x1000 e não a partir do zero. Isso nao eh o que ocorre quando se faz o boot de uma placa. Provavelmente o Balau fez dessa forma, porque toda placa/simulador vem com um código em eprom que deve ser emulado que corresponde justamente ao código de boot. O que nos interessa nesse post eh a funcao printuart0.

Observe que em http://balau82.wordpress.com/2010/02/14/simplest-bare-metal-program-for-arm/ o codigo estah escrito como acontece em uma placa de verdade, no caso a placa versatile que estah sendo emulada pelo qemu. Use test.ld (o ldscript) e statup.s desse post para fazer o código rodar a partir de 0x0 como acontece em uma placa real. Esse post nao explica como fazer a placa imprimir; por isso precisamos do primeiro post. Observe:

  • o startup.s desse post contém o vetor de interrupcao na posicao zero.
  • Quando ativamos o gdb para se contectar com o qemu usando o alias eabi-qmeu; executamos o script em gcc-arm/docker/files/.gdbinit/qemu abaixo. Observe que esse script faz a contexao com o qemu via target remote e apos isso, faz load. Este load irah carregar o seu programa na posicao zero no lugar do firmware da placa.
layout regs
target remote localhost:1234
load
  • usem o test.ld abaixo que coloca o vetor de interrupcao em zero; nao usem o test.ld que coloca na posicao 0x1000.
ENTRY(_Reset)
SECTIONS
{
 . = 0x0;
 .text : {
 startup.o (INTERRUPT_VECTOR)
 *(.text)
 }
 .data : { *(.data) }
 .bss : { *(.bss COMMON) }
 . = ALIGN(8);
 . = . + 0x1000; /* 4kB of stack memory */
 stack_top = .;

Depois que conseguir imprimir "Hello World" usando o qemu, coloque um breakpoint antes de imprimir o "Hello World" e analise o cpsr. Qual é o modo em que o processador executa? Verifique se está em "Supervisor Mode". Qual é o valor de sp?

Consulte o cpsr em:

no capitulo 1, item 1.6.4

9.2.3 tratando a instrucao invalida em startup.s

Voltando para http://balau82.wordpress.com/2010/02/28/hello-world-for-bare-metal-arm-using-qemu/, crie a funcao Undefined em C que imprime a string "instrucao invalida!" e fique em um loop infinito. Pendure essa funcao no vetor de interrupcao na posicao correspondente a undefined instruction. Observe que dentro do gdb é necessário executar o "load" após o "target".

Toda instrucao corresponde a uma word na memoria. Ex: "MOV R0,R1" corresponde a uma word que pode ser vista como um numero hexadecimal. Alguns numeros fazem sentido (podem ser executados) e outros nao fazem sentido. Por exemplo, a word 0xffffffff eh uma instrucao invalida.

Coloque uma instrucao invalida (0xffffffff) em startup.s (com o startup.s inicializando o codigo em 0x0). Para isso:

Reset_Handler:
	LDR sp, =stack_top
	BL c_entry
	.word 0xffffffff
	B .

Rode o test.elf com a instrucao invalida. Observe se a string eh impressa. Talvez voce tenha visto um lixo sendo impresso ou o qemu tenha travado o talvez voce tenha visto tudo funcionando bem (por acaso). Vamos corrigir em seguida.

O possivel motivo do lixo impresso foi que o ponteiro de pilha sp no modo undefined nao foi inicializado. Veja a figura 1-2 do ARM Lab Manual. Observe os registradores no modo supervisor e no modo undefined. O registrador R0 no modo supervisor e no modo undefined sao os mesmos porque nao estao marcados (nao estao shaded), assim como a maioria dos outros registradores. Contudo, o registrador R13 (ou sp) e R14 sao diferentes, estao marcados/shaded, dependendo do modo, tanto que o SP no modo supervisor eh chamado de SPsupervisor e no modo undefined SPundefined. Quando o processador passa de um modo para outro, ele passa a usar o SP correspondente ao modo e se ele nao estiver inicializado teremos mproblemas.

Coloque um breakpoint antes e depois da instrucao invalida e analise o cpsr. Tente observar a mudanca de modo do processador. Conseguiram? Se sim, observe a mudanca no registrador sp (provavelmente o sp no modo Undefined está zerado ou com um valor qualquer. Verifique isso; portanto, qualquer alteracao de pilha nesse modo pode levar a erros com o sp errado). Se nao conseguiram, vejam o Undefined Handler no proximo item e observem a mudanca de modo dentro do Undefined Handler.

9.2.4 Um Undefined Handler simples, porem errado.

Pendure o seguinte UndefinedHandler no vetor de interrupcao em startup.s (certifique-se de que o ld script coloca o codigo em 0x0):

Undefined_Handler:
	LDR sp, =stack_top
	BL undefined

Coloque um breakpoint dentro do UndefinedHandler para observar a mudanca de modo no registrador de status.

Existem 2 erros nesse UndefinedHandler

  • a pilha nao deve ser inicializada a cada entrada na rotina de excessao. Veja o proximo item - a pilha no Undefined mode.
  • o retorno estah errado.

9.2.5 A pilha no Undefined mode.

Quando o processador chaveia de modo, o registrador sp de um modo não é o mesmo registrador sp que no outro modo; por isso é necessário inicializar os sp's de todos os modos logo no reset da placa como visto na aula passada. Veja o item 1.6 "The ARM register set" da apostila de 16.1 e observe se existem outros registradores que variam conforme o modo do processador. Para evitar problema no uso da pilha no modo Undefined, é necessário que ResetHandler inicialize também o ponteiro de pilha SPUNDEF (registrador sp no modo undefined). Inicilize SPUNDEF com o valor 0x2000 e inicialize o SPSVC (do supervisor) em 0x1000. Para isso é necessário utilizar a instrucao MSR (Move to Status Register). Usando MSR vah para o modo undefined e altere o SP. Esse SP no modo undefined (SPUNDEF) eh diferente do SP no modo supervisor. Depois volte ao modo supervisor. Descubra o número a ser carregado em MSR (observando principalmente o modo e desabilitando as interrupcoes. Exemplo de como se altera a pilha:

MRS r0, cpsr    @ salvando o modo corrente em R0
MSR cpsr_ctl, #0b11011011 @ alterando o modo para undefined - o SP eh automaticamente chaveado ao chavear o modo
LDR sp, =undefined_stack_top @ a pilha de undefined eh setada 
MSR cpsr, r0 @ volta para o modo anterior 

Pergunta:

  • A pilha do Undefined eh inicializada onde?

resposta:

  • ResetHandler pois a pilha do Undefined nao deve ser alterada logo na entrada do UndefinedHandler - o erro eh semelhante a alterar o topo da pilha ao chamar uma funcao!

9.2.6 Undefined handler

Refaça o item anterior observando se sp é alterado corretamente na troca de modos.

Crie o undefined handler em assembly de forma que ele salve e recupere os registradores. Veja:

http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0471c/Ciheidgb.html

Explique como o processador volta para o modo anterior ao sair do undefined handler. Rode o programa passo a passo observando os registradores e cpsr sendo salvos e recuperados.

A instrucao:

LDMFD sp!,{R0-R12,pc}^

serah a ultima a ser executada pelo undefinedhandler. Ela deve fazer com que o pc continue a partir da instrucao indefinida com o modo anterior (no caso supervisor). Como essa instrucao faz isso?

Responda:

  • por que tem um chapeuzinho no final da instrucao? Para que serve isso?
  • por que essa instrucao nao salva os registradores sp (ou r13) e r14?
  • se essa eh a primeira instrucao a ser executada, o sp jah deve ter sido inicializado. Quem fez isso? (voce jah deve ter feito isso logo quando a placa eh incializada usando a instrucao MSR para chavear o modo e inicializar o sp).

Antes de escrever a instrucao de store que salva os registradores no comeco do undefinedhandler coloque um breakpoint na entrada do undefinedhandler e veja onde estah o endereco de retorno. Estah na pilha ou no registrador LR? Agora acerte o undefinedhandler com as instrucoes que armazenam e recuperam o estado da pilha com o STMFD.

Agora, em seu codigo faca com que primeiro seja executada a instrucao indefinida e depois imprima o "Hello World" a fim de testar se o retorno do handler de undefined instruction estah funcionando. Bata um pritscreen mostrando o retorno para a instrucao seguinte a 0xffffffff para ser enviado como tarefa.

9.2.7 modo kernel x modo usuario

Embora existam diversos modos no ARM, podemos classificar em usuário e o resto. Quando o ARM comeca a executar está em modo supervisor. Passe para o modo usuário usando MSR e tente voltar do modo usuário para supervisor também usando MSR. O que acontece? Por quê? Refaca a experiencia trocando entre modos dentro do resto (undefined, abort, supervisor, etc.). O que acontece? É possível concluir então que existem dois grandes modos: usuário e supervisor?

9.3 Envie respondendo aas tarefas do google classroom.

9.3.1 Entrega dos arquivos fontes (feito em grupo, entrega individual) + relatorio

  • arquivos fontes: Entregue no dia da aula os codigos fontes de cada exercicio. No cabecalho de cada codigo, escreva como o arquivo deve ser gerado. Comente o codigo para voce mesmo entender na hora da prova. Caso tenha feito algum script para geracao do codigo (recomendado), anexe o script em seu codigo. Embora o codigo seja feito em grupo, a entrega eh INDIVDUAL.
  • relatorio: Formato: pdf. Entregue ateh o final do dia aula o relatorio (pdf) cada exercicio por seu grupo, com o codigo e screenshot do tela do gdb. Embora o relatorio seja feito em grupo, a entrega eh INDIVDUAL.

9.3.2 Entrega de printscreen (INDIVIDUAL)

Bata um printscreen mostrando o retorno para a instrucao seguinte a 0xffffffff (instrucao invalida) para ser enviado como tarefa; ou seja, mostre que o handler que trata de instrucao invalida retorna corretamente.

10 <2021-07-15 qui> . E10: interrupcao de tempo no Versatile emulado.

10.1 Planejamento (individual):

– Mostre irq.s e Comente as partes em que o código está dividido e o que faz cada parte. – Como o timer eh programado? Como se define o intervalo entre interrupcoes? – Para que servem os registradores do timer e onde sao utilizados no programa (inicializacao no progama principal ou interrupt handler)? TIMER0L - load TIMER0V - value TIMER0C - control TIMER0X - clear – Para que servem os registradores da controladora de interrupcao e onde sao utilizados no programa (inicializacao no progama principal ou interrupt handler)? INTEN - enable INTPND - status INTSEL - select (FIQ, IRQ)

– O que significa:

LDR r0, INTEN
LDR r1,=0x10 @bit 4 for timer 0 interrupt enable
STR r1,[r0]

10.2 Experiencia:

No ano de 2011, a equipe do Lucas Estevam fez o seguinte documento: http://www.pcs.usp.br/~jkinoshi/2012/exp-int-versatile.pdf Este relatório possui várias coisas interessantes:

  1. qemu emulando uma placa versatile sem nada - sem linux, etc.
  2. o firmware que programa a interrupcao de timer da placa, cria o vetor de interrupcao, faz o vetor de interrupcao apontar para a rotina de tratamento de interrupcao, dispara o timer de forma a gerar a interrupcao. A rotina de interrupcao limpa o pedido de interrupcao.
  3. o ld-script permite que aloquemos código em posicoes fixas, em particular, na posicao definida pela ARM para o vetor de interrupcao.
  4. Eles nao estao usando o tootle chain que vimos na aula passada arm-none-eabi e sim o que usamos ao longo das aulas que foi o arm-elf. Ao lerem a apostila usem sempre o arm-none-eabi e em especial, arm-none-eabi-ld.

O objetivo da aula é:

  • observar o vetor de interrupcao definido pela ARM.
  • observar como é feita a programacao do timer.
  • observar como as interrupcoes sao habilitadas.
  • observar como o pedido de interrupcao eh abaixado na rotina de interrupcao.
  • observar como funciona o modo usuario e o modo supervisor e consequentemente como as pilhas de usuario e supervisor sao utilizadas.

Na aula:

  • Crie um relatorio em texto para ser enviado via email com o subject "labmicro E10" no final da aula:

10.3 Geracao do código e como se roda.

  • Gere o código usando arm-none-eabi. Nao use arm-none-linux-eabi e qualquer outro toolchain. Usando os alias, voce pode gerar assim:
eabi-as irq.s -o irq.o
eabi-ld -T irqld.ld irq.o -o irq.elf
eabi-bin irq.elf irq.bin

Observe dois alias para rodar o codigo:

alias qemu='qemu-system-arm -M versatilepb -m 128M -nographic -s -S -kernel'
alias eabi-qemu='arm-none-eabi-gdb -tui --command=/home/student/.gdbinit/qemu'

Assim, rode em um terminal:

qemu irq.bin

e em outro terminal:

eabi-qemu -se irq.elf

Experimente rodar o codigo da apostila dentro do gdb, observando se cai na interrupcao de hardware fazendo algo do tipo:

b main
r
b do_irq_interrupt
c

Obs: Voce nao cairah na interrupcao apenas fazendo step (s) ou next (n); use o continue (c); nao use run (r) depois de cair no main.

Muito provavelmente voce deve cair no "doirqinterrupt", porem, se voce fez tudo corretamente (tem certeza que o codigo fonte estah correto?), entao existe uma pequena possibilidade de que a pilha tenha sido inicializada de forma errada como estah escrito no topico abaixo "Corrija os ERROS na apostila". Se depois do corrigir a inicializacao das pilhas, o seu codigo nao interromper eh porque ele deve estar errado (copiado errado ou pilha nao inicializada) ou foi gerado errado.

10.4 Observacao sobre o codigo

10.4.1 teste do INTPND

TST r0, #0x0010 @verifica se é uma interupção de timer BNE handlertimer @vai para o rotina de tratamento da interupção de timer

A instrucao TST faz o ANDS bit a bit e seta a flag Z (usada pelo BNE) caso o resultado seja zero. O importante eh olhar se o bit estah setado. Se estiver, entao o resultado nao eh zero e por isso eh feito o desvio para o handlertimer.

10.5 Corrija os ERROS na apostila

10.5.1 O código da apostila não inicializa adequadamente as pilhas na placa versatile

As pilhas no modo supervisor e no modo IRQ nao sao inicializadas. Com base na experiência da aula passada, ajuste isso; usando MSR. Se voce nao fizer isso, terah problemas graves ao ocorrerem as interrupcoes porque o codigo nao vai retornar para a posicao certa antes da interrupcao.

10.5.2 O retorno de IRQ é feito de forma ERRADA

Observe o codigo

do_irq_interrupt: @Rotina de interrupções IRQ
   STMFD sp!, {r0 - r3, LR}
   @Empilha os registradores
   LDR r0, INTPND @Carrega o registrador de status de interrupção 
   LDR r0, [r0]
   TST r0, #0x0010 @verifica se é uma interupção de timer
   BNE handler_timer @vai para o rotina de tratamento da interupção de timer
   LDMFD sp!, {r0 - r3,lr}
   mov pc, r14
   @retorna

O retorno do IRQ está errado em:

mov pc, r14

r14 é o LR. O LR é automaticamente setado pelo processador ARM quando ocorre a IRQ; porém da seguinte forma: LR = enderecoderetorno + 4 devido ao pipeline. Para resolver isso é necessário fazer retirar 4 de LR.

  • Obs - logo ao entrar na rotina de interrupcao, observe para qual instrucao o LR aponta atraves de:
x/i $lr

depois de subtrair 4 de LR, observe novamente

x/i $lr

Agora faz sentido? Isso ocorre por que a propria ARM define assim; meu chute eh pelo pipeline que andou no irq, mas nao andou no undefined (visto na aula passada).

10.5.3 Problema ao recuperar o cpsr anterior

As duas instruções:

LDMFD sp!, {r0 - r3,lr}
mov pc, r14

não recuperam o cpsr anterior. Isso acarreta um problema. O CPSR contém um bit que diz se o processador atende as interrupcoes IRQ ou não. Logo quando ocorre a interrupcao o bit I do cpsr eh zerado para desabilitar que uma interrupcao ocorra dentro de outra. Como o CPSR nao retorna ao valor anterior, ele permanece com as interrupcoes desabilitadas. Com base nessa informacao, altere o código de forma a que a rotina de interrupcao IRQ TERMINE com:

LDMFD sp!,{R0-R12,pc}^

como vimos na aula passada e explicado em http://infocenter.arm.com/help/index.jsp?topic=/com.arm.doc.dui0471c/Ciheidgb.html

  • Obs1: vários alunos trocaram a instrucao LDMFD sp!,{R0-R12,pc}^ por algo como LDMFD sp!,{R0-R12,lr}^ e depois alteraram o pc. Isso nao funciona pois o modo de operacao (e pilhas e backed registers) jah foram alterados. Eh obrigatorio executar LDMFD sp!,{R0-R12,pc}^ para retornar da rotina de interrupcao.
  • Obs2: Se sua equipe nao estiver conseguindo retornar da interrupcao rodando LDMFD sp!,{R0-R12,pc}^ observe a pilha. Coloque um breakpoint nessa instrucao e examine como estah a pilha observando o sp. Os valores que estao na pilha correspondem aos que serao colocados nos registradores, e em especial no pc? O retorno deve ser para o loop infinito do programa principal. Esse endereco de retorno estah de fato na pilha? Ao observar o endereco de retorno na pilha, voce pode observar a instrucao que serah executada atraves do comando do debugger x/i ENDERECO.

10.5.4 Polemica - BNE x BLNE

Alguns alunos consideraram que o mais correto eh usar BLNE ao inves de BNE. Isso nao eh necessario, depende de como handlertimer retorna. A ideia da apostila era retornar para o loop do programa principal diretamente, portanto nao cosideramos isso como um erro da apostila. Contudo, o codigo da apostila nao estah elegante. O melhor eh fazer como no codigo abaixo usando BLNE e fazer handlertimer retornar para quem chamou ao inves de fazer LDMFD.

do_irq_interrupt: @Rotina de interrupções IRQ
   STMFD sp!, {r0 - r3, LR}
   @Empilha os registradores
   LDR r0, INTPND @Carrega o registrador de status de interrupção 
   LDR r0, [r0]
   TST r0, #0x0010 @verifica se é uma interupção de timer
   BLNE handler_timer @vai para o rotina de tratamento da interupção de timer
   LDMFD sp!, {r0 - r3,lr}
   mov pc, r14    @retorna

10.6 Observe e faça pequenas alteracoes no codigo em assembly

10.6.1 examine no debuguer:

  • debugue o codigo: rode até a instrucao LDMFD sp!,{R0-R12,pc}^ e examine a pilha. Verifique se o valor a ser carregado em PC está correto.
  • Coloque um breakpoint logo no início da interrupcao de timer, o mais perto possivel do vetor de interrupcao, e rode até lá. Qual é o modo em que o processador roda? Qual é o sp? O que se vê na pilha? Relacione o PC que deveria apontar para a instrucao seguinte antes de ocorrer a interrupcao com o LR (veja que tem uma diferenca de 4 bytes).
  • Como o processador chaveia de modo ao sair da rotina de interrupcao? Verifique isso localizando a instrucao que faz isso. Quando estamos debugando passo a passo e observamos que o processador sai da rotina de interrupcao, pode acontecer do timer da maquina virtual continuar marcando o tempo e como nós seres humanos somos muito mais lentos que a máquina, é bem provável que logo ao sair da rotina de interrupcao, já tenha ocorrido outra interrupcao de timer. Verifique se isso acontece.

10.6.2 desabilite as instrucoes

  • Qual é a instrucao que habilita as interrupcoes? Desabilite as interrupcoes no cpsr e rode. O que acontece? Voce acha razoavel habilitar as interrupcoes enquanto se programa o timer? De fato isso nao eh razoavel, primeiro deve-se programar o timer para depois habilitar as interrupcoes. Isto eh: alterar cpsr zerando o bit I deve ser a ultima operacao a ser feita. Altere isso no codigo. Veja se funciona.
  • Se tudo estiver correto, anexe esse código no email a ser enviado ao professor.

10.6.3 endereco do vetor de interrupcao

  • Como o código definiu o vetor de interrupcao? Responda relacionando com o ARM e o ldscript.

10.6.4 timer, inicializacao

  • Observe como foi feita a programacao do timer em timerinit. A apostila faz
timer_init:
 mrs r0, cpsr
 bic r0,r0,#0x80
 msr cpsr_c,r0 @enabling interrupts in the cpsr
 LDR r0, INTEN
 LDR r1,=0x10 @bit 4 for timer 0 interrupt enable
 STR r1,[r0]
 LDR r0, TIMER0C
 LDR r1, [r0]
 MOV r1, #0xA0 @enable timer module
 STR r1, [r0]
 LDR r0, TIMER0V
 MOV r1, #0xff @setting timer value
 STR r1,[r0]
 mov pc, lr

mais especificamente:

LDR r0, TIMER0V
MOV r1, #0xff @setting timer value
STR r1,[r0]

Isso nao estah de acordo com a documentacao em http://infocenter.arm.com/help/topic/com.arm.doc.ddi0271d/DDI0271.pdf pois o registrador TIMER0V eh para se ler o valor do timer, enquanto o registrador TIMER0L eh para se fazer a carga do valor inicial. Assim, consideramos que a programacao correta do timer deveria ser:

LDR r0, TIMER0L
MOV r1, #0xff @setting timer value
STR r1,[r0]
  • Qual é o modo em que o processador roda logo no início enquanto o timer está sendo configurado? Qual registrador deve ser observado? Qual é sp utilizado?

O Mitsuo/2018 pensou no modo e na sequencia de programacao do timer, de tal forma que a alteracao do TIMER0L, altera o intervalo das interrupcoes. Troque o timerinit no seu codigo pelo codigo abaixo e faca experiencias alterando o valor de TIMER0L.

timer_init:
 LDR r0, INTEN
 LDR r1,=0x10 @bit 4 for timer 0 interrupt enable
 STR r1,[r0]
 LDR r0, TIMER0L
 LDR r1, =0xffffff @setting timer value
 STR r1,[r0]
 LDR r0, TIMER0C
 MOV r1, #0xE0 @enable timer module
 STR r1, [r0]
 mrs r0, cpsr
 bic r0,r0,#0x80
 msr cpsr_c,r0 @enabling interrupts in the cpsr
 mov pc, lr

Obs: o timer pode ser configurado como "periodic" (interrompe de tempo em tempo) ou "free running" (interrompe apenas uma vez). O valor para periodic no site estah como 1, mas a equipe do Elton,Helio,Mafra-19 observaram que ocorre o contrario: o valor para periodic deve ser zero. Essa disnticao entre "free running" e "periodic" funciona ao usar o registrador TIMER0V. Quando o registrador TIMER0L recebe um valor, entao, sempre interrompe periodicamente.

10.6.5 Fazendo o tratamento da interrupcao em C.

O código está todo em assembly, mas é possível codificar parte dele em C. Para isso precisamos de seguir diversos passos:

  • observe o código de handlertimer:
handler_timer:
	LDR r0, TIMER0X
	MOV r1, #0x0
	STR r1, [r0] @clear timer interrupt

	@ do whatever you want with the timer here

	LDMFD   sp!, {r0 - r3,lr}	 
	mov pc, r14

A instrucao mov pc, r14 corresponde ao retorno de doirqinterrupt (feito de forma errada) e não de handlertimer.

  • Altere o código assembly para que handlertimer seja uma rotina chamada em doirqinterrupt através de:

BLNE handlertimer; ou seja, handlertimer deverá se comportar como uma rotina normal que retorna para quem a chamou (e nao como um endereco para onde o codigo eh desviado e nao retorna). Verifique usando o qemu+gdb se voce consegue entrar na rotina handlertimer.

  • Crie dois arquivos assembly irq.s e handler.s contendo handlertimer; verifique se funciona, observando duas ou mais interrupcoes de relogio e observe se o sp não está variando a cada chamada - se não está gradativamente empilhando coisas.

Observe que ao linkar, a ordem com que se colocam os objetos irq.o e handler.o eh extremamente importante. Deve-se colocar primeiro o codigo que contenha o vetor de interrupcao.

  • Depois recodifique handlertimer em C. Ao fazer isso, declare um pointer em C referente a TIMER0X de forma a colocar o valor zero nessa posicao de memoria a fim de baixar o pedido de interrupcao. Nao eh preciso aproveitar a definicao de TIMER0X do assembly; eh muito mais facil declarar o ponteiro direto em C.

Obs: para exportar o nome handlertimer eh necessario usar .global

  • A partir de agora, ao ativar o qemu retire a opcao que "mata" a serial: -serial /dev/null, pois caso contrário, voce não verá os caracteres na tela.
  • Misture o codigo dessa experiencia com a da aula passada a fim de que o programa imprima uma vez "Hello World" no program principal, antes mesmo de programar o timer.
  • Usando a experiência passada, faça com que o handlertimer em C imprima o digito "#" a cada interrupcao.
  • Altere o programa principal para que ele fique em um loop continuamente imprimindo o digito " " (espaco) de tempo em tempo (fique em um loop para gastar tempo). Assim, na tela voce deverá ver " "s e "#"s intercalados. IMPORTANTE: tire um printscreen dos ' 's e '#' sendo impressos para entregar ao professor como atividade.
  • O que acontece se nao retirarmos o pedido de interrupcao na rotina que trata a interrupcao? Experimente deixando o programa rodar e explique. A frequencia com que os caracteres sao impressos varia se nao retirarmos o pedido de interrupcao? Ou seja, a relacao entre " "s e "#"s foi alterada? Explique no relatorio. Rode o programa de uma vez sem breakpoints. Em pelo menos uma equipe, aconteceu do resultado ser diferente rodando passo a passo pois nesse caso, o 1 e 2 eram intercalados enquanto que rodando de uma vez o resultado era o esperado (somente a rotina de interrupcao imprimia).

10.7 Envie respondendo aas tarefas do google classroom.

10.7.1 Entrega de relatorio e dos arquivos fontes (feito em grupo, entrega individual)

  • codigos fontes (*.s, *.c): Entregue no dia da aula os codigos fontes de cada exercicio. No cabecalho de cada codigo, escreva como o arquivo deve ser gerado. Comente o codigo para voce mesmo entender na hora da prova. Caso tenha feito algum script para geracao do codigo (recomendado), anexe o script em seu codigo. Embora o codigo seja feito em grupo, a entrega eh INDIVDUAL.
  • relatorio: Formato: .pdf. Entregue o relatorio (pdf) contendo codigo e screenshot da tela do gdb. Embora o relatorio seja feito em grupo, a entrega eh INDIVDUAL.

10.7.2 Entrega de printscreen (INDIVIDUAL)

No item "Fazendo o tratamento da interrupcao em C." pede-se que o aluno tire um printscreen da tela contendo o "#", impresso quando ocorre a interrupcao e espacos e " "s impresso no loop principal. Faca o upload mosrando isso na tela de seu computador.

11 <2021-07-22 qui> . E11: Chaveamento entre 2 processos.

11.1 Planejamento (individual):

Escreva o codigo de um programa que salva e recupera todos os registradores, inclusive PC, SP, LR e CPSR do processo que sofreu a interrupção e entregue esse codigo (em texto ou pdf; ou mesmo escrito a mao, mas lembre-se que voce deve rodar o codigo - ver abaixo - e alem disso, o seu codigo vai ser aproveitado na aula por isso acho que o pdf ou texto dessa vez eh melhor) ao retornar essa atividade no google classroom.

Para isso:

  • caso seu grupo nao tenha rodado a experiencia da aula passada, continue ateh ver os "#"s (da interrupacao) e " "s (do programa principal) intercalados.
  • Crie uma estrutura de dados (um espaco na memoria), linhaA, onde voce deverah salvar todos os registradores r0-r12 ao entrar e sair da rotina de interrupcao. Rode e teste isso em casa.
  • Aumente essa estrutura de dados para armazenar os outros registradores: PC do programa principal, LR do programa principal, SP do programa principal e CPSR do programa principal. Os outros registradores LR, SP, PC e CPSR provém de fonte diferente (veja: http://aelmahmoudy.users.sourceforge.net/electronix/arm/chapter3.htm)
  • - CPSR/supervisor está salvo em SPSR/IRQ.
  • - PC/supervisor está salvo em LR/IRQ; melhor dizendo: PC/supervisor = LR/irq - 4

Obtenha esses registradores e armazena em linhaA. Uma ideia eh, logo ao entrar na interrupcao de relogio, salvar o PC do processo, ou seja, salvar LR/IRQ -4 (pode ser em memoria numa variavel declarada por voce), liberando LR para outros usos.

  1. - LR e SP são banked registeres (ver pag 1.7 da apostila Lab Manual).

Para obter o LR e o SP do modo supervisor use as instrucoes que alteram o modo do processador MRS R0,CPSR ; or SPSR MSR CPSR,R0 ; or SPSR

Tome cuidado ao usar MSR e MRS pois, ao chavear de modo, vc. pode acabar sujando registradores como o spsr; alem disso, lembre-se que o CPSR contem o bit I onde zero em I habilita as interrupcoes. Voce deverah alterar os modos para pegar LR e SP com as interrupcoes desabilitadas. Coloque os bits I e F em 1 (desabilitando as interrupcoes) na posicao correspondente em R0 ao fazer MSR CPSR,R0

linhA eh uma estrutura que possui 17 registradores (R0-15 + cpsr) de 4 bytes, portanto possui 17*4 = 68 bytes. Cada um dos registradores da taskA poderia ser acessado individualmente. Por exemplo: R0 estah em linhaA, R1 em linhaA+4 e assim por diante.

  • Reescreva o código de forma que na interrupcao de relogio, salve todos os registradores (incluindo LR, SP, PC, CPSR) e os recupere (como se fosse o chaveamento de um processo apenas).
  • Compile e execute o código em casa. Observe os '#"s e ' 's sendo impressos como acontecia na aula de interrupcao.
  • tire um pritscreen dessa tela e anexe no planejamento.

Apresente o programa rodando no comeco da aula para o professor.

11.2 Objetivo:

  • Usar a interrupcao do timer para chavear entre duas tarefas. Crie uma taskA que continuamente imprime "1" e uma taskB que imprime "2".
  • Observe que o processo nao deve saber de nenhuma informacao do nosso "nanokernel" como onde sua pilha eh inicializada, a variavel nproc (numero do processo), linhaA ou linhaB.

O pseudo codigo de taskA deve ser algo como:

void taskA() {
  while(1) {
     print("1");
  }
}

Vamos criar um nanokernel que faz esse chaveamento:

  • carregue o vetor de interrupcao.
  • No reset, faca a inicializacao da controladora de interrupcoes e do timer.
  • A rotina de interrupcao do timer deve fazer o chaveamento entre as tarefas; para isso ela deverá salvar o estado do processo corrente em uma estrutura de dados que vamos chamar de tabela de processos. Ao sair da rotina de interrupcao, os registradores do outro processo devem ser recuperados.

Para isso:

  • Rode o programa que voce trouxe no planejamento. Voce consegue ver todos os registradores salvos adequadamente? Consegue observar a pilha do supervisor e a pilha do IRQ?
  • Além de linhaA, declare o espaco linhaB para a taskB.
  • A rotina de interrupcao deve observar o processo que estah rodando. Se for a taskA rodando, salva o estado na linha da tabela de processos da taskA e recupera o estado da linha da taskB e de forma semelhante para a taskB. Para isso, declare a variavel nproc (global) que contem o numero do processo rodando (0 para A e 1 para B).
  • reserve uma area de pilha para cada processo. Isto eh: a pilha da taskA deve estar numa posicao de memoria diferente da pilha da taskB.
  • O programa principal (correspondente a E10) deixa de ser o loop infinito. A taskA jah sai rodando logo na inicializacao. De tal forma que quando ocorrer a primeira interrupcao de tempo, a taskB passa a rodar. Ou seja, no Reset, logo depois da inicializacao, chama-se a taskA.
  • Ao ocorrer a primeira interrupcao de tempo, devemos chavear da taskA para a taskB; mas como colocar a taskB para rodar se ela nunca rodou antes? A resposta eh que basta inicilizar adequadamente os valores de linhaB fazendo com que a taskB rode normalmente. Para a taskB, deixe o SP, PC e CPSR correspondentes armazenados corretamente em linhaB. Exemplo: para inicializar o PC na linhaB deve ser fazer algo como " Store em linhaB + 60, o endereco taskB".
  • Cuidado: Inicializar o CPSR de forma errada na linhaB, com as interrupcoes desabilitadas, irah 'travar' tudo.
  • Inicialize o SP na linhaB de forma que a pilha de taskA e a pilha de taskB caiam em posicoes de memoria diferentes.
  • Ao rodar o programa, é de se esperar que saiam "1"s e "2"s intercalados.

Dicas para o debug:

  • verifique a cada interrupcao de relogio se o ponteiro de pilha (do modo interrupt) estah sempre no mesmo nivel. Caso isso nao ocorra, dados podem estar sendo acumulados na pilha. Se a pilha do modo interrupt nao estiver empilhando e desempilhando adequadamente, deve acontecer que, depois do programa estiver rodando por um certo tempo, ele trava.
  • a cada interrupcao de relogio, aproveite para ver o nivel das pilhas dos outros processos. Verifique se elas estao em posicoes diferentes e se estao acumulando bytes a cada interrupcao.

11.3 Envie respondendo aas tarefas do google classroom.

11.3.1 Entrega dos arquivos fontes + relatorio (feito em grupo, entrega individual)

  • fontes (*.s, *.c) Entregue no dia da aula os codigos fontes de cada exercicio. No cabecalho de cada codigo, escreva como o arquivo deve ser gerado. Comente o codigo para voce mesmo entender na hora da prova. Caso tenha feito algum script para geracao do codigo (recomendado), anexe o script em seu codigo. Embora o codigo seja feito em grupo, a entrega eh INDIVDUAL.
  • relatorio Formato: pdf. Entregue o relatorio (pdf) contendo codigo e screenshot da tela do gdb. Embora o relatorio seja feito em grupo, a entrega eh INDIVDUAL.

11.3.2 Entrega de printscreen (INDIVIDUAL)

  • Altere a taskA para imprimir seu nome e a taskB para imprimir seu NUSP.

No item "Fazendo o tratamento da interrupcao em C." pede-se que o aluno tire um printscreen do seu nome intercalado com seu NUSP sendo impressos pelos 2 processos, com os '#' a cada interrupcao. Faca o upload mosrando isso na tela de seu computador.

  • apresente ao professor no final da aula.

12 <2021-07-29 qui> . E12: Preparar projeto do curso a ser entregue ou terminar o chaveamento entre 2 processos.

13 <2021-08-05 qui> . E13: Preparar projeto do curso a ser entregue ou terminar o chaveamento entre 2 processos.

14 <2021-08-12 qui> . Prova2

Projeto

Ideía: Há vários anos atrás, fazia parte da avaliacao da disciplina a entrega de um trabalho que poderia ajudar o curso de labmicro e foi dessa forma, que surgiu a E11 - a experiencia que lida com a interrupcao do timer da placa Versatile emulada pelo qemu.

O livro 16.12 contém um pequeno kernel de um sistema operacional voltado para a placa Versatile emulada pelo qemu com código já pronto de como esses periféricos são acessados. O trabalho P2 pode ser um dos 3 casos:

  • focar num periférico (timer, teclado, uart, sd cards). Ver item abaixo: "Perifericos que interrompem (livro K C Wang)." Ex: criar uma experiencia que envolve a recepcao de dados do teclado. Criar a apostila que propoe a experiencia, informa dados sobre a programacao do periferico como portas e como deve ser feita a programacao da placa versatile para atender as interrupcoes (como a VIC deve ser programada). Esse tipo de experiencia eh bem simples de ser feito porque basicamente corresponde a rodar o código já pronto que está no livro. Esta apostila deve ser algo parecido com o da apostila na experiencia E11. Este trabalho pode envolver a equipe, mas cada aluno deve colocar no final da apostila um item próprio que ele tenha feito a fim de diferenciar dos outros membros da equipe. Este item especifico do aluno deve envolver algum codigo a ser desenvolvido. Cada aluno deve apresentar o codigo geral para a apostila como um todo e o codigo especifico da parte proposta pelo aluno.
  • focar em dois periféricos. Ex: receber dados do teclado e gravar no sd card depois que o usuario digitar control-q. Esse tipo de experiencia eh mais complexo e pode envolver a equipe inteira. Criar a apostila que propoe a experiencia, dados sobre a programacao de cada periferico como portas e como deve ser feita a programacao da placa versatile para atender as interrupcoes (programacao da VIC).
  • Nao criar apostila nenhuma, apenas rodar o codigo do cap 6 (ver item abaixo "Gerenciamento de memoria (livro K C Wang).") e criar um relatorio comentando como cada codigo foi rodado, problemas que surgiram, etc. Pode ser feito em equipe.

Cada equipe deve me retornar o que pretende fazer ateh segunda-feira. Em geral, o professor vai vetar projetos iguais (dependendo do projeto e das equipes). Por exemplo: se duas equipe escolherem o mesmo projeto de focar no teclado, entao a equipe que respondeu essa tarefa primeiro terah prioridade. Para evitar atrasos na tomada de decisao de que vai ser a P2, cada equipe deve propor 3 temas na esperanca de que nenhuma equipe tenha pego algum dos temas previamente.

Algumas equipes ainda nao terminaram a E11 sobre o chaveamento de processos. Espero que se dediquem e apresentem o chaveamento de processos funcionando o quanto antes a fim de que voces trabalhem na P2 durante as aulas E12 e E13.

Na aula da Prova2 estarei recebendo e avaliando os trabalhos equipe por equipe. Apresentem a apostila e o codigo rodando da experiencia proposta.

14.1 Perifericos que interrompem (livro K C Wang).

14.1.1 Vectored Interrupt Controller (VIC) (basico para os outros perifericos)

Ver paginas 47-55. Explique:

  • o que sao interrupcoes vetoradas e nao vetoradas,
  • como as interrupcoes podem ser habilitadas ou desabilitadas pela controladora e
  • como funciona a prioridade (quando duas ou mais interrupcoes acontecem ao mesmo tempo).

Proponha na apostila, diversas formas de programar a VIC mostrando como isso afeta a interrupcao de pelo menos dois perifericos: timer (E11) e algum outro como o teclado.

14.1.2 timer

paginas 56-61

  • Observe como foi feito a interrupcao do driver e compare com a E11, anotando possiveis melhorias na E11.
  • Observe como o livro explica o uso dos registradores do timer. Estah mais claro que na apostila E11?

14.1.3 teclado

pagins 61-67

14.1.4 UART

pgs 67-69

14.1.5 SD cards

pgs 63-74

14.2 Gerenciamento de memoria (livro K C Wang).

pgs 169-191

15 Avaliacao:

Nota Final = (Fase1 + 2*Fase2)/3

Fase1 = (E2+E3+E4+E5+E6)/5 * 0.2 + 0.8 * P1 Fase2 = (E8+E9+E10+E11)/4 * 0.2 + 0.8 * P2

15.1 Avaliação por experiência:

  • planejamento: 3.0
  • aula + relatorio: 7.0

16 Referências:

16.2 - docker com gnuarm- https://github.com/EpicEric/gcc-arm

16.3 ARM Assembly Language Fundamentals and Techniques, William Hohl, CRC Press

16.4 ARM System-on-Chip Architecture (2nd Edition), Steve Furber

16.5 ARM System Developer’s Guide Designing and Optimizing System Software; Andrew N. Sloss, Dominic Symes, Chris Wright; Elsevier

16.6 Linux Embarcado, Sergio Prado, https://e-labworks.com/treinamentos/linux/slides

16.7 http://www.billgatliff.com/gettingStarted.html : Uma apostila que explica o ambiente GNU para a placa evaluator 7t

16.12 Wang, K.C.; "Embedded and Real-Time Operating Systems", Springer; Softcover Reprint of the Original 1st 2017 ed. edição (9 setembro 2018)

Author: jk

Created: 2021-07-23 sex 14:30

Validate