Creating a multi port serial object

in Propeller 2
I have been working on a large hexapod robot for several years that currently uses 7 P1 processors (6 leg controllers and one master). After a while it become evident that the P1master was not fast enough to perform floating point trig calculations so I started to look for other options. Luckily the P2 is available so my quest has been to replicate or find replacements for the P1 Objects used in my code. The robot relies on serial communications between the master and the individual leg controllers for leg movements. At this point its using 2 copies of the P1 4 port serial communications object for a total of 8 separate serial lines (1 line for output from master to legs, 6 separate receive only lines from the legs to the master, and 1 line to the master via the prop plug for troubleshooting).
Looking over the serial options I thought JonnyMac's jm_serial.spin2 looked easiest to modify to allow multiple ports with its use of Smart Pins. I did a quick mod to allow 2 ports for testing. The code compiles with no errors but in testing I'm not getting any response at the PST terminal. As best I can tell the Smart Pins aren't being configured correctly in the addport routine but I'm not figuring out what I did wrong. I archived the demo (I used JonnyMac's serial demo also, commented out most of the code to reduce complexity until it works) and attached it along with a portion of the serial code.
Looking over the serial options I thought JonnyMac's jm_serial.spin2 looked easiest to modify to allow multiple ports with its use of Smart Pins. I did a quick mod to allow 2 ports for testing. The code compiles with no errors but in testing I'm not getting any response at the PST terminal. As best I can tell the Smart Pins aren't being configured correctly in the addport routine but I'm not figuring out what I did wrong. I archived the demo (I used JonnyMac's serial demo also, commented out most of the code to reduce complexity until it works) and attached it along with a portion of the serial code.
con { fixed io pins }
RX1 = 63 { I } ' programming / debug
TX1 = 62 { O }
FS_CS = 61 { O } ' flash storage
FS_CLK = 60 { O }
FS_MOSI = 59 { O }
FS_MISO = 58 { I }
con { pst formatting }
HOME = 1
CRSR_XY = 2
CRSR_LF = 3
CRSR_RT = 4
CRSR_UP = 5
CRSR_DN = 6
BELL = 7
BKSP = 8
TAB = 9
LF = 10
CLR_EOL = 11
CLR_DN = 12
CR = 13
CRSR_X = 14
CRSR_Y = 15
CLS = 16
obj
nstr : "jm_nstr" ' number-to-string
var
long rxp0
long txp0
long rxp1
long txp1
byte pbuf[80] ' padded strings
pub null()
'' This is not a top level object
pub addport(port1, rxpin, txpin, baud) | bitmode
'' Start simple serial coms on rxpin and txpin at baud, can use different rx/tx based on selected port
case port1
0: longmove(@rxp0, @rxpin, 2) ' save port0 pins
1: longmove(@rxp1, @rxpin, 2) ' save port1 pins
bitmode := muldiv64(clkfreq, $1_0000, baud) & $FFFFFC00 ' set bit timing
bitmode |= 7 ' set bits (8)
org
fltl rxpin ' configure rx smart pin
wrpin ##P_ASYNC_RX, rxpin
wxpin bitmode, rxpin
drvl rxpin
fltl txpin ' configure tx smart pin
wrpin ##(P_ASYNC_TX | P_OE), txpin
wxpin bitmode, txpin
drvl txpin
end
pub rxflush(port1)
'' Clear serial input
repeat
while (rxcheck(port1) >= 0)
pub rxcheck(port1) : rxbyte | check
'' Check for serial input
'' -- returns -1 if nothing available
rxbyte := -1
case port1
0:
check := pinr(rxp0)
if (check)
rxbyte := rdpin(rxp0) >> 24
1:
check := pinr(rxp1)
if (check)
rxbyte := rdpin(rxp1) >> 24
pub rxtime(port1, ms) : b | mstix, t
'' Wait ms milliseconds for a byte to be received
'' -- returns -1 if no byte received, $00..$FF if byte
mstix := clkfreq / 1000
t := getct()
repeat
until ((b := rxcheck(port1)) >= 0) or (((getct() - t) / mstix) > ms)
pub rx(port1) : rxbyte
'' Wait for serial input
'' -- blocks!
repeat
rxbyte := rxcheck(port1)
until (rxbyte >= 0)
pub tx(port1, b)
'' Emit byte
case port1
0:
wypin(txp0, b)
txflush(port1)
1:
wypin(txp1, b)
txflush(port1)
pub txn(port1, b, n)
'' Emit byte n times
repeat n
tx(port1, b)
pub txflush(port1) | check
'' Wait until last byte has finished
case port1
0:
repeat
check := pinr(txp0)
while (check == 0)
1:
repeat
check := pinr(txp1)
while (check == 0)
pub str(port1, p_str)
'' Emit z-string at p_str
repeat (strsize(p_str))
tx(port1, byte[p_str++])
Comments
var long cog ' cog flag/id long rxp[8] long txp[8] long rxhub[8] ' hub address of rxbuf long txhub[8] ' hub address of txbuf long rxhead[8] ' rx head index long rxtail[8] ' rx tail index long txhead[8] ' tx head index long txtail[8] ' tx tail index long rxmask[8] long txmask[8] long txdelay[8] ' ticks to transmit one byte byte rxbuf[BUF_SIZE] ' buffers byte txbuf[BUF_SIZE] byte pbuf[80] ' padded strings pub init() '' initialize code stop() pub addport(port, rxpin, txpin, mode, baud) : result | baudcfg, spmode '' add additional ports - call before start. miniumum 1 port required '' does not check for port duplication yet '' Start simple serial coms on rxpin and txpin at baud '' run addport at least once before calling start method '' -- port.... port number up to maximum (0-7 in test) '' -- rxpin... receive pin (-1 if not used) '' -- txpin... transmit pin (-1 if not used) '' -- mode.... %0xx1 = invert rx '' %0x1x = invert tx '' %01xx = open-drain/open-source tx if (rxpin == txpin) ' pin must be unique return false '' check if pin used by any other ports if cog OR (port > 1) 'port in range and no cog running abort if rxpin <> -1 long[@rxmask][port] := decod rxpin 'copy rxpin into rxmask if txpin <> -1 long[@txmask][port] := decod txpin longmove(long[@rxp][port], @rxpin, 1) 'save pins longmove(long[@txp][port], @txpin, 1) long[@txdelay][port] := clkfreq / baud * 11 ' tix to transmit one byte baudcfg := muldiv64(clkfreq, $1_0000, baud) & $FFFFFC00 ' set bit timing baudcfg |= (8-1) ' set bits (8) if (long[@rxp][port] >= 0) ' configure rx pin if used spmode := P_ASYNC_RX if (mode.[0]) spmode |= P_INVERT_IN pinstart(long[@rxp][port], spmode, baudcfg, 0) if (long[@txp][port] >= 0) ' configure tx pin if used spmode := P_ASYNC_TX | P_OE case mode.[2..1] %01 : spmode |= P_INVERT_OUTPUT %10 : spmode |= P_HIGH_FLOAT ' requires external pull-up %11 : spmode |= P_INVERT_OUTPUT | P_LOW_FLOAT ' requires external pull-down pinstart(long[@txp][port], spmode, baudcfg, 0) pub start() : result cog := coginit(COGEXEC_NEW, @uart_mgr, @rxp) + 1 ' start uart manager cog return cog
The SETQ works with the RDLONG to block transfer 4 parameters into rdx, tdx, p_rxbuf and p_txbuf. Does this same mechanism work for transferring in the array of pin values stored in rxp[8] from the addport routine? Another question is how to determine how many ports have actually been assigned via the addports routine.
I haven't found any examples of moving arrays into pasm so I don't know if its possible or just no one has needed to do it yet. I'll run some tests and see what happens also.
I still haven't completely sorted out how I'm going to do multi-port, but these are my thoughts:
-- true mode only
-- additional ports will use external buffers
-- keep a byte flag of ports that are opened
-- have a command mail box that can tell the pasm cog to add a port
I'm hoping that the current code will allow for global cog variables to be used so that there's not too much redundancy.
When I started on the P1 the object exchange was pretty good and the code objects I needed so there wasn't any need to learn pasm. Now the P2 is still pretty young and the extensions to allow in-line pasm make learning it a whole lot more attractive. My biggest stumbling block is the lack of in-depth documentation at this point. I tend to learn best from written examples vs studying someone else's code and figuring out their thought process. Knowledge is coming along but slowly.
var long cog ' cog flag/id long rxp ' rx smart pin long txp ' tx smart pin long rxhub ' hub address of rxbuf long txhub ' hub address of txbuf long rxhead ' rx head index long rxtail ' rx tail index long txhead ' tx head index long txtail ' tx tail index long txdelay ' ticks to transmit one byte
Making sure I understand ptra pointer for passing info to PASM. In the VAR section, longs rxp and on are in a specific order so they can be transferred into PASM codecog := coginit(COGEXEC_NEW, @uart_mgr, @rxp) + 1 ' start uart manager cog
That is done here with the pointer to the memory address of rxp in coginit.uart_mgr setq #4-1 ' get 4 parameters from hub rdlong rxd, ptra ... rxd res 1 ' receive pin txd res 1 ' transmit pin p_rxbuf res 1 ' pointer to rxbuf p_txbuf res 1 ' pointer to txbuf
Here the setq is used to get 4 parameters in hub memory starting at the address @rxp. Is the SetQ #4-1 just written like this for readability as it is starting with memory at location 0 through 3?rdlong is used to do a block copy of the 4 variables from hub memory over to cog memory. Since the reserved variables listed in the PASM section are in the same order as the VAR values, the copy keeps everything going to the right places.
uart_main testb rxd, #31 wc ' rx in use?
I'm not sure I fully understand testb. The description for testb is 'Test bit S[4:0] of D, write to C/Z. C/Z = D[S[4:0]].', I know this is a Verilog description but I haven't been able to decipher what that actually means. Rxd would have the output from the smartpin, is the smartpin output located in the 24-31 positions of the output long value. If that is the case then testb is checking for a stop bit from an incoming value?rx_serial testp rxd wc ' anything waiting? if_nc ret
Here is testp that tests the smartpin. The description is 'Test IN bit of pin D[5:0], write to C/Z. C/Z = IN[D[5:0]].', not sure what it means by the IN bit reference. If C is empty then the code returns to the last location on the stack which should be rx_serial.Thanks for any help.
When a smart pin is ready it will give you a 1 with testp. When this is the case you use rdpin to get the value. With an RX UART you have to shift that value right by 24 bits to realign for the user.
Thanks for any help!
* it only reserves space
* Does not set it to zero
* Beware of any following code - it is mostly at the end of a piece of pasm code so should be followed only by a separate piece of code - a new DAT, ORG, PUB, PRI, VAR
IIRC there is a P1 write up in the manual describing its use.
PTRA points to the hub memory location that is set when using COGINIT. In the case below it points to the memory location of global variable RXP.
cog := coginit(COGEXEC_NEW, @uart_mgr, @rxp) + 1
In the assembly code the SETQ and RDLONG then block copy the next 4 long values starting at global variable RXP into the memory location reserved for RXD and the next 3 sequential memory locations reserved in assembly.uart_mgr setq #4-1 ' get 4 parameters from hub rdlong rxd, ptra
RXD is single unassigned long reserved long value set at the end of the assembly code. So the effect of the process copies the values in the global variables into the 4 variables used by assembly.rxd res 1 ' receive pin txd res 1 ' transmit pin p_rxbuf res 1 ' pointer to rxbuf p_txbuf res 1 ' pointer to txbuf
As Cluso99 states the RES command reserves sequential long spaces but doesn’t assign a value (this is done by the rdlong command) and it needs to be at the end of the pasm commands otherwise bad things can happen.
Could the following sequence have been used instead of RES?
rxd Long 0 ' receive pin txd Long 0 ' transmit pin p_rxbuf Long 0 ' pointer to rxbuf p_txbuf Long 0 ' pointer to txbuf
Going further I saw this syntax used in the array rotation assembly coderxd Long 0[8]
Does this create an array of 8 elements all assigned a 0 value that are accessed like rxd[0] through rxd[7]?This leads me to the problem I am trying to figure out. If I have a global variable that is an array, how do get the array values into assembly? For instance I declare these global variables.
var Long rxp[8] Long txp[8]
When the cog is setup the same code is used as it just points to the start of the global variable addresscog := coginit(COGEXEC_NEW, @uart_mgr, @rxp) + 1
In assembly the code to get the 16 values into assembly variables would be like thisuart_mgr setq #16-1 ' get 16 parameters from hub rdlong rxd, ptra
rxd res 8 ' receive pin txd res 8 ' transmit pin
This is the simplified version of the code I have been using for testing but so far it doesn’t appear to work. If the syntax is correct then my problem is most likely my method I’m using to test the code. I asked a lot of questions here but I want to make sure I understand this data transfer process as it hasn’t always worked the way I thought it should.It is safer to use long 0 rather than res 1 unless you’re short on hub space.
var long portctr long rxp[7] ' rx smart pin long txp[7] ' tx smart pin long rxhub[7] ' hub address of rxbuf long txhub[7] ' hub address of txbuf long rxhead[7] ' rx head index long rxtail[7] ' rx tail index long txhead[7] ' tx head index long txtail[7] ' tx tail index
uart_mgr setq #33-1 ' get 33 parameters from hub rdlong Port, ptra
rxd Long 0[8] ' receive pin txd Long 0[8] ' transmit pin p_rxbuf Long 0[8] ' pointer to rxbuf p_txbuf Long 0[8] ' pointer to txbuf
I need to read the first indexed value in rxhead and used this coderdlong t2, ptra[33 + index]
I get a compiler error that the ptra index constant has to be between -32 and 31. Is there another method for access the hub data?Please can you put up a link to that old video of Jeff Martin.
It sounds like good value!
They implied there was going to be an advanced video that I haven't found yet. I didn't see this on the Parallax youtube channel, if there is another advanced video I haven't found it yet.
rxd Long 0[8] ' receive pin txd Long 0[8] ' transmit pin p_rxbuf Long 0[8] ' pointer to rxbuf p_txbuf Long 0[8] ' pointer to txbuf rhead Long 0[8] ‘ Pointer to rxhead rtail Long 0[8] ‘ Pointer to rxtail thead Long 0[8] ‘ Pointer to txhead ttail Long 0[8] ‘ Pointer to txtail
That seems wasteful of memory space, seems there should be a better method that I’m not aware of.RANDOM ACCESS INTERFACE Here are the random-access hub RAM read instructions: EEEE 1010110 CZI DDDDDDDDD SSSSSSSSS RDBYTE D,S/#/PTRx {WC/WZ/WCZ} EEEE 1010111 CZI DDDDDDDDD SSSSSSSSS RDWORD D,S/#/PTRx {WC/WZ/WCZ} EEEE 1011000 CZI DDDDDDDDD SSSSSSSSS RDLONG D,S/#/PTRx {WC/WZ/WCZ} For these instructions, the D operand is the register which will receive the data read from the hub. The S/#/PTRx operand supplies the hub address to read from. If WC is expressed, the MSB of the byte, word, or long read from the hub will be written to C. If WZ is expressed, Z will be set if the data read from the hub equaled zero, otherwise Z will be cleared. Here are the random-access hub RAM write instructions: EEEE 1100010 0LI DDDDDDDDD SSSSSSSSS WRBYTE D/#,S/#/PTRx EEEE 1100010 1LI DDDDDDDDD SSSSSSSSS WRWORD D/#,S/#/PTRx EEEE 1100011 0LI DDDDDDDDD SSSSSSSSS WRLONG D/#,S/#/PTRx EEEE 1010011 11I DDDDDDDDD SSSSSSSSS WMLONG D,S/#/PTRx For these instructions, the D/# operand supplies the data to be written to the hub. The S/#/PTRx operand supplies the hub address to write to. WMLONG writes longs, like WRLONG; however, it does not write any D byte fields whose data are $00. This is intended for things like sprite overlays, where $00 byte data represent transparent pixels. In the case of the 'S/#/PTRx' operand used by RDBYTE, RDWORD, RDLONG, WRBYTE, WRWORD, WRLONG, and WMLONG, there are five ways to express a hub address: $000..$1FF - register whose 20 LSBs will be used as the hub address #$00..$FF - 8-bit immediate hub address ##$00000..$FFFFF - 20-bit immediate hub address (invokes AUGS) PTRx {[index5]} - PTR expression with a 5-bit scaled index PTRx {[##index20]} - PTR expression with a 20-bit unscaled index (invokes AUGS) If AUGS is used to augment the #S value to 32 bits, the #S value will be interpreted differently: #%0AAAAAAAA - No AUGS, 8-bit immediate address #%1SUPNNNNN - No AUGS, PTR expression with a 5-bit scaled index ##%000000000000AAAAAAAAAAA_AAAAAAAAA - AUGS, 20-bit immediate address ##%000000001SUPNNNNNNNNNNN_NNNNNNNNN - AUGS, PTR expression with a 20-bit unscaled index PTRx expressions without AUGS: INDEX6 = -32..+31 for non-updating offsets INDEX = 0..16 for ++'s and --'s SCALE = 1 for RDBYTE/WRBYTE, 2 for RDWORD/WRWORD, 4 for RDLONG/WRLONG/WMLONG S = 0 for PTRA, 1 for PTRB U = 0 to keep PTRx same, 1 to update PTRx (PTRx += INDEX*SCALE) P = 0 to use PTRx + INDEX*SCALE, 1 to use PTRx (post-modify) IIIIII = INDEX6, uses %100000..%111111 for -32..-1 and %000000..%011111 for 0..31 NNNNN = INDEX, uses %00001..%01111 for 1..15 and %00000 for 16 nnnnn = -INDEX, uses %10000..%11111 for -16..-1 1SUPNNNNN PTR expression ------------------------------------------------------------------------------ 100000000 PTRA 'use PTRA 110000000 PTRB 'use PTRB 100IIIIII PTRA[INDEX6] 'use PTRA + INDEX6*SCALE 110IIIIII PTRB[INDEX6] 'use PTRB + INDEX6*SCALE 101100001 PTRA++ 'use PTRA, PTRA += SCALE 111100001 PTRB++ 'use PTRB, PTRB += SCALE 101111111 PTRA-- 'use PTRA, PTRA -= SCALE 111111111 PTRB-- 'use PTRB, PTRB -= SCALE 101000001 ++PTRA 'use PTRA + SCALE, PTRA += SCALE 111000001 ++PTRB 'use PTRB + SCALE, PTRB += SCALE 101011111 --PTRA 'use PTRA - SCALE, PTRA -= SCALE 111011111 --PTRB 'use PTRB - SCALE, PTRB -= SCALE 1011NNNNN PTRA++[INDEX] 'use PTRA, PTRA += INDEX*SCALE 1111NNNNN PTRB++[INDEX] 'use PTRB, PTRB += INDEX*SCALE 1011nnnnn PTRA--[INDEX] 'use PTRA, PTRA -= INDEX*SCALE 1111nnnnn PTRB--[INDEX] 'use PTRB, PTRB -= INDEX*SCALE 1010NNNNN ++PTRA[INDEX] 'use PTRA + INDEX*SCALE, PTRA += INDEX*SCALE 1110NNNNN ++PTRB[INDEX] 'use PTRB + INDEX*SCALE, PTRB += INDEX*SCALE 1010nnnnn --PTRA[INDEX] 'use PTRA - INDEX*SCALE, PTRA -= INDEX*SCALE 1110nnnnn --PTRB[INDEX] 'use PTRB - INDEX*SCALE, PTRB -= INDEX*SCALE Examples: Read byte at PTRA into D 1111 1010110 001 DDDDDDDDD 100000000 RDBYTE D,PTRA Write lower word in D to PTRB - 7*2 1111 1100010 101 DDDDDDDDD 110111001 WRWORD D,PTRB[-7] Write long value 10 at PTRB, PTRB += 1*4 1111 1100011 011 000001010 111100001 WRLONG #10,PTRB++ Read word at PTRA into D, PTRA -= 1*2 1111 1010111 001 DDDDDDDDD 101111111 RDWORD D,PTRA-- Write lower byte in D at PTRA - 1*1, PTRA -= 1*1 1111 1100010 001 DDDDDDDDD 101011111 WRBYTE D,--PTRA Read long at PTRB + 10*4 into D, PTRB += 10*4 1111 1011000 001 DDDDDDDDD 111001010 RDLONG D,++PTRB[10] Write lower byte in D to PTRA, PTRA += 15*1 1111 1100010 001 DDDDDDDDD 101101111 WRBYTE D,PTRA++[15] Read word at PTRB into D, PTRB += 16*2 1111 1010111 001 DDDDDDDDD 111100000 RDWORD D,PTRB++[16]
So the error I got from trying to use ptra[56] is due to operating under INDEX5: PTRx {[index5]} - PTR expression with a 5-bit scaled index. This limits the ptra[-32 to 31] in this mode. There is another mode that allows a 20 bit index: PTRx {[##index20]} - PTR expression with a 20-bit unscaled index (invokes AUGS).PTRx expressions with AUGS: If "##" is used before the index value in a PTRx expression, the assembler will automatically insert an AUGS instruction and assemble the 20-bit index instruction pair: RDBYTE D,++PTRB[##$12345] ...becomes... 1111 1111000 000 000111000 010010001 AUGS #$00E12345 1111 1010110 001 DDDDDDDDD 101000101 RDBYTE D,#$00E12345 & $1FF
It appears that if I use the INDEX20 mode then I can use the following command for larger index numbers. Is my understanding correct for this use of INDEX20?rdlong t2, ptra[##(33 + index)]
rd_hub mov hubpntr, ptra mov t1, hubindex shl t1, #2 add hubpntr, t1 _ret_ rdlong hubdata, hubpntr wr_hub mov hubpntr, ptra mov t1, hubindex shl t1, #2 add hubpntr, t1 _ret_ wrlong hubdata, hubpntr
I will try out your code and see how it goes, it looks pretty good. I’m still curious about the ##INDEX20 operation mode, haven’t run across any examples of it in my searches except for in the P2 silicone documentation I posted. This assembly coding exercise has been good in making me study the documents much more closely and search for examples of each code element, learning a lot more about P2 assembly this way even if it is a slow process!
This should work providing that index is a constant. Which may not be what you need. If index is a variable, you'll need to do something else, for example adding some appropriate multiple of index to ptra before using ptra (and then subtracting it back to restore ptra after you're done).
rd_hub mov hubpntr, ptra mov t1, hubindex shl t1, #2 add hubpntr, t1 _ret_ rdlong hubdata, hubpntr wr_hub mov hubpntr, ptra mov t1, hubindex shl t1, #2 add hubpntr, t1 _ret_ wrlong hubdata, hubpntr
Is shl T1, #2 used to line up to the next long by multiplying the index by 4?Yes, it's assuming that you want to read an array of longs, so the indexes refer to longs instead of bytes. That's why index has to be multiplied.
You'll also need to offset ptra by the appropriate amount for the variables you're using. It looks like rxd has offset 0 and 8 longs, so the txd array will start at offset 32 (8*4), p_rxbuf at offset 64, and so on. so you'd do something like:
con RXD_OFFSET = 0 TXD_OFFSET = 32 RXBUF_OFFSET = 64 ' ... and so on ' access txd array shl index, #2 ' multiply index by 4 add ptra, index ' offset ptra by index*4 rdlong t2, ptra[##TXD_OFFSET] ' read data from hub sub ptra, index ' restore ptra shr index, #2 ' restore index (only if necessary)
This could be made a bit faster if you "inverted" things and used an array of structures rather than a set of arrays. That is, instead of declaring
long rxd[8] long txd[8]
and so on, for 9 different variables (or however many you need for each serial port) you did:con struct_size = 9 ' number of variables per serial port rxd_offset = 0 ' first variable is rxd txd_offset = 1 ' second variable is txd rptr_offset = 2 ' third is receive pointer ... ' and so on var long serdata[struct_size * num_ports] dat ' fragment to show how to access tx pin mov t1, index mul t1, #struct_size*4 ' adjust to start of data for port "index" add ptra, t1 ' fixup ptra rdlong data, ptra[txd_offset] ' now data holds the txd value sub ptra, t1 ' restore ptra
If you're reading many variables all together the "add ptra, t1" / "sub ptra, t1" pairs can be combined, i.e. you just have to do one "fixup ptra" before reading the variables and one "restore ptra" at the end once all the variables are read.
Your last code example is great in that the array structure uses a lot of memory setting up for 8 ports even if you are only using 1 or 2 ports. This reduces the amount of space needed to only what is required for the actual number of ports.
For single instances, the large offset will be more efficient in both code space and execution time, but for accessing blocks using a single ptra manipulation you quickly reverse that.
Another advantage of using the structure approach could be to perform a single ptra manipulation around a burst read of only the parameter block for the port to be operated on.
@DiverBob, As you have only buffer address, head, and tail pointers per buffer you can only have fixed size buffers, which might introduce risks of frame loss on fast ports, or wasting hubram for slower ports.
The structure could grow to two more longs per port to give a buffer max address for each buffer, allowing per port buffer sizes and even different sized buffers for rx and tx functions.
If the head and tail longs contain the absolute hubram addresses then there's no pointer maths required to access the buffer, and the wrapping tests simply compare the head and tail pointers against the buffer max address, wrapping to the buffer start address. All of this can be performed against data in cogram with the buffer pointer section of the parameter block being written back to hubram before switching to a different port.
Advantages:
No pointer maths
Parameters sit at fixed locations in cogram, so code remains static
Variable buffer sizes enabled by clean code
Only 10 long parameter block loaded per port, so constant parameter loading time per port and smaller cogram footprint.
Simpler code (no indexing) meaning smaller, faster code in cogram.
Disadvantage:
Parameters must be reloaded every time you need to switch to support a different port; although this is likely the case anyway.