abstracting the client/server protocol

Discussion in 'VHDL' started by alb, Aug 19, 2013.

  1. alb

    alb Guest

    Hi everyone,

    I'm trying to implement a testbench with a client/server abstraction
    [1], but I have a little problem implementing a serial protocol with bit

    In my 'client' side I'd like to write/read to the serial link, but
    /someone/ should take care about bit stuffing. Considering that on my
    client I may need to do some write/read ops which are interleaved with
    other types of activities (say on different interfaces), I presume the
    'bit stuffing' mechanism should be entirely handled on the server side.

    The way I split the code is the following: a test case file
    (testcase.vhd) which instantiate the harness and issue all necessary
    commands to cover the intended functions, an harness file (harness.vhd)
    which contains the DUT instantiation and the server instantiation (with
    signal mapping and so on) with a clock process, a server file
    (server.vhd) with the translation between the 'virtual interface' used
    in the test case file and the physical interface of the DUT.

    In testcase.vhd I have one unique process which performs the necessary ops:

    main: process
    init(); -- set defaults state to all DUT input signals
    serial_set(..., ..., to_srv, fr_srv);
    serial_get(..., ..., to_srv, fr_srv);
    end process;

    I presume that only in my server I can perform the necessary operations
    *with* 'bit stuffing' and I should take care about 'bit stuffing' when
    there's no transaction on the serial interface (serial_set/serial_get).

    Am I too off road?
    What about having a testcase where the bit stuffing is either wrong or
    absent? Should I use a separate server for that?

    Thanks for any suggestion/comment,


    [1] see 'Writing Testbenches: functional verification of HDL Models' -
    2nd Edition, Janick Bergeron
    alb, Aug 19, 2013
    1. Advertisements

  2. alb

    Andy Guest

    This is the essence of transaction based modelling.

    Simply being able to abstract the transaction data from the bit stuffing isvery useful, especially if you want to layer constrained random transaction generation on top of that.

    If the interface supports it, you can also layer message-level transactionson top of byte-level transactions. If you do this, then any arbitration between multiple clients needs to occur at a point where messages from different clients do not get inter-mingled.

    The transaction data can include information about error injection (incorrectly stuffing the bits) as needed.

    You can also use a single bidirectional record port with resolved element types (including your own resolved types, since this is not synthesizable) for communications in both directions with the server, simplifying the send/receive subprogram calls.

    Andy, Aug 20, 2013
    1. Advertisements

  3. alb

    alb Guest

    Hi Andy,

    On 20/08/2013 16:05, Andy wrote:
    that is a personal goal and be sure I'll post something on the topic as
    soon as I get there!
    For the time being my model is extremely simple: one test case -> one
    client -> one file. My testcases will leverage the first one which is
    aiming to test the interface.

    I've seen a paper [1] presenting a similar approach with cases specified
    in different architectures of the same unit. I do not really see the
    advantage of doing this, but I do not have a strong opinion either.

    Having multiple clients all accessing the server may require an extra
    layer to ensure message integrity. But I do not see the point of having
    multiple clients interacting unless the 'message integrity' is a feature
    of the device under test and has to be verified.
    This is certainly part of the verification plan, even though I have no
    idea as of now on how to implement that. At the moment I have a
    write/read abstract procedures that initiate the transaction from the
    client and a 'dispatcher' process on the server side that calls the
    appropriate subprograms to interact with the DUT.

    Bit stuffing instead should be something that occurs independently from
    the client transmitting anything (I need to stuff a bit when no
    transaction is occurring as well). This means that a concurrent process
    should monitor the 'wire' and inject a '1' each N '0's. While I could
    potentially disrupt the bit stuffing during a transaction (with a
    special flag in the record port?), I still cannot see how I could do
    this when no transaction is occurring...
    well, indeed I do not favor too much the choice of having two separate
    record ports to handle the two directions. In the end the two records
    have great similarities and the main difference is simply the
    'direction' (to or from the server).

    Currently this is what I have:

    type kind is (RD, WR);

    type to_srv_ctrl is record -- abstract register to server
    it : integer range 0 to 20; -- interface type
    ad : integer range 0 to 65535; -- address
    dt : integer range 0 to 65535; -- data
    op : kind; -- read/write operation
    go : boolean; -- initiate operation
    end record;

    type fr_srv_ctrl is record -- abstract register from server
    ad : integer range 0 to 65535; -- address
    dt : integer; -- return data from server
    op : kind; -- read/write operation
    go : boolean; -- data ready
    end record;

    with the interface type 'it' I select an heterogeneous set of
    interfaces, and the 'go' flag is used to initiate the transaction. In
    principle I should be able to merge most of the elements.

    It is not too much overhead in the subprogram call interface (only two
    signals more), but it is annoying to have structures which do seem quite

    While these record types are extremely generic and can suit several type
    of transactions, they suffer from the possibility to add 'ad hoc
    features' in the transaction that are 'it' specific (like the
    bit-stuffing error injection).

    Any suggestion/comments?


    [1] Accelerating Verification Through
    Pre-Use of System-Level, Transaction Based Testbench Components
    alb, Aug 20, 2013
  4. alb

    Andy Guest


    The referenced paper is from Synthworks, which offers an EXCELLENT testbench & verification course. I have taken it, and I strongly recommend it. The course also covers the OSVVM library for constrained random stimulus generation and functional coverage models. Jim Lewis also provides a lot of useful packages and example code in addition to OSVVM for his students.

    One thing to consider about test cases is whether or not there may be interaction between test case scenarios. Simultaneous testing of multiple test cases helps verify expected or expose unexpected interactions.

    If you only have one or a few simulator licenses available, simultaneous testing is more efficient, since it often takes less time than two separate tests run sequentially. Unless you go to great lenghts to configure empty architectures for unused RTL entities in each specific test, simulation timecorrelates with the number of clock cycles pretty well, no matter how manythings the RTL model is doing in each of those clock cycles.

    On the other hand, if you have access to a sim farm with lots of licenses, running separate test cases on separate machines in parallel is faster.

    Depending on where your "test case code" is (at the top level architecture,or in a test controller entity at a lower level), using separate architectures for one common entity may or may not make that much difference.

    Creating resolved types for integers and booleans is pretty simple: pick a value that is "off" (does not interfere with the results), and drive that value (e.g. 0 or false) when you want to use it as an input. The resolution function can assert a warning (or worse) if more than one driver value is non-off (contention should not occur), and then it can simply return any non-off value in the received array.

    If I understand your situation, the BFM that turns transactions into bits on its "physical" interface is cranking out some bits whether or not a "realtransaction" is in progress. You could have a separate interface for an error injection transaction that simply tells the BFM to create specific errors while not generating bits related to real transactions. You can also addargument(s) to your real transaction procedures (defaulted to off) that cause the transaction to be executed with errors injected.

    Andy, Aug 21, 2013
  5. alb

    alb Guest

    Hi Andy, sorry for the late reply, I was offline for few days...

    On 21/08/2013 16:24, Andy wrote:
    I'm trying to build up a crew of people interested in the course here at
    Cern, in order to get an on-site course and maybe with a 'discount'
    considering that we are part of an international organization ;-)
    I see your point. For what concern the serial protocol in the OP each
    transaction is atomic and there's no 'memory' in the system to induce
    any interaction. OTOH there are other interfaces which are 'active'
    simultaneously and may have an impact when stimulated concurrently.

    If I need to have a simultaneous testing than I would need to rework the
    test case a little bit. Since currently I have one sequence of
    transactions in one process, I may think about using two processes which
    run concurrently and see what is the interaction.
    I agree and moreover I do not have a set of configurations to play with,
    therefore I would need to manually remove/add components along the way
    (too painful).
    At Cern we have several licenses and I'm planning to run simulations in
    batch mode through our batch system. Unfortunately I haven't spent
    enough time on this part yet and so far I'm running simulations one
    after the other one on a single license machine.
    As of now my 'test case' is at the top level. The harness instantiate
    the most of the common parts (DUT, BFM, clock generation) and the 'test
    case' instantiate the harness.
    I guess I didn't get it. What is 'worse than a warning'? If I get an
    error how can the simulation continue?
    that is correct, because of the 'bit-stuffing' property of the protocol.
    meaning the BFM should 'listen' continuously the interface to check if
    errors need to occur or not. This will require a concurrent interface to
    the BFM which adds errors on top of the one which creates 'good
    That is indeed yet another type of error, which acts at the level of
    packet format (wrong CRC, ...). I guess with the combination of the two
    I can expose many unwanted 'features' of the code. The main question
    here would be: for how long should I simulate? I guess here I'm stuck
    with building a coverage model (out of a non existing requirements'
    alb, Aug 26, 2013
  6. alb

    Andy Guest


    WRT "warnings or worse", most simulators can:

    1) print a message and keep going (e.g. note or warning)
    2) stop (breakpoint from which you can continue, e.g. error)
    3) quit (cannot continue e.g. failure)

    based on the severity level (NOTE, WARNING, ERROR, etc) of the assert or report statement. Most simulators allow changing the specified behavior for each severity level as well, but that becomes a global behavior change for all assertions/reports of that severity level.

    Also, assuming you have a package for your resolved types and their resolution functions, you can use a constant in that package for the severity usedby the assertions in your resolution functions. While debugging your testbench, you can set the constant to WARNING or ERROR, but later on, when running regression simulations "for the record" you can change that constant toFAILURE so that there is no possibility of continuing on from a resolutionfunction problem in a record run simulation.

    Andy, Aug 26, 2013
    1. Advertisements

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments (here). After that, you can post your question and our members will help you out.