====== 8. I/O prostor mapovaný do paměti ======
* pro vyučující: [[..:..:internal:tutorials:08:start|cvičení 8]]
===== Osnova cvičení =====
- Semestrální projekt
- Zápis na I/O bránu
- Čtení z I/O brány
- Zápis řetězce na sériový port
- Volání služby operačního systému
**Co bych si měl na cvičení zopakovat/připravit**
- Rozumět programům z předchozích cvičení
- Rozumět přednášce ([[courses:b35apo:lectures:07:start|7. Vstupně výstupní subsystém - 1. část]])
===== Náplň cvičení =====
Cílem je z programu přistupovat na periferie procesorového zařízení.
==== Semestrální práce ====
Studenti by měli po cvičení utvořit týmy, prostudovat požadavky [[courses:b35apo:semestral:start|semestrálního projektu]] a připravit návrh svého projektu.
===== Úkoly =====
S problematikou se seznamte nejdříve v simulátoru [[..:..:documentation:qtrvsim:start|QtRvSim]]. Jednoduchá vstupně výstupní periferie je mapovaná od adresy 0xffffc100.
==== Zápis hodnoty na výstupní port ====
- Napište program, který zobrazí 32-bitové číslo z registru na výstupní periferii - registr ''SPILED_REG_LED_LINE''. Jedná se o řádku 32 LED diod na desce [[..:..:documentation:mz_apo:start|MZ_APO]] (fyzická adresa 0x43c40004) nebo ekvivalentním výstupu v simulátoru [[..:..:documentation:qtrvsim:start|QtRvSim]] (přímo přístupná adresa 0xffffc104).
- Změňte následující program, aby vzor 101 dojel až doleva a zmizel. Program se po odjetí vzoru zastaví.
lui t0, 0xffffc
ori t0, t0, 0x100
addi t1, zero, 5
sw t1, 4(t0)
slli t1, t1, 1
sw t1, 4(t0)
slli t1, t1, 1
sw t1, 4(t0)
slli t1, t1, 1
ebreak
- Změňte program, aby vzor 101 dojel až doleva. Jakmile se rozsvítí LED nejvíce v levo, pak se vzor odrazí a bude se pohybovat vpravo.
- Vyzkoušejte program i pro jiné počáteční hodnoty vzoru než je 5, tedy 101. (Zkuste i vzory, které mají nastavený nejvyšší bit).
- Zjistěte co udělá následující program:
lui t0, 0xffffc
ori t0, t0, 0x100
addi t1, zero, 0
addi t2, zero, 0
la s0, n
lw s0,0(s0)
cykl1:
slli t1, t1, 1
ori t1, t1, 1
addi t2,t2,1
sw t1, 4(t0)
bne s0,t2, cykl1
ebreak
.data
n:
.word 5
-Změňte program, aby zadaný počet jedniček dojel doleva a pak se vrátil vpravo a zmizel/přejel doprava. Pak opět vygenerujte daný počet jedniček.
- Zjistěte co dělá tento program, pomocí informací [[../../documentation/mz_apo/start|mapovaných periferiích]]:
li t0, 0xffffc000 // base address into memory mapped I/O area
addi t1, zero, 48
addi t6, zero, 1234
addi t2, zero, 10
loop:
lw t3, 0x08(t0)
andi t3, t3, 1
beq t3, zero, loop
sw t1, 0x0c(t0)
addi t1, t1, 1
addi t2, t2, -1
bne t2, zero, loop
ebreak
- Načtěte počet svítících LED (jedniček) z předposledního programu z terminálu sériového portu (pouze pokud bude připraven znak).
==== Čtení ze vstupního portu ====
- Napište program, který do registru načte 32-bitovou hodnotu ze vstupní periferie (trojice otočných voličů). Na desce [[..:..:documentation:mz_apo:start|MZ_APO]] je stav voličů k přečtení z fyzické adresy 0x43c40024). V simulátoru [[..:..:documentation:qtmips:start|QtRvSim]] je nastavení voličů dostupné na adrese 0xffffc124.
- Upravte program pro generování jedniček tak, aby počet jedniček byl natočení červeného voliče dělené 16 (instrukce srli) zvětšené o 1 (aby výsledná hodnota počtu jedniček byla v rozsahu 1 - 16).
- Rozšiřte program tak, že ze vstupu přečte číslo a na výstup zobrazí hodnotu z odpovídající pozice ve výše uvedeném poli. Pokud je zadáno číslo, překračující rozsah pole, zobrazí program na výstupu samé jedničky (rozsvítí všechny LED).
==== Čtení a zápis z programu v jazyce C ====
Kompletní popis periferií implementovaných v simulátoru [[..:..:documentation:qtrvsim:start|QtRvSim]] naleznete na stránce třetího cvičení v odstavci [[..:03:start#periferie_mapovane_do_pametoveho_adresniho_prostoru|periferie mapované do paměťového adresního prostoru]] případně přímo v souboru [[https://github.com/cvut/qtrvsim/#peripherals|README.md]] přímo z projektu. Součástí třetího cvičení je i ukázka překladu z jazyka C do assembleru s rozborem generovaného kódu. Nalézá se tam také archiv s programem. Program naleznete in v adresáři ''/opt/apo/qtrvsim_binrep''. Program se shodnou funkcí pro desku MZ_APO naleznete v adresáři ''/opt/apo/binrep/mzapo_binrep''. Porovnání analýzy kódu zkompilovaného pro architekturu ARM naleznete na konci dnešního cvičení.
==== Zápis řetězce na sériový port ====
Program na výpis řetězce na sériový port pro QtRvSim
.equ SERIAL_PORT_BASE, 0xffffc000
#base address of QtRVSim serial port
.equ SERP_RX_ST_REG, 0xffffc000 #Receiver status register
.equ SERP_RX_ST_REG_o, 0x0000 #Offset of RX_ST_REG
.equ SERP_RX_ST_REG_READY_m, 0x1 #Data byte is ready to be read
.equ SERP_RX_ST_REG_IE_m, 0x2 #Enable Rx ready interrupt
.equ SERP_RX_DATA_REG, 0xffffc004 #Received data byte in 8 LSB bits
.equ SERP_RX_DATA_REG_o, 0x0004 #Offset of RX_DATA_REG
.equ SERP_TX_ST_REG, 0xffffc008 #Transmitter status register
.equ SERP_TX_ST_REG_o, 0x0008 #Offset of TX_ST_REG
.equ SERP_TX_ST_REG_READY_m, 0x1 #Transmitter can accept next byte
.equ SERP_TX_ST_REG_IE_m, 0x2 #Enable Tx ready interrupt
.equ SERP_TX_DATA_REG, 0xffffc00c #Write word to send 8 LSB bits
.equ SERP_TX_DATA_REG_o, 0x000c #Offset of TX_DATA_REG
write:
li a0, SERIAL_PORT_BASE # a0 ukazuje na zacatek pameti UART
la a1, text_1 # nacti ukazatel na retezec
next_char:
lb t1, 0(a1) # nacti znak k odeslani
beq t1, zero, end_char # 0 ukoncuje retezec
addi a1, a1, 1 # posun ukazatel
tx_busy:
lw t0, SERP_TX_ST_REG_o(a0) # zjisti statu odesilaci fronty
andi t0, t0, SERP_TX_ST_REG_READY_m # vymaskuj bit READY
beq t0, zero, tx_busy # pokud neni volno v UARTU cekej zkus to znovu
sw t1, SERP_TX_DATA_REG_o(a0) # je volno - zapis bajt
j next_char # posli dalsi znak
end_char:
ebreak # ukonci program
.data
text_1:
.asciz "Hello world.\n" # retezec ukonceny 0
Program čtení ze sériového portu pro QtRvSim
.equ SERIAL_PORT_BASE, 0xffffc000
#base address of QtRVSim serial port
.equ SERP_RX_ST_REG, 0xffffc000 #Receiver status register
.equ SERP_RX_ST_REG_o, 0x0000 #Offset of RX_ST_REG
.equ SERP_RX_ST_REG_READY_m, 0x1 #Data byte is ready to be read
.equ SERP_RX_ST_REG_IE_m, 0x2 #Enable Rx ready interrupt
.equ SERP_RX_DATA_REG, 0xffffc004 #Received data byte in 8 LSB bits
.equ SERP_RX_DATA_REG_o, 0x0004 #Offset of RX_DATA_REG
.equ SERP_TX_ST_REG, 0xffffc008 #Transmitter status register
.equ SERP_TX_ST_REG_o, 0x0008 #Offset of TX_ST_REG
.equ SERP_TX_ST_REG_READY_m, 0x1 #Transmitter can accept next byte
.equ SERP_TX_ST_REG_IE_m, 0x2 #Enable Tx ready interrupt
.equ SERP_TX_DATA_REG, 0xffffc00c #Write word to send 8 LSB bits
.equ SERP_TX_DATA_REG_o, 0x000c #Offset of TX_DATA_REG
gets:
li a0, SERIAL_PORT_BASE # a0 ukazuje na zacatek pameti UART
la a1, text_1 # nacti ukazatel na buffer
addi t2, zero, 40
next_char:
rx_not_ready:
lw t0, SERP_RX_ST_REG_o(a0) # zjisti statu prijimaci fronty
andi t0, t0, SERP_RX_ST_REG_READY_m # vymaskuj bit READY
beq t0, zero, rx_not_ready # pokud neni znak UARTU cekej zkus to znovu
lw t1, SERP_RX_DATA_REG_o(a0) # je znak - precti ho a tim odstran
sb t1, 0(a1) # uloz znak do bufferu
addi t1, t1, -13 # test je to novy radek?
beq t1, zero, end_char # ukoncuje cteni
addi a1, a1, 1 # posun ukazatel
addi t2, t2, -1 # kontroluj kolik muzeme nacist
bne t2, zero, next_char
end_char:
ebreak # ukonci program
.data
text_1:
.word 0,0,0,0,0,0,0,0,0,0
Program pro práci s otočnými voliči v QtRvSim
#base of SPILED port region
.equ SPILED_REG_BASE, 0xffffc100
#RGB LED 1 barevne slozky – 8 bitu kazda
.equ SPILED_REG_LED_RGB1, 0xffffc110
.equ SPILED_REG_LED_RGB1_o, 0x0010
#RGB LED 2 barevne slozky – 8 bitu kazda
.equ SPILED_REG_LED_RGB2, 0xffffc114
.equ SPILED_REG_LED_RGB2_o, 0x0014
#Tri 8 bitove otocne volice
#nejvyssi bajt informace o stitsknuti
.equ SPILED_REG_KNOBS_8BIT, 0xffffc124
.equ SPILED_REG_KNOBS_8BIT_o, 0x0024
#32 LEDek kazdy bit jedna LED dioda
.equ SPILED_REG_LED_LINE, 0xffffc104
.equ SPILED_REG_LED_LINE_o, 0x0004
li a0, SPILED_REG_BASE # a0 ukazuje na zacatek pameti pro I/O
ori t2, t2, -1
loop:
lw t0, SPILED_REG_KNOBS_8BIT_o(a0) # nacti data od otocnych volicu
sw t0, SPILED_REG_LED_RGB1_o(a0)
xor t1, t0, t2
sw t1, SPILED_REG_LED_RGB2_o(a0)
srli t0, t0, 24
andi t0, t0, 4
beq t0, zero, loop # pokud nebyl zmacknut cerveny volic
ebreak # ukonci program
==== Volání služby operačního systému ====
[[https://github.com/cvut/qtrvsim/#system-calls-support|Dokumentace systémových volání v QtRvSimu]]
=== Příklad použití systémového volání ===
[[https://github.com/cvut/qtrvsim/blob/master/src/gui/samples/template-os.S|template-os.S]]
.globl _start
.globl __start
.option norelax
// Linux kernel compatible system calls subset
.equ __NR_exit, 93 // void exit(int status)
.equ __NR_read, 63 // ssize_t read(int fd, void *buf, size_t count)
.equ __NR_write, 64 // ssize_t write(int fd, const void *buf, size_t count)
.equ __NR_close, 57 // int close(int fd)
.equ __NR_openat, 56 // int openat(int fd, const char *pathname, int flags, mode_t mode)
// use fd = -100 for normal open behaviour. Full openat not supported.
.equ __NR_brk, 214 // void * brk(void *addr)
.equ __NR_ftruncate64, 46 // int ftruncate64(int fd, off_t length)
.equ __NR_readv, 65 // ssize_t readv(int fd, const struct iovec *iov, int iovcnt)
.equ __NR_writev, 66 // ssize_t writev(int fd, const struct iovec *iov, int iovcnt)
.text
__start:
_start:
addi a7, zero, __NR_write // load syscall number
addi a0, zero, 1 // load file descriptor
addi a1, zero, text_1 // load text address
addi a2, zero, text_1_e - text_1 // load text length
ecall // print the text
addi a7, zero, __NR_exit // load syscall numver
addi a0, zero, 0 // load status argument
ecall // exit
final:
ebreak // request developer interaction
jal zero, final
.data
.org 0x400
data_1: .word 1, 2, 3, 4
text_1: .ascii "Hello world.\n" // store ASCII text, no termination
text_1_e:
===== Odkazy =====