PNut/Spin2 Latest Version (v34Q)

1456810

Comments

  • Can an ISR (presumably in lower cog RAM) call a Spin method somehow?

    Or, is there some other way for an interrupt to trigger a Spin method?
  • Rayman wrote: »
    Can an ISR (presumably in lower cog RAM) call a Spin method somehow?

    Or, is there some other way for an interrupt to trigger a Spin method?

    Calling a Spin2 method from an interrupt is complicated. You'd need to have some dedicated stack space in VAR, to start. You'd be interrupting the Spin2 program midway through a SKIPF pattern. There would be some things that would need to be carefully saved and restored. I'm sure it's possible, though.
  • cgracey wrote: »
    wmosscrop wrote: »
    This won't compile for me:
    OBJ
      jm_numbers[2] : "jm_nstrings"  
     
    PUB begin(value) | str
      str := jm_numbers[0].fmt_number(value, 10, 0, 8, " ")
    

    It expects a "." after the jm_numbers reference in the begin method, as if the object wasn't part of an array.

    Okay. I'll have this fixed in about 8 hours. I need to get some sleep, first. Sorry about this. We'll get it figured out soon.

    So, I just modified my VGA demo program to have 4 instances of the VGA driver and I can use each of them separately:
    CON _clkfreq = 297_000_000
    
    OBJ
      vga[4] : "VGA_640x480_text_80x40"	'_clkfreq >= 50MHz
    ' vga : "VGA_1280x1024_text_160x85"	'_clkfreq >= 216MHz
    ' vga : "HDTV_1920x1080_text_240x90"	'_clkfreq >= 297MHz
    ' vga : "HDTV_1920x1080_text_135x160"	'_clkfreq >= 297MHz
    
    PUB go() | i, t
    
      vga[3].start(8)		'start vga
    
      send := @vga[3].print		'set send pointer
    
      send(4, $004040, 5, $00FFFF)	'cyan on dark cyan
    
      t := getct()			'capture time
    
      i := @text			'print file
      repeat @textend-i
        send(byte[i++])
    
      i := getct()			'capture time
    
      t := muldiv64(i - t, 1_000_000, clkfreq)	'get delta in microseconds
    
      send(12, "Elapsed time during printing was ", dec(t), " microseconds.")
    
    PRI dec(value) | flag, place, digit
    
      flag~
      place := 1_000_000_000
      repeat
        if flag ||= (digit := value / place // 10) || place == 1
          send("0" + digit)
          if lookdown(place : 1_000_000_000, 1_000_000, 1_000)
            send(",")
      while place /= 10
    
    DAT
    
    text	file	"vga_text_demo.txt"
    textend
    

    Can you post more of your code that is giving you trouble?
  • @cgracey Is there an error with unary negation? This creates an error in Spin2, but not in Spin1.
    pub set_forward(speed)
    
    '' Run motor forward at speed
    
      if (speed < 0)
        -speed
        
      set_speed(speed)
    
    For the moment I've fixed it with...
    pub set_forward(speed)
    
    '' Run motor forward at speed
    
      if (speed < 0)
        speed := -speed
        
      set_speed(speed)
    
    Again, I point this out because of the difference between Spin1 and Spin2.
  • JonnyMac wrote: »
    @cgracey Is there an error with unary negation? This creates an error in Spin2, but not in Spin1.
    pub set_forward(speed)
    
    '' Run motor forward at speed
    
      if (speed < 0)
        -speed
        
      set_speed(speed)
    
    For the moment I've fixed it with...
    pub set_forward(speed)
    
    '' Run motor forward at speed
    
      if (speed < 0)
        speed := -speed
        
      set_speed(speed)
    
    Again, I point this out because of the difference between Spin1 and Spin2.

    In Spin2:

    -= speed

    The prior syntax was cleaner in that case, but there is a consistent pattern to the new syntax.

    Look at the operators list in the documentation file I have going.
  • cgraceycgracey Posts: 12,677
    edited 2020-03-15 - 09:32:03
    I just posted new version v34n at the top of this thread.

    Documentation has been updated to cover all built-in methods.

    Built-in methods now require '()', just like user methods: GETCT().

    GETSECS() was added to determine seconds since reset/boot. It divides the 64-bit system counter by CLKFREQ to get a 32-bit 'seconds' value (this was JonnyMac's idea). 32 bits can hold 136 years' of seconds. It's something to think how the system counter, running at 297 MHz, goes through those same 32 bits in under 15 seconds.

    I rewrote the STRSIZE and STRCOMP methods to be much smaller:
    '
    '
    ' STRSIZE(adr)		- returns length
    ' STRCOMP(adra,adrb)	- returns true/false
    '
    strsize_	loc	pa,#strsize_buff
    		skip	#%1
    strcomp_	loc	pa,#strcomp_buff
    
    load_buff	setq	#7-1			'largest buff program is 7 longs
    		rdlong	buff,pa
    		jmp	#buff
    
    
    strsize_buff	rdfast	#0,x
    		mov	x,#0
    .loop		rfbyte	y		wz
    	if_nz	ijnz	x,#.loop
    		ret
    
    strcomp_buff	popa	ptrb
    		rdfast	#0,x
    .loop		rdbyte	x,ptrb++
    		rfbyte	y
    		cmp	x,y		wz
    	if_z	tjnz	x,#.loop
    	_ret_	muxz	x,_FFFFFFFF
    

    They now load code snippets into BUFF[0..6] and execute them from cog RAM, which is fast and compact.

    Also, the xxxxFILL method was shrunk by handling the fill-value replication in the same way STRSIZE/STRCOMP work.

    The interpreter code is now 76 bytes smaller.
  • JonnyMacJonnyMac Posts: 6,595
    edited 2020-03-15 - 09:46:44
    If you can only add one "get time" command, I think getms() (get milliseconds since reset) would be more useful for differential timing. Hopefully, you have space for a few more instructions so you can divide clkfreq by 1000. 32 bits of millisecond is 49 days -- I think that's going to be okay. :)

    Thanks for adding that.
  • cgraceycgracey Posts: 12,677
    edited 2020-03-15 - 10:13:46
    JonnyMac wrote: »
    If you can only add one "get time" command, I think getms() (get milliseconds since reset) would be more useful for differential timing. Hopefully, you have space for a few more instructions so you can divide clkfreq by 1000. 32 bits of millisecond is 49 days -- I think that's going to be okay. :)

    Thanks for adding that.

    We can have both, actually.

    Thinking about this, it could be tricky. If we use (CT64 / (CLKFREQ/1000) for milliseconds, it's going to have a potentially large error compared to (CT64 / CLKFREQ) if CLKFREQ's three least-significant digits are not 0. It would be lossy. However, we could just compute (CT64 / CLKFREQ) and then use the remainder to get a correlated milliseconds value. That's how it would have to work for GETSECS() to correlate to GETMS(). We would do (remainder FRAQ CLKFREQ) to get a 32-bit fraction and then scale that unsigned value by 1,000 to get milliseconds. We could also scale by 1,000,000 to get microseconds. This would keep all our timing straight across time units. In fact, we would have those sub-second readings for 2^64 system clock ticks. Maybe there'd be some value in a 'GETSUS() : seconds, microseconds' method, which would always return whole seconds and microseconds, for almost 2,000 years, as a continuous function, at 297MHz.
  • I think most of the Spin2 interpreter is in cog reg/LUT RAM with rest in hub RAM. Is there a size limit for how much is hub RAM?

    A previous request for a new feature was met with the response that it would not fit.
  • TonyB_ wrote: »
    I think most of the Spin2 interpreter is in cog reg/LUT RAM with rest in hub RAM. Is there a size limit for how much is hub RAM?

    A previous request for a new feature was met with the response that it would not fit.

    It can get as big as the hub RAM, but I have kept it at 4KB or less. I don't want it to become a pig.

    What was that feature? I can't remember.
  • cgracey wrote: »
    TonyB_ wrote: »
    I think most of the Spin2 interpreter is in cog reg/LUT RAM with rest in hub RAM. Is there a size limit for how much is hub RAM?

    A previous request for a new feature was met with the response that it would not fit.

    It can get as big as the hub RAM, but I have kept it at 4KB or less. I don't want it to become a pig.

    What was that feature? I can't remember.

    It was using 64-bit GETCT for WAITMS/WAITUS. About half a dozen posts starting at
    http://forums.parallax.com/discussion/comment/1491573/#Comment_1491573
  • TonyB_TonyB_ Posts: 1,460
    edited 2020-03-15 - 12:12:27
    SKIP can be a quicker alternative to JMP for hub exec, however the more skipped instructions there are the greater the chance of a FIFO reload.

    In this Spin2 interpreter code is it worth saving two longs at the cost of 12 extra cycles?
    ' a: GETSECS()
    ' b: MULDIV64(m1,m2,d)
    '
    getsecs_	pusha	x			'a
    
    		getct	z		wc	'a
    		getct	y			'a
    
    		rdlong	x,#@clkfreq_hub		'a
    
    		skip	#%011111		'a
    
    muldiv64_	setq	#2-1			'| b	pop m1 and m2
    		rdlong	y,--ptra		'| b	x=d, y=m1, z=lm2
    
    		qmul	y,z			'| b	multiply m1 * m2
    		getqx	y			'| b
    		getqy	z			'| b
    
    		setq	z			'a b	divide by d
    		qdiv	y,x			'a b
    
    	_ret_	getqx	x			'a b	return quotient
    

    Alternative:
    ' GETSECS()
    '
    getsecs_	pusha	x
    
    		getct	z		wc
    		getct	y
    
    		rdlong	x,#@clkfreq_hub
    
    		setq	z			'divide by d
    		qdiv	y,x
    
    	_ret_	getqx	x			'return quotient
    '
    '
    ' MULDIV64(m1,m2,d)
    '
    muldiv64_	setq	#2-1			'pop m1 and m2
    		rdlong	y,--ptra		'x=d, y=m1, z=lm2
    
    		qmul	y,z			'multiply m1 * m2
    		getqx	y
    		getqy	z
    
    		setq	z			'divide by d
    		qdiv	y,x
    
    	_ret_	getqx	x			'return quotient
    

    EDIT:
    A long in another SKIP sequence can be saved.
  • Thanks, Tony. That's right, using the 64-bit count for the WAITMS and WAITUS.

    How you rewrote the code is how I had first written it.
  • Are long variable arrays long aligned in HUB?

    I think I need that to be so for wflong use with wrapping...
  • Rayman wrote: »
    Are long variable arrays long aligned in HUB?

    I think I need that to be so for wflong use with wrapping...

    Every object's VAR area starts out long-aligned, but variables get packed into memory in the order they are declared. If a single byte is expressed, you will no longer be long aligned. You can use the ALIGNL keyword to force long alignment before you declare your long array.

    VAR ALIGNL LONG Buff[1000]
  • Every object's VAR area starts out long-aligned, but variables get packed into memory in the order they are declared.
    This is a change from the P1, right? In the P1 aren't variables backed by size; longs, words, then bytes? I like this new feature as it allows one to create a structure that is easily saved and retrieved from a byte-oriented device like an EEPROM.
  • TonyB_TonyB_ Posts: 1,460
    edited 2020-03-16 - 12:09:39
    Code can be a bit quicker and shorter sometimes by deliberately not SKIPping over unwanted instructions.

    Spin2 interpreter:
    ' a: In-line PASM
    ' b: REGEXEC(hubadr)
    ' c: REGLOAD(hubadr)
    ' d: CALL(anyadr)
    '
    inline		setq	#16-1			'a		load local variables from hub into buff
    		rdlong	buff,dbase		'a
    		bith	pb,#31			'a		set flag to restore local variable to hub
    
    		mov	ptrb,pb			'a		get bytecode ptr into ptrb
    		skip	##%11100100000111	'a	x2	begin inline_pasm skip pattern
    
    regexec_	skip	##%1111000000		'| b	x2	begin REGEXEC skip pattern
    regload_	mov	ptrb,x			'| b c		get hubadr into ptrb
    
    		rdword	w,ptrb++		'a b c		read start register
    		rdword	y,ptrb++		'a b c		read length of pasm code, minus 1
    
    		setq	y			'a b c		read in code
    		altd	w			'a b c
    		rdlong	0,ptrb++		'a b c		altd causes ptrb++ to inc by 4, not by (y+1)*4
    
    	_ret_	popa	x			'| | c		REGLOAD done, pop stack
    
    		shl	y,#2			'a |		update bytecode ptr for inline_pasm
    		add	y,ptrb			'a |
    
    call_		mov	w,x			'| |   d	get CALL address
    		popa	x			'| b   d	pop stack
    
    		mov	y,pb			'| b   d	save bytecode ptr
    		mov	z,ptra			'a b   d	save ptra
    

    Alternative that saves one long and two cycles for patterns a and b, but adds four cycles for d:
    inline		setq	#16-1			'a		load local variables from hub into buff
    		rdlong	buff,dbase		'a
    		bith	pb,#31			'a		set flag to restore local variable to hub
    
    		mov	ptrb,pb			'a		get bytecode ptr into ptrb
    		skip	##%1001110000011	'a	x2	begin inline_pasm skip pattern
    
    regexec_	skip	#%11000000		'| b		begin REGEXEC skip pattern
    regload_	mov	ptrb,x			'| b c		get hubadr into ptrb
    
    		rdword	w,ptrb++		'a b c		read start register
    		rdword	y,ptrb++		'a b c		read length of pasm code, minus 1
    
    		setq	y			'a b c		read in code
    		altd	w			'a b c
    		rdlong	0,ptrb++		'a b c		altd causes ptrb++ to inc by 4, not by (y+1)*4
    
    	_ret_	popa	x			'| | c		REGLOAD done, pop stack
    
    call_		mov	w,x			'| |   d	get CALL address
    		popa	x			'| b   d	pop stack
    
    		shl	y,#2			'a b   d	update bytecode ptr for inline_pasm
    		add	y,ptrb			'a b   d	b: & d: incorrect y is overwritten next
    
    		mov	y,pb			'| b   d	save bytecode ptr
    		mov	z,ptra			'a b   d	save ptra
    

    10-bit immediate replaced by 8-bit for second SKIP.
  • TonyB_, awesome! Keep going. There's probably all kinds of stuff in there that can be improved. It's really good when another person looks it over.
  • JonnyMac wrote: »
    Every object's VAR area starts out long-aligned, but variables get packed into memory in the order they are declared.
    This is a change from the P1, right? In the P1 aren't variables backed by size; longs, words, then bytes? I like this new feature as it allows one to create a structure that is easily saved and retrieved from a byte-oriented device like an EEPROM.

    Right. This is different than Spin, where all the variables were grouped by size. This way, they all stack in the order they are declared, with no regard for alignment, unless you put an alignment operator in.
  • cgracey wrote: »
    Can you post more of your code that is giving you trouble?
    OBJ
      jm_numbers[2] : "jm_nstrings"  
     
    PUB begin(value) | str
      jm_numbers[0].fmt_number(value, 10, 0, 8, " ")         ' Compiles
      str := jm_numbers[0].fmt_number(value, 10, 0, 8, " ")  ' Does not compile
    

    It appears to me that the issue is with assignment statements. Your sample code doesn't appear to have any with results from an object reference.

  • cgracey wrote: »
    I just posted new version v34n at the top of this thread.

    Documentation has been updated to cover all built-in methods.

    Is there any documentation regarding new spin2 keywords/methods such as the PINxxx methods?

  • wmosscrop wrote: »
    cgracey wrote: »
    I just posted new version v34n at the top of this thread.

    Documentation has been updated to cover all built-in methods.

    Is there any documentation regarding new spin2 keywords/methods such as the PINxxx methods?

    Yes, there is a documnentation link within the first post in this thread.
  • cgracey wrote: »
    TonyB_, awesome! Keep going. There's probably all kinds of stuff in there that can be improved. It's really good when another person looks it over.

    A 10-bit immediate is the most annoying thing, ever.
  • TonyB_ wrote: »
    A 10-bit immediate is the most annoying thing, ever.
    The ability to affect multiple pins simultaneously with one instruction is very powerful.

    Pick a starting pin, and then add how many above it. Not difficult, just new.
  • I think it'd be nice to get a constant from an object like:

    object.constant

    instead of the current:

    object#constant
  • Rayman wrote: »
    I think it'd be nice to get a constant from an object like:

    object.constant

    instead of the current:

    object#constant

    The new way IS object.constant.
  • cgracey wrote: »
    TonyB_, awesome! Keep going. There's probably all kinds of stuff in there that can be improved. It's really good when another person looks it over.

    I'll keep going until I get it right. There was an undeliberate error due to concentrating on a and b and forgetting about d. Corrected above, however I think this is a lot better (blank lines removed for brevity):
    ' a: In-line PASM
    '
    inline		setq	#16-1			'a		load local variables from hub into buff
    		rdlong	buff,dbase		'a
    		bith	pb,#31			'a		set flag to restore local variable to hub
    		mov	ptrb,pb			'a		get bytecode ptr into ptrb
    		rdword	w,ptrb++		'a		read start register
    		rdword	y,ptrb++		'a		read length of pasm code, minus 1
    		setq	y			'a		read in code
    		altd	w			'a
    		rdlong	0,ptrb++		'a		altd causes ptrb++ to inc by 4, not by (y+1)*4
    		shl	y,#2			'a		update bytecode ptr for inline_pasm
    		add	y,ptrb			'a
    		mov	z,ptra			'a		save ptra
    		call	w			'a		call pasm code (can use pa/pb/ptra/ptrb/stack, C/Z=0)
    		mov	pb,y		wc	'a		restore bytecode ptr
    	if_c	setq	#16-1			'a		if inline_pasm, restore local variables to hub
    	if_c	wrlong	buff,dbase		'a	
    	_ret_	mov	ptra,z			'a		restore ptra
    '
    '
    ' b: REGEXEC(hubadr)
    ' c: REGLOAD(hubadr)
    ' d: CALL(anyadr)
    '
    regexec_	skip	#%11000000		'  b		begin REGEXEC skip pattern
    regload_	mov	ptrb,x			'  b c		get hubadr into ptrb
    		rdword	w,ptrb++		'  b c		read start register
    		rdword	y,ptrb++		'  b c		read length of pasm code, minus 1
    		setq	y			'  b c		read in code
    		altd	w			'  b c
    		rdlong	0,ptrb++		'  b c		altd causes ptrb++ to inc by 4, not by (y+1)*4
    	_ret_	popa	x			'  | c		REGLOAD done, pop stack
    call_		mov	w,x			'  |   d	get CALL address
    		popa	x			'  b   d	pop stack
    		mov	y,pb			'  b   d	save bytecode ptr
    		mov	z,ptra			'  b   d	save ptra
    		call	w			'  b   d	call pasm code (can use pa/pb/ptra/ptrb/stack, C/Z=0)
    		mov	pb,y		wc	'  b   d	restore bytecode ptr
    	if_c	setq	#16-1			'  b   d	if inline_pasm, restore local variables to hub
    	if_c	wrlong	buff,dbase		'  b   d
    	_ret_	mov	ptra,z			'  b   d	restore ptra
    

    +8 longs (34 now, 26 originally)
    a: inline -18 cycles (-33%)
    b: REGEXEC -6 cycles (-14%)
  • TonyB_ wrote: »
    cgracey wrote: »
    TonyB_, awesome! Keep going. There's probably all kinds of stuff in there that can be improved. It's really good when another person looks it over.

    I'll keep going until I get it right. There was an undeliberate error due to concentrating on a and b and forgetting about d. Corrected above, however I think this is a lot better (blank lines removed for brevity):
    ' a: In-line PASM
    '
    inline		setq	#16-1			'a		load local variables from hub into buff
    		rdlong	buff,dbase		'a
    		bith	pb,#31			'a		set flag to restore local variable to hub
    		mov	ptrb,pb			'a		get bytecode ptr into ptrb
    		rdword	w,ptrb++		'a		read start register
    		rdword	y,ptrb++		'a		read length of pasm code, minus 1
    		setq	y			'a		read in code
    		altd	w			'a
    		rdlong	0,ptrb++		'a		altd causes ptrb++ to inc by 4, not by (y+1)*4
    		shl	y,#2			'a		update bytecode ptr for inline_pasm
    		add	y,ptrb			'a
    		mov	z,ptra			'a		save ptra
    		call	w			'a		call pasm code (can use pa/pb/ptra/ptrb/stack, C/Z=0)
    		mov	pb,y		wc	'a		restore bytecode ptr
    	if_c	setq	#16-1			'a		if inline_pasm, restore local variables to hub
    	if_c	wrlong	buff,dbase		'a	
    	_ret_	mov	ptra,z			'a		restore ptra
    '
    '
    ' b: REGEXEC(hubadr)
    ' c: REGLOAD(hubadr)
    ' d: CALL(anyadr)
    '
    regexec_	skip	#%11000000		'  b		begin REGEXEC skip pattern
    regload_	mov	ptrb,x			'  b c		get hubadr into ptrb
    		rdword	w,ptrb++		'  b c		read start register
    		rdword	y,ptrb++		'  b c		read length of pasm code, minus 1
    		setq	y			'  b c		read in code
    		altd	w			'  b c
    		rdlong	0,ptrb++		'  b c		altd causes ptrb++ to inc by 4, not by (y+1)*4
    	_ret_	popa	x			'  | c		REGLOAD done, pop stack
    call_		mov	w,x			'  |   d	get CALL address
    		popa	x			'  b   d	pop stack
    		mov	y,pb			'  b   d	save bytecode ptr
    		mov	z,ptra			'  b   d	save ptra
    		call	w			'  b   d	call pasm code (can use pa/pb/ptra/ptrb/stack, C/Z=0)
    		mov	pb,y		wc	'  b   d	restore bytecode ptr
    	if_c	setq	#16-1			'  b   d	if inline_pasm, restore local variables to hub
    	if_c	wrlong	buff,dbase		'  b   d
    	_ret_	mov	ptra,z			'  b   d	restore ptra
    

    +8 longs (34 now, 26 originally)
    a: inline -18 cycles (-33%)
    b: REGEXEC -6 cycles (-14%)

    That's a good time savings, but expensive in terms of longs.
  • JonnyMacJonnyMac Posts: 6,595
    edited 2020-03-16 - 23:03:43
    Silly question, perhaps, but can I abort a rep block by simply jumping out it?
      org
                            fltl    _scl
                            fltl    _sda
    
                            rep     #9, #8
                            drvl    _scl
                            waitx   tix
                            waitx   tix
                            fltl    _scl
                            waitx   tix
                            waitx   tix
                            testb   _sda                    wc
            if_c            jmp     #.done
    .done
      end
    
  • TonyB_TonyB_ Posts: 1,460
    edited 2020-03-16 - 23:28:43
    JonnyMac wrote: »
    Silly question, perhaps, but can I abort a rep block by simply jumping out it?
    Yes.
Sign In or Register to comment.