Page 1 of 1

Configuring Buffer Sizes

Posted: Sat Jan 18, 2020 1:48 pm
by gray
I am struggling with an seemingly straightforward problem. I am re-implementing my channels for input and output. Channels are abstractions of actual devices, such as serials. For a buffered implementation I need, well, buffers. I would like to have different buffer sizes for the different serials in use. As ARRAY sizes need to be defined a compile time, different buffer sizes for different serials result in different TYPEs representing/abstracting the serials, and I have been struggling with handling those TYPEs within the channel procedures (see 'putBytes').

Below is the code exemplifying my woes, with the best solution I could come up with. Do I simply miss the obvious solution here?!

I would appreciate any comments and hints. Thanks!

Code: Select all

MODULE M40;

  IMPORT Error;

  CONST
    (* Buffer sizes for the two example serial devices *)
    S1TxBufSize = 128;
    S1RxBufSize = 32;
    S2TxBufSize = 64;
    S2RxBufSize = 8;
    
  TYPE
    (* Channel TYPE def *)
    (* From IO.mod *)
    Channel* = POINTER TO ChannelDesc;
    ChannelDesc* = RECORD
      PutBytes*: PROCEDURE(ch: Channel; data: ARRAY OF BYTE; n: INTEGER)
      (* ... *)
    END;
    
    (* these are the essential "ideal" TYPE defs, one per serial device *)
    (* simply two circular buffers plus the int prio *)
    (* --- not used in code below, just to give the idea --- *)
    SerialB1 = POINTER TO SerialB1Desc;
    SerialB1Desc = RECORD(ChannelDesc)
      irqPrio: INTEGER;
      tix, tox, rix, rox: INTEGER;
      txBbuf: ARRAY S1TxBufSize OF BYTE;
      rxBbuf: ARRAY S1RxBufSize OF BYTE
    END;
    
    SerialB2 = POINTER TO SerialB2Desc;
    SerialB2Desc = RECORD(ChannelDesc)
      irqPrio: INTEGER;
      tix, tox, rix, rox: INTEGER;
      txBbuf: ARRAY S2TxBufSize OF BYTE;
      rxBbuf: ARRAY S2RxBufSize OF BYTE
    END;
    (* --- *)
    
    (* this is the actual implementation *)
    (* abstract serial buffer *)
    SerialBuffer = POINTER TO SerialBufferDesc;
    SerialBufferDesc = RECORD END;
    
    (* the serial device abstraction *)
    SerialB* = POINTER TO SerialBDesc;
    SerialBDesc* = RECORD(ChannelDesc)
      irqPrio: INTEGER;
      tix, tox, rix, rox: INTEGER;
      buf: SerialBuffer
    END;
    
    (* the actual buffers for the two serial devices *)
    Serial1Buffer = POINTER TO Serial1BufferDesc;
    Serial1BufferDesc = RECORD(SerialBufferDesc)
      tx: ARRAY S1TxBufSize OF BYTE;
      rx: ARRAY S1RxBufSize OF BYTE
    END;
    
    Serial2Buffer = POINTER TO Serial2BufferDesc;
    Serial2BufferDesc = RECORD(SerialBufferDesc)
      tx: ARRAY S2TxBufSize OF BYTE;
      rx: ARRAY S2RxBufSize OF BYTE
    END;
    
  PROCEDURE putB(sb: SerialB; data: ARRAY OF BYTE; n: INTEGER; VAR buf: ARRAY OF BYTE);
    VAR i, next: INTEGER;
  BEGIN
    (* enter monitor to inhibit serial interrupts, based on sb.irqPrio *)
    FOR i := 0 TO n - 1 DO
      next := (sb.tix + 1) MOD LEN(buf);
      ASSERT(next # sb.tox, Error.BufferOverflow);
      buf[sb.tix] := data[i];
      sb.tix := next
    END
    (* exit monitor *)
  END putB;
        
  PROCEDURE putBytes(ch: Channel; data: ARRAY OF BYTE; n: INTEGER);
    VAR
      buf: SerialBuffer;
  BEGIN
    buf := ch(SerialB).buf;
    CASE buf OF
      Serial1Buffer: putB(ch(SerialB), data, n, buf.tx)
    | Serial2Buffer: putB(ch(SerialB), data, n, buf.tx)
    END
  END putBytes;
  
  PROCEDURE Init*(sb: SerialB; serialNo: INTEGER (* ... *));
    VAR s1b: Serial1Buffer; s2b: Serial2Buffer;
  BEGIN
    sb.PutBytes := putBytes;
    (* ... *)
    CASE serialNo OF
      1: NEW(s1b); sb.buf := s1b
    | 2: NEW(s2b); sb.buf := s2b
    END;
    (* ... *)
  END Init;

END M40.

Re: Configuring Buffer Sizes

Posted: Sun Jan 19, 2020 11:50 am
by cfbsoftware
You may well 'like' to have different buffer sizes and in theory it may be 'correct' but is it really worth all this effort?
How many different buffers are you expecting?
What are the minimum and maximum sizes?
How many instances of the serial channels would there be?

For your example I would simplify the declarations to:

Code: Select all

Buffer = RECORD 
  data: ARRAY MaxBufSize OF BYTE;
  size: INTEGER
END;
    
SerialB = POINTER TO SerialBDesc;
SerialBDesc = RECORD(ChannelDesc)
  irqPrio: INTEGER;
  tix, tox, rix, rox: INTEGER;
  txBuf, rxBuf: Buffer;
END;
i.e. allocate enough bytes in the array for the largest buffer and use the size element to indicate the actual size of the instance. You would just be wasting a few hundred bytes of data.

Re: Configuring Buffer Sizes

Posted: Mon Jan 20, 2020 6:46 am
by gray
You are right, I had come to the same conclusion. To actually see what the type complexity entails, down to the interrupt handler and so on, I implemented the module, and it results in roughly doubling the code size: about 4k for the "complex" version, about 2k for a version along the lines you suggest, and to compare, the non-buffered "busy-waiting" version is about 900 bytes (Cortex-M3). It was a fun mental exercise to explore the edges of the type system, but it's definitely not worth the price, also in terms of of maintenance.

Thanks!