Com 16 bits podemos ter até 65536 variações de instruções (provavelmente alguns desses 65536 opcodes são ignorados pelo processador). Acho que talvez seja melhor criarmos um arquivo binário com instruções de 00 00 a FF FF e abrirmos no IDA. Aí teremos todos os opcodes e todas as intruções reconhecidas pelo IDA no modo Thumb.
Sinto que é uma pergunta besta, mas aí vai:
Nenhuma instrução espera parâmetros? Com o instruction set do 8051 eu sei que não dá para fazer isso.
Pelo que entendi do ARM Assembly, os parâmetros ficam embutidos nas instruções. Naquela primeira referência que enviei, isso é explicado de maneira bem didática.
Isso! Então os parâmetros existem, mas eles não têm bytes dedicados, como nas máquinas CISC.
Por exemplo, na instrução 8052
mov A, #0xB0 nós temos um byte que identifica a instrução (#74) e outro exclusivo para o operando (#B0). Isso é a causa de um dos dois problemas da arquitetura CISC. Apesar de ter a seu favor uma grande quantidade de tipos de instruções mais complexas disponível ao programador, as intruções podem variar bastante na quantidade de bytes e no tempo de execução. Assim, só em olhar o assembly de duas rotinas diferentes que realizam a mesma função, um programador não pode dizer de imediato qual a que vai ocupar menos espaço e qual vai ser executada mais rapidamente. Existem instruções mais complexas que ocupam menos espaço que instruções mais complexas. Instruções menores podem gastar mais ciclos do processador do que instruções maiores. E os dois fatores importantes para o desempenho são o espaço e o tempo.
A arquitetura RISC, então, têm como objetivo instruções que ocupam sempre o mesmo espaço e gastam sempre o mesmo número de ciclos do processador. Na verdade, apenas 1 ciclo. O preço é que temos um conjunto de instruções mais simples, que podem exigir mais do programador. Isso é tudo na teoria, porém, na prática, os resultados são bem próximos dos ideais. O ARM, no modo Thumb, possui sempre instruções de 2 bytes e a maioria das instruções gasta apenas 1 ciclo por execução.
O seguinte documento do ARM10 (pela documentação, o ARM10 é compatível com o ARM7, mas não sei até que ponto) mostra o mapa de opcodes do modo Thumb:
http://infocenter.arm.com/help/topic/com.arm.doc.dvi0014a/DVI0014A_ARM10T_PO.pdf. Ele não possui, porém, os valores dos opcodes dos bits exclusivos para a identificação das instruções, mas acredito que sejam os mesmos da documentação indicada pelo zeurt.
Na página 14 temos o mapa. A primeira instrução é o "deslocamento por valor imediato", que é nada mais que deslocar os bits de um valor para a esquerda (multiplicar por 2) ou para a direita (dividir por 2). Essa instrução possui 3 operandos: o valor constante que indica o número de deslocamentos (o número de bits deslocados), um registrador de origem e um registrador de destino. A instrução começa sempre com 000 e depois temos 2 bits para o opcode. Isso nos dá 4 variações na instrução (00, 01, 10 e 11), que eu acredito que deva ser deslocar à esquerda, deslocar à direita, rodar à esquerda e rodar à direita. Em seguida, temos 5 bits para identificar o operando constante e, por último, os bits que identificam o registrador de origem (leitura do valor que será deslocado) e os bits que identificam o registrador que irá guardar o resultado. Nenhum desses parâmetros possui um byte exclusivo. A instrução LSLS R0, R0, #0x4, que significa pegar o valor em R0, deslocar 4 bits à esquerda (equivale a multiplicar o valor por 2
4) e guardar o resultado no próprio registrador R0, terá a seguinte codificação:
000 00 00100 000 000
Lembrando que #B00100=4. Agora, separando os bytes:
00000001 | 00000000 = #01 | #00
Como o código é formado começando pelo byte menos significativo, o opcode final será #00 #01.
Aqui cabe ressaltar a pseudo-instrução NOP. Esta instrução tem código 00 00 no modo Thumb. Assim, ela é, na verdade, um LSLS R0, R0, #0, que significa pegar o valor de R0, deslocar 0 bits e guardar novamente em R0. Apesar de parecer complexa, esta instrução gasta somente 1 ciclo do processador, como um NOP.
Vamos analisar uma instrução de um único parâmetro: a instrução de salto relativo incondicional, o BRANCH. Olhando no mapa, a quarta instrução de baixo para cima, vemos que ela começa sempre com 11100. Ela possui mais 11 bits para o offset do salto relativo. Isso pode fazer parecer com que exista um byte de parâmetro. Por exemplo, o código 02 E0 signfica um salto de 2*16 bits (4 endereços) para frente. Já o código 04 E0 significa saltar para 8 endereços à frente. Assim, podemos pensar que XX E0 é o código do BRANCH, sendo que XX é o offset. Porém, é só uma coincidência. Se o offset for maior que 255, o outro valor não será mais E0.
Acho que a única instrução que podemos considerar com parâmetro é o MOV (quarta instrução de cima para baixo). Isso porque, no modo Thumb, o valor do offset dessa instrução tem examente 8 bits. Mas isso é uma "coincidência" (cuidadosamente planejada pelos engenheiros) do modo Thumb.
No modo 32 bits, o offset é maior.
Eu ainda não tinha percebido que os nossos firmwares usam instruções de 16 bits (modo Thumb) e não de 32 bits (modo ARM).
Acho que é porque até agora mexi muito pouco no ARM, e apenas para modificações mais simples, sendo que dava mais atenção às instruções do que à codificação das mesmas.
Aparentemente é o chamado Thumb-2 que, segundo a documentação do ARM, permite usar o modo Thumb juntamente com algumas instruções de 32 bits. É o caso dos BL (Branch with Link). Veja que os BLs têm 4 bytes.
EDIT: Parece que o modo Thumb normal é o mais usado nesses firmwares, mas há algumas poucas rotinas em modo ARM também. Já o BL do modo Thumb tem prefixo de 16 bits e sufixo de 16 bits, sendo uma das poucas instruções daquele modo que possuem 4 bytes. Mais informações,
neste tópico.