From 98ca338aba486ab5e33545d2833b7c26d50da73b Mon Sep 17 00:00:00 2001 From: Virgil Dupras Date: Sun, 22 Dec 2019 21:50:20 -0500 Subject: [PATCH] avra: add LD/ST --- apps/zasm/avr.asm | 116 +++++++- tools/tests/avra/seg7multiplex.asm | 343 ++++++++++++++++++++++++ tools/tests/avra/seg7multiplex.expected | Bin 0 -> 284 bytes tools/tests/avra/testldst.asm | 18 ++ tools/tests/avra/testldst.expected | 2 + 5 files changed, 473 insertions(+), 6 deletions(-) create mode 100644 tools/tests/avra/seg7multiplex.asm create mode 100644 tools/tests/avra/seg7multiplex.expected create mode 100644 tools/tests/avra/testldst.asm create mode 100644 tools/tests/avra/testldst.expected diff --git a/apps/zasm/avr.asm b/apps/zasm/avr.asm index cf52b8f..3a4df82 100644 --- a/apps/zasm/avr.asm +++ b/apps/zasm/avr.asm @@ -31,8 +31,11 @@ instrNames: .equ I_BRBS 16 .db "BRBS", 0 .db "BRBC", 0 +.equ I_LD 18 +.db "LD", 0 +.db "ST", 0 ; Rd(5) + Rr(5) (from here, instrTbl8) -.equ I_ADC 18 +.equ I_ADC 20 .db "ADC", 0 .db "ADD", 0 .db "AND", 0 @@ -97,7 +100,7 @@ instrNames: .db "TST", 0 .db "WDR", 0 .db "XCH", 0 -.equ I_ANDI 82 +.equ I_ANDI 84 .db "ANDI", 0 .db "CBR", 0 .db "CPI", 0 @@ -106,10 +109,10 @@ instrNames: .db "SBCI", 0 .db "SBR", 0 .db "SUBI", 0 -.equ I_RCALL 90 +.equ I_RCALL 92 .db "RCALL", 0 .db "RJMP", 0 -.equ I_CBI 92 +.equ I_CBI 94 .db "CBI", 0 .db "SBI", 0 .db "SBIC", 0 @@ -118,7 +121,7 @@ instrNames: ; ZASM limitation: CALL and JMP constants are 22-bit. In ZASM, we limit ; ourselves to 16-bit. Supporting 22-bit would incur a prohibitive complexity ; cost. As they say, 64K words ought to be enough for anybody. -.equ I_CALL 96 +.equ I_CALL 98 .db "CALL", 0 .db "JMP", 0 .db 0xff @@ -280,8 +283,11 @@ parseInstruction: ld bc, 0 ld e, a ; Let's keep that instrID somewhere safe ; First, let's fetch our table row - cp I_ADC + cp I_LD jp c, .BR ; BR is special, no table row + jp z, .LD ; LD is special + cp I_ADC + jp c, .ST ; ST is special ; *** Step 2: parse arguments sub I_ADC ; Adjust index for table @@ -448,6 +454,40 @@ parseInstruction: ; bit in H, k in L. jr .spitBR2 +.LD: + ld h, 'R' + ld l, 'z' + call _parseArgs + ret nz + ld d, 0b10000000 + jr .LDST +.ST: + ld h, 'z' + ld l, 'R' + call _parseArgs + ret nz + ld d, 0b10000010 + call .swapHL + ; continue to .LDST + +.LDST: + ; Rd in H, Z in L, base upcode in D + call .placeRd + ; We're spitting LSB first, so let's compose it. + ld a, l + and 0b00001111 + or c + call ioPutB + ; Now, MSB's bit 4 is L's bit 4. How convenient! + ld a, l + and 0b00010000 + or d + or b + ; MSB composed! + call ioPutB + cp a ; ensure Z + ret + ; local routines ; place number in H in BC at position .......d dddd.... ; BC is assumed to be 0 @@ -502,6 +542,9 @@ parseInstruction: ; 'D' - A double-length number which will fill whole HL. ; 'R' - an r5 value: r0-r31 ; 'r' - an r4 value: r16-r31 +; 'z' - an indirect register (X, Y or Z), with our without post-inc/pre-dec +; indicator. This will result in a 5-bit number, from which we can place +; bits 3:0 to upcode's 3:0 and bit 4 at upcode's 12 in LD and ST. ; ; All arguments accept expressions, even 'r' ones: in 'r' args, we start by ; looking if the arg starts with 'r' or 'R'. If yes, it's a simple 'rXX' value, @@ -579,6 +622,8 @@ _parseArgs: jr z, _readK8 cp 'D' jr z, _readDouble + cp 'z' + jp z, _readz ret ; something's wrong _readBit: @@ -708,4 +753,63 @@ _readExpr: pop ix ret +; Parse one of the following: X, Y, Z, X+, Y+, Z+, -X, -Y, -Z. +; For each of those values, return a 5-bit value than can then be interleaved +; with LD or ST upcodes. +_readz: + call strlen + cp 3 + jp nc, unsetZ ; string too long + ; Let's load first char in A and second in A'. This will free HL + ld a, (hl) + ex af, af' + inc hl + ld a, (hl) ; Good, HL is now free + ld hl, .tblStraight + or a + jr z, .parseXYZ ; Second char null? We have a single char + ; Maybe + + cp '+' + jr nz, .skip + ; We have a + + ld hl, .tblInc + jr .parseXYZ +.skip: + ; Maybe a - + ex af, af' + cp '-' + ret nz ; we have nothing + ; We have a - + ld hl, .tblDec + ; continue to .parseXYZ +.parseXYZ: + ; We have X, Y or Z in A' + ex af, af' + call upcase + ; Now, let's place HL + cp 'X' + jr z, .fetch + inc hl + cp 'Y' + jr z, .fetch + inc hl + cp 'Z' + ret nz ; error +.fetch: + ld a, (hl) + ; Z already set from earlier cp + ret + +.tblStraight: + .db 0b11100 ; X + .db 0b01000 ; Y + .db 0b00000 ; Z +.tblInc: + .db 0b11101 ; X+ + .db 0b11001 ; Y+ + .db 0b10001 ; Z+ +.tblDec: + .db 0b11110 ; -X + .db 0b11010 ; -Y + .db 0b10010 ; -Z diff --git a/tools/tests/avra/seg7multiplex.asm b/tools/tests/avra/seg7multiplex.asm new file mode 100644 index 0000000..7075ff3 --- /dev/null +++ b/tools/tests/avra/seg7multiplex.asm @@ -0,0 +1,343 @@ +; This is a copy of my seg7multiplex main program, translated for zasm. +; The output of zasm was verified against avra's. + +; 7-segments multiplexer for an ATtiny45 +; +; Register usage +; R0: Digit on AFF1 (rightmost, QH on the SR) +; R1: Digit on AFF2 (QG on the SR) +; R2: Digit on AFF3 (QF on the SR) +; R3: Digit on AFF4 (leftmost, QE on the SR) +; R5: always zero +; R6: generic tmp value +; R16: generic tmp value +; R18: value to send to the SR. cleared at every SENDSR call +; in input mode, holds the input buffer +; R30: (low Z) current digit being refreshed. cycles from 0 to 3 +; +; Flags on GPIOs +; GPIOR0 - bit 0: Whether we need to refresh the display +; GPIOR0 - bit 1: Set when INT_INT0 has received a new bit +; GPIOR0 - bit 2: The value of the new bit received +; GPIOR0 - bit 4: input mode enabled + +; Notes on register usage +; R0 - R3: 4 low bits are for digit, 5th bit is for dot. other bits are unused. +; +; Notes on AFF1-4 +; They are reversed (depending on how you see things...). They read right to +; left. That means that AFF1 is least significant, AFF4 is most. +; +; Input mode counter +; When in input mode, TIMER0_OVF, instead of setting the refresh flag, increases +; the counter. When it reaches 3, we timeout and consider input invalid. +; +; Input procedure +; +; Input starts at INT_INT0. What it does there is very simple: is sets up a flag +; telling it received something and conditionally sets another flag with the +; value of the received bit. +; +; While we do that, we have the input loop eagerly checking for that flag. When +; it triggers, it records the bit in R18. The way it does so is that it inits +; R18 at 1 (not 0), then for every bit, it left shifts R18, then adds the new +; bit. When the 6th bit of R18 is set, it means we have every bit we need, we +; can flush it into Z. + +; Z points directly to R3, then R2, then R1, then R0. Because display refresh +; is disabled during input, it won't result in weird displays, and because +; partial numbers result in error display, then partial result won't lead to +; weird displays, just error displays. +; +; When input mode begins, we change Z to point to R3 (the first digit we +; receive) and we decrease the Z pointer after every digit we receive. When we +; receive the last bit of the last digit and that we see that R30 is 0, we know +; that the next (and last) digit is the checksum. + +.inc "avr.h" +.inc "tn254585.h" +.inc "tn45.h" + +; pins +.equ RCLK 0 ; on PORTB +.equ SRCLK 3 ; on PORTB +.equ SER_DP 4 ; on PORTB +.equ INSER 1 ; on PORTB + +; Let's begin! + +.org 0x0000 + RJMP MAIN + RJMP INT_INT0 + RETI ; PCINT0 + RETI ; TIMER1_COMPA + RETI ; TIMER1_OVF + RJMP INT_TIMER0_OVF + +MAIN: + LDI R16, RAMEND&0xff + OUT SPL, R16 + LDI R16, RAMEND}8 + OUT SPH, R16 + + SBI DDRB, RCLK + SBI DDRB, SRCLK + SBI DDRB, SER_DP + + ; we generally keep SER_DP high to avoid lighting DP + SBI PORTB, SER_DP + + ; target delay: 600us. At 1Mhz, that's 75 ticks with a 1/8 prescaler. + LDI R16, 0x02 ; CS01, 1/8 prescaler + OUT TCCR0B, R16 + LDI R16, 0xb5 ; TOP - 75 ticks + OUT TCNT0, R16 + + ; Enable TIMER0_OVF + IN R16, TIMSK + ORI R16, 0x02 ; TOIE0 + OUT TIMSK, R16 + + ; Generate interrupt on rising edge of INT0 + IN R16, MCUCR + ORI R16, 0b00000011 ; ISC00 + ISC01 + OUT MCUCR, R16 + IN R16, GIMSK + ORI R16, 0b01000000 ; INT0 + OUT GIMSK, R16 + + ; we never use indirect addresses above 0xff through Z and never use + ; R31 in other situations. We can set it once and forget about it. + CLR R31 ; high Z + + ; put 4321 in R2-5 + CLR R30 ; low Z + LDI R16, 0x04 + ST Z+, R16 ; 4 + DEC R16 + ST Z+, R16 ; 3 + DEC R16 + ST Z+, R16 ; 2 + DEC R16 + ORI R16, 0b00010000 ; DP + ST Z, R16 ; 1 + CLR R30 ; replace Z to 0 + + SEI + +LOOP: + RCALL INPT_CHK ; verify that we shouldn't enter input mode + SBIC GPIOR0, 0 ; refesh flag cleared? skip next + RCALL RDISP + RJMP LOOP + +; ***** DISPLAY ***** + +; refresh display with current number +RDISP: + ; First things first: setup the timer for the next time + LDI R16, 0xb5 ; TOP - 75 ticks + OUT TCNT0, R16 + CBI GPIOR0, 0 ; Also, clear the refresh flag + + ; Let's begin with the display selector. We select one display at once + ; (not ready for multi-display refresh operations yet). Let's decode our + ; binary value from R30 into R16. + MOV R6, R30 + INC R6 ; we need values 1-4, not 0-3 + LDI R16, 0x01 +RDISP1: + DEC R6 + BREQ RDISP2 ; == 0? we're finished + LSL R16 + RJMP RDISP1 + + ; select a digit to display + ; we do so in a clever way: our registers just happen to be in SRAM + ; locations 0x00, 0x01, 0x02 and 0x03. Handy eh! +RDISP2: + LD R18, Z+ ; Indirect load of Z into R18 then increment + CPI R30, 4 + BRCS RDISP3 ; lower than 4 ? don't reset + CLR R30 ; not lower than 4? reset + + ; in the next step, we're going to join R18 and R16 together, but + ; before we do, we have one thing to process: R18's 5th bit. If it's + ; high, it means that DP is highlighted. We have to store this + ; information in R6 and use it later. Also, we have to clear the higher + ; bits of R18. +RDISP3: + SBRC R18, 4 ; 5th bit cleared? skip next + INC R6 ; if set, then set R6 as well + ANDI R18, 0xf ; clear higher bits + + ; Now we have our display selector in R16 and our digit to display in + ; R18. We want it all in R18. + SWAP R18 ; digit goes in high "nibble" + OR R18, R16 + + ; While we send value to the shift register, SER_DP will change. + ; Because we want to avoid falsely lighting DP, we need to disable + ; output (disable OE) while that happens. This is why we set RCLK, + ; which is wired to OE too, HIGH (OE disabled) at the beginning of + ; the SR operation. + ; + ; Because RCLK was low before, this triggers a "buffer clock" on + ; the SR, but it doesn't matter because the value that was there + ; before has just been invalidated. + SBI PORTB, RCLK ; high + RCALL SENDSR + ; Flush out the buffer with RCLK + CBI PORTB, RCLK ; OE enabled, but SR buffer isn't flushed + NOP + SBI PORTB, RCLK ; SR buffer flushed, OE disabled + NOP + CBI PORTB, RCLK ; OE enabled + + ; We're finished! Oh no wait, one last thing: should we highlight DP? + ; If we should, then we should keep SER_DP low rather than high for this + ; SR round. + SBI PORTB, SER_DP ; SER_DP generally kept high + SBRC R6, 0 ; R6 is cleared? skip DP set + CBI PORTB, SER_DP ; SER_DP low highlight DP + + RET ; finished for real this time! + +; send R18 to shift register. +; We send highest bits first so that QH is the MSB and QA is the LSB +; low bits (QD - QA) control display's power +; high bits (QH - QE) select the glyph +SENDSR: + LDI R16, 8 ; we will loop 8 times + CBI PORTB, SER_DP ; low + SBRC R18, 7 ; if latest bit isn't cleared, set SER_DP high + SBI PORTB, SER_DP ; high + RCALL TOGCP + LSL R18 ; shift our data left + DEC R16 + BRNE SENDSR+2 ; not zero yet? loop! (+2 to avoid reset) + RET + +; toggle SRCLK, waiting 1us between pin changes +TOGCP: + CBI PORTB, SRCLK ; low + NOP ; At 1Mhz, this is enough for 1us + SBI PORTB, SRCLK ; high + RET + +; ***** INPUT MODE ***** + +; check whether we should enter input mode and enter it if needed +INPT_CHK: + SBIS GPIOR0, 1 ; did we just trigger INT_INT0? + RET ; no? return + ; yes? continue in input mode + +; Initialize input mode and start the loop +INPT_BEGIN: + SBI GPIOR0, 4 ; enable input mode + CBI GPIOR0, 1 ; The first trigger was an empty one + + ; At 1/8 prescaler, a "full" counter overflow is 2048us. That sounds + ; about right for an input timeout. So we co the easy route and simply + ; clear TCNT0 whenever we want to reset the timer + OUT TCNT0, R5 ; R5 == 0 + CBI GPIOR0, 0 ; clear refresh flag in case it was just set + LDI R30, 0x04 ; make Z point on R3+1 (we use pre-decrement) + LDI R18, 0x01 ; initialize input buffer + +; loop in input mode. When in input mode, we don't refresh the display, we use +; all our processing power to process input. +INPT_LOOP: + RCALL INPT_READ + + ; Check whether we've reached timeout + SBIC GPIOR0, 0 ; refesh flag cleared? skip next + RCALL INPT_TIMEOUT + + SBIC GPIOR0, 4 ; input mode cleared? skip next, to INPT_END + RJMP INPT_LOOP ; not cleared? loop + +INPT_END: + ; We received all our date or reached timeout. let's go back in normal + ; mode. + CLR R30 ; Ensure Z isn't out of bounds + SBI GPIOR0, 0 ; set refresh flag so we start refreshing now + RET + +; Read, if needed, the last received bit +INPT_READ: + SBIS GPIOR0, 1 + RET ; flag cleared? nothing to do + + ; Flag is set, we have to read + CBI GPIOR0, 1 ; unset flag + LSL R18 + SBIC GPIOR0, 2 ; data flag cleared? skip next + INC R18 + + ; Now, let's check if we have our 5 digits + SBRC R18, 5 ; 6th bit cleared? nothing to do + RCALL INPT_PUSH + + OUT TCNT0, R5 ; clear timeout counter + + RET + +; Push the digit currently in R18 in Z and reset R18. +INPT_PUSH: + ANDI R18, 0b00011111 ; Remove 6th bit flag + + TST R30 ; is R30 zero? + BREQ INPT_CHECKSUM ; yes? it means we're at checksum phase. + + ; Otherwise, its a regular digit push + ST -Z, R18 + LDI R18, 0x01 + RET + +INPT_CHECKSUM: + CBI GPIOR0, 4 ; clear input mode, whether we error or not + MOV R16, R0 + ADD R16, R1 + ADD R16, R2 + ADD R16, R3 + ; only consider the first 5 bits of the checksum since we can't receive + ; more. Otherwise, we couldn't possibly validate a value like 9999 + ANDI R16, 0b00011111 + CP R16, R18 + BRNE INPT_ERROR + RET + +INPT_TIMEOUT: + CBI GPIOR0, 4 ; timeout reached, clear input flag + ; continue to INPT_ERROR + +INPT_ERROR: + LDI R16, 0x0c ; some weird digit + MOV R0, R16 + MOV R1, R16 + MOV R2, R16 + MOV R3, R16 + RET + +; ***** INTERRUPTS ***** + +; Record received bit +; The main loop has to be fast enough to process that bit before we receive the +; next one! +; no SREG fiddling because no SREG-modifying instruction +INT_INT0: + CBI GPIOR0, 2 ; clear received data + SBIC PINB, INSER ; INSER clear? skip next + SBI GPIOR0, 2 ; INSER set? record this + SBI GPIOR0, 1 ; indicate that we've received a bit + RETI + +; Set refresh flag whenever timer0 overflows +; no SREG fiddling because no SREG-modifying instruction +INT_TIMER0_OVF: + SBI GPIOR0, 0 + RETI + + diff --git a/tools/tests/avra/seg7multiplex.expected b/tools/tests/avra/seg7multiplex.expected new file mode 100644 index 0000000000000000000000000000000000000000..1cb0a70c1c8ad3dc899406c859be655d22db85a2 GIT binary patch literal 284 zcmXwyze>YE0LCwu1~08KcCs$gq{6$AeVOzt;MRrru$iE$#K+rNGQRfd z@KkbJNd|!A4J_wDoijv{&Kch3yrX=#OW(c_T{oRIM>KjwbipGwU4?9;J-ySs9jQh7 zU6eTd%OmEyaXO+WTd1ZhM91@TbXfUIPd8fz&I|`SrsK}xLI1OLb~PaTWJo3i(U?7@ KF^~9wh7G?ai+fN2 literal 0 HcmV?d00001 diff --git a/tools/tests/avra/testldst.asm b/tools/tests/avra/testldst.asm new file mode 100644 index 0000000..e21bfb0 --- /dev/null +++ b/tools/tests/avra/testldst.asm @@ -0,0 +1,18 @@ +ld r0, X +ld r1, Y +ld r2, Z +ld r3, X+ +ld r4, Y+ +ld r5, Z+ +ld r6, -X +ld r7, -Y +ld r8, -Z +st X, r9 +st Y, r10 +st Z, r11 +st X+, r12 +st Y+, r13 +st Z+, r14 +st -X, r15 +st -Y, r16 +st -Z, r17 diff --git a/tools/tests/avra/testldst.expected b/tools/tests/avra/testldst.expected new file mode 100644 index 0000000..e8e55e1 --- /dev/null +++ b/tools/tests/avra/testldst.expected @@ -0,0 +1,2 @@ +  =IQnzْ͒ + \ No newline at end of file