When we develop FPGA based solutions of course we make use of all the IP which we possibly can, as this accelerates the development. However, there are always cases where we need to generate custom IP cores using our hardware description language of choice.
Developing and verifying this IP blocks brings with it several challenges, which can make a project be a little more painful if it is not done correctly.
Of course one of the key things we need to think about when working with the FPGA is we need to first think about the the functionality we wish to implement, along with a following the correct development process.
For this example we will be using the following process
- Define Requirements
- Create Micro Architecture and Interfacing
- Write the RTL description of the module
- Develop a Bus Functional Model of the interfaces for the RTL
- Create a test bench for the verification
For this project we are going to create a simple QSPI Slave interface to AXI Stream this will have the following requirements.
These requirements are pretty simple to create the module. We could then integrate the module with a previous project to access the AXI4 network within a chip over QSPI.
Micro Architecture and InterfacingFrom this we need to create the micro architecture which means we need to understand how QSPI in serial memory mode works.
The waveforms for the READ4IO and PP4IO can be seen below
What is interesting about the waveforms is they first use a single line to IO0 to send in the command either 0x38 for write or 0xEB for a read operation.
Once the command has been sent the remainder of the transaction is performed using all IO lines.
The impacts on our architecture are that we need a 8 element shift register for the IO0 and two element shift registers for IO1-3. Once the command has been received we are able to use just the lower two elements of the IO0 registers.
Our micro architecture therefore needs to be able to do the following
- Detect a new transaction by identifying falling edge on CSn
- Detect a rising edge on the SCK once a new transaction is enabled
- Enable data to be shifted into IO0 register
- Once 8 bits of data are in IO0 register depending check for valid read or write command
- For Write enable IO1-3 along with IO0 - Every two SCLK output a byte of data on the AXIS Interface. Ensure IO drivers are Tristate in that state.
- For Read, during the dummy clock cycles, read from the AXIS interface
- For Read at the end of the dummy cycles output the data on IO0-IO3
- De assertion of CSn stops the transaction and resets state machines etc.
With the micro code architecture completed we are able to write the HDL, I will be using VHDL.
One of the key elements when designing the micro architecture is to define the interfaces in a sensible manner, especially internal to the FPGA to connect with other blocks. While it might be interesting to define your own please do NOT, select a industry standard such as AXI etc. This enables you to easily define the interfaces, ensure other developers are able to work easily with them when it comes to integration. This approach also eases verification of the interfaces as standard BFM can be used, it also over time builds up a useful repository of IP modules which can be reused across several projects reducing development time and recurring engineering costs making you much more competitive.
As such this block uses AXI Stream to send and receive data over the QSPI interface.
HDL DesignThe HDL of the design can be seen below in the attached file at the end of this project.
The entity and interfaces of the block are
The state machines implemented within the design
With the HDL completed the next step is to do the verification using our VHDL simulator.
Test BenchHow we verify the FPGA design is working is as expected is by creating a test bench. The test bench applies stimulus to the Unit Under Test (UUT) and observes the outputs of the UUT to ensure its behaviour is as expected.
While we use a VHDL Simulator to test the RTL and we can see the waveform of what is going off within the UUT.
We DO NOT want to use the waveform for the verification, we should check and log to a file or transcript that the behaviour of the UUT and if it is expected or unexpected behaviour.
We use the waveform to debug the test bench or the UUT if we see anomalies in the results expected.
To test our UUT we need to create a test bench this wraps around the UUT and provides the necessary stimulus and checking.
How we do this is critical, as what we want is the ability for the test bench to apply a number of different test cases to the UUT.
Each test case is a unique set of stimulus, which applies a specific test to the UUT to check for a specific outcome to that stimulus.
In simple examples the test case might just be to verify different operating modes for example, read and write operations.
For more complex systems these test cases can also be used to verify failure modes, boundary conditions and corner cases of the UUT. This would be the case in aerospace or space applications.
Creating these test cases can be complex and forms the most time consuming element of the verification process. Depending on the complexity of the UUT there may be a small number of test cases or many tens or even hundreds.
As such we want to separate the test bench from the test cases. We achieve this we use several different layers of abstraction including
- Test Case (s) - One or more test cases, which apply stimulus to the UUT via the Test Harness.
- Test Harness - This contains the mapping of the the UUT, along with connections for IP models e.g. RAMS, DDR Models etc
- UUT - The unit we are testing.
To achieve this in the easiest manner, we are going to make use of a significant number of Bus Functional Models (BFM) and Transactional Level Models (TLM). These will leverage in VHDL this will use procedures and libraries.
Of course there are frameworks such as UVM, UVVM and OSVVM which we can use for these applications however, as the purpose of this project is education we are going to do it from scratch.
Done correctly this enables the test cases to be a series of BFM / TLM level calls and associated checking. In this way the test case(s) looks much more like a script than a traditional HDL file.
This has several advantages as we are able to add additional test cases as needed without making the test bench one big complex file which might otherwise be the case. Long files make maintenance and understanding difficult going forward.
Flexibility like this is important as we need to be able to accommodate changes in the UUT functionality which ripple through to needing more test cases be created.
For this application we have the following
- Test Case - applies the stimulus - in this case we have one test case which contains a simple test.
- Test Bench - contains the test infrastructure of the test bench. This includes the UUT, AXIS FIFO, and clocking / reset circuits.
- UUT - QSPI IP core we are testing
- QSPI BFM Library - Library which contains a number of BFM which as as the QSPI master to perform reads and writes over the interface.
To test this project we can created a Vivado project which contains the HDL, and the test benches. We use Vivado to provide a AXIS FIFO in the test bench to allow looping back of the commands written, to verify the read back.
Ensure all of the VHDL files are set as VHDL-2008 otherwise there will several warning and errors generated.
If we look at the project hierarchy we will see the structure for the verification with the test case at the top level, the test bench below that which contains the AXIS FIFO and the UUT.
You cannot see the package as that is available only under the libraries view
Running the VHDL behavioural simulation will open the waveform view. Select the UUT and add the signals to the waveform.
Click on run all, the simulation should run to completion. You will see the activity on the IO signals and the internal signals. This is interesting to look at however, it does not as stated previously easily show the design is working as expected.
In this case the test bench was designed to be self checking, this will report in the transcript the success or failure of the test bench. What we expect is every value written we receive back the same value the test bench does this checking.
In a more complex system we would create a verification cross reference matrix which would show each for each requirement which test case was being used to demonstrate that requirement was correctly implemented.
For serious safety critical applications, a different engineer might be used to perform the verification and provide the results for certification.
Wrap UpIn this project we have the process outline for a development of an IP core for a FPGA. There are elements which have not been covered such as coding rules and static analysis etc however it provides a good introduction for those who wish to get started.
Comments