OpenCores
URL https://opencores.org/ocsvn/ion/ion/trunk

Subversion Repositories ion

[/] [ion/] [trunk/] [doc/] [src/] [tex/] [simulation.tex] - Rev 220

Go to most recent revision | Compare with Previous | Blame | View Log

\chapter{Logic Simulation}
\label{logic_simulation}
 
    The project has been simulated using Modelsim 6.3g. The test bench
    uses some features not present in earlier versions (namely library Signal 
    Spy) so if you use some other simulator or some earlier version of Modelsim, 
    see section on Modelsim dependencies (\ref{modelsim_dependencies}) below.\\
 
    In short, the simulation test bench is meant to run any of the code samples
    provided in directory /src, under a controlled environment, while logging
    the cpu state to a text log file.\\
 
    This log file can then be compared to a log file generated by a software
    simulator for the same code sample (see section 5.1). The software
    simulator is the 'golden model' against which the cpu is tested, so any
    difference between both log files means trouble.\\
 
    This method is far easier than building a fully automated test bench, and
    much more convenient and reliable than a visual inspection of the simulation
    state.\\
 
    In addition to the main log file, there is a console log file to which all 
    data written to the UART is logged (see section~\ref{uart_logging}).\\
 
    There are a few simulation test bench templates in the /src directory, which 
    are used by all the code samples.\\
    The only ones actually used are '/src/code\_rom\_template.vhdl' and 
    '/src/sim\_params\_template.vhdl'. The others
    are remnants of previous versions that will be removed ASAP.\\
 
    The template in file '/src/code\_rom\_template.vhdl' is filled with object
    code meant to be run from internal FPGA BRAM. This is how we load bootstrap
    code into our FPGA. The resulting file is '/vhdl/demo/code\_rom\_pkg.vhdl'
    and is used by both the simulation test bench and the synthesizable MCU.\\
 
    The template in file '/src/sim\_params\_template.vhdl' is filled with 
    simulation parameters (such as the simulation length, etc.) and the resulting 
    file is written as '/vhdl/tb/sim\_params\_pkg.vhdl'. This file is only used
    by the simulation test bench.
 
    All of this template filling is done by a python script (/src/bin2hdl.py) 
    which is invoked from the makefiles and explained in section xxx.\\
 
    Note that all code samples share the same vhdl files: you need to run the 
    makefile target 'sim' for the sample you want to simulate; that will 
    overwrite the two files mentioned above. So there's no vhdl file that is
    specific to a particular code sample.\\
 
    The actual test bench entity is at '/vhdl/tb/mips\_tb.vhdl' and is shared 
    by all the code samples.\\
 
    While the test benches and sample code are good enough to catch MOST errors
    in the full system (i.e. cache included) they don't help with diagnostic;
    once you know there's an error, and the approximate address where it's
    triggered (approximate because of the cache) you have to dig into the 
    simulation waveforms to find it. It's easier than it seems.\\
 
\section{Running the Simulation}
\label{running_the_simulation}
 
    A simulation script can be found at '/sim/mips\_tb.do'. This script will
    simulate the test bench entity in file '/vhdl/tb/mips\_tb.vhdl'.\\
 
    All the code samples are run with the same script.\\
 
    The test bench files mentioned in the previous section are automatically 
    generated for each of the sample programs. This is automatically done by the 
    sample code makefile,
    assuming you have a MIPS cross-toolchain in your computer (see section~\ref{code_samples}).\\
 
    For convenience, a pre-generated mips\_tb.vhdl is included so you can launch
    a simulation without having to install toolchains, etc. The code is that
    of the 'hello world' sample.\\
 
    I guess that if you are interested in this sort of stuff then you probably
    know more about Modelsim than I do. Yet, here's a step-by-step guide to
    simulating the 'hello world' sample:
 
\begin{enumerate}
    \item Run 'make hello\_sim' from directory '/src/hello'.
        This will compile the program sources, build the necessary binary object
        files and then create the two package files mentioned above.\\
        Read the makefile and comments in the python script '/src/bin2hdl.py'
        for details.\\
 
        ALTERNATIVELY, if you don't have a toolchain you can just skip this
        step and use the default vhdl files provided, which are those of the
        'hello world' sample.\\
 
    \item Within Modelsim, change directory to /syn. Modelsim will create its 
        stuff in this directory. This includes the
        log file, which by default will be '/syn/hw\_sim\_log.txt', and the 
        console log file '/syn/hw\_sim\_console.log'.\\
        (You could use any other directory, this is just a convenient place to
        put modelsim data out of the way. Just remember where the log files
        are.).
 
    \item Run script '/sim/mips\_tb.do' (menu tools-\textgreater tcl-\textgreater execute macro)
        The simulation will run to completion and print a message in Modelsim's 
        transcript window when it's done. You can open the console log file 
        to see the program output, in this case the 'Hello world' message.\\
 
\end{enumerate}
 
    The test bench terminates the simulation when:
 
\begin{enumerate}
 
    \item It detects two consecutive code fetches from the same address.
    \item The simulation timeout is reached.
 
\end{enumerate}
 
    Condition 1 is meant to detect single-instruction loops such as those
    commonly found after the invocation of the main() function in a C program. 
    This is a convenient way for the software to signal its termination.\\
 
    The timeout is one of the simulation parameters which is defined in
    the makefiles. It is arbitrarily fixed for each sample by trial and error 
    so that the program has time to execute. Change them if necessary.\\
 
\section{Simulation File Logging}
\label{sim_logging}
 
    The simulation test bench will log any of the following events:
 
    \begin{itemize}
    \item Changes in the register bank.
    \item Changes in registers HI and LO (implemented even if mul/div is not).
    \item Changes in registers EPC and SR.
    \item Data loads (any resulting register change is logged separately).
    \item Data stores.
    \end{itemize}
 
    Note that changes in other internal registers, including PC, are not logged.
    This means that for example a long chain of NOPs, or MOVEs that don't change
    register values, will not be seen in the log file. This is on purpose.\\
 
    Events are logged with the address of the instruction that triggered 
    the change. This holds true even for load instructions.\\
    Early versions of the project logged the address of the 
    preceding instruction -- it was confusing and I have fixed it.\\
 
    The simulation log file is stored by default in modelsim's working directory
    (see above). I don't provide any automated script to do the comparison, you
    should use whatever diff tool you like best.\\
 
\section{Log File Format}
\label{log_file_format}
 
    There is a text line for each of the following events:
 
    \begin{itemize}
    \item Register change
 
        "(pc) [reg\_num]=value"\\
 
        Where:
 
        \begin{tabular}{ l l }
            pc       & =\textgreater PC value (8-digit hex)\\
            reg\_num & =\textgreater Register index (2-digit hex), or any of {LO,HI,EP}\\
            value    & =\textgreater New register value (8-digit hex)\\
        \end{tabular}\\
 
 
    \item Write cycle (store)
 
        "(pc) [address] |mask|=value WR"\\
 
        Where:
 
        \begin{tabular}{ l l }
        pc      & =\textgreater PC value (8-digit hex)\\
        address & =\textgreater Write address\\
        mask    & =\textgreater Byte-enable mask (2-digit hex)\\
        value   & =\textgreater Write data\\
        \end{tabular}\\
 
        The PC value is the address of the instruction that caused the logged
        change, NOT the actual value of the PC at the time of the change. 
        This is so to make the hardware logger's life easier -- the SW simulator
        and the real HW don't work exactly the same when the cache starts 
        stalling the cpu (which the SW does not simulate) and the best reference
        point for all instructions is their own adddress.
 
        The mask will have a '1' at bits 3..0 for each byte write-enabled. MSB
        is bit 3, LSB is bit 0. Note that the data is big endian, so the MSB
        is actually the LOWER address. The upper nibble of the mask is always 0.
 
        The value will match the behavior of the ion cpu; the significant
        byte(s) will have the actual write data and the other bytes will not
        be relevant but will behave exactly as the real hardware (so that the
        logs are directly comparable).
 
        The WR at the end of the line is for visual reference only.
 
    \item Read cycle (load)
 
        "(pc) [address] \textless ** \textgreater =value RD"\\
 
        Where:
 
        \begin{tabular}{ l l }
        pc      & =\textgreater PC value (8-digit hex)\\
        address & =\textgreater Read address\\
        \textless ** \textgreater    & =\textgreater Padding (ignore)\\
        value   & =\textgreater Read data\\
        \end{tabular}\\
 
        Note that in the real machine, the data is read into the cpu one cycle
        after the address bus is output (because the memory is synchronous) so
        that the full read cycle spans 2 clock cycles (when proper interlocking
        is implemented, the load will overlap the next instruction; right now
        it just stalls the pipeline for 1 cycle). This is simplified in the log
        files for readability.
 
        Note that the size of the read (LH/LB/LW) instruction is not recorded:
        the CPU always reads 32-bit words.
 
        The RD at the end of the line is for visual reference only.
 
    \end{itemize}
 
 
    For example, these are lines 1153-1162 of the simulation log for the
    default 'hello world' test program:
 
                \begin{verbatim}
                ...
                (BFC009AC) [05]=20000000
                (BFC009B0) [20000020] <**>=00000003 RD
                (BFC009B0) [03]=00000003
                (BFC009B8) [03]=00000002
                (BFC009C0) [03]=20000000
                (BFC009C4) [20000000] |0F|=00000070 WR
                (BFC00E74) [12]=00000004
                (BFC00E78) [10]=BFC01048
                (BFC00E7C) [BFC01048] <**>=00000069 RD
                (BFC00E7C) [05]=00000069
                ...
                \end{verbatim}\\
 
    (NOTE: this example taken from revision 176, yours may vary)\\
 
    The read cycle at pc=0xbfc009b0 modifies register 0x03; that's why there 
    are two lines with the same pc value.\\
 
    The code that produced that log is this (from hello.lst):
 
                \begin{verbatim}
                ...
                bfc009ac:    3c052000     lui   a1,0x2000
                bfc009b0:    8ca30020     lw    v1,32(a1)
                bfc009b4:    00000000     nop
                bfc009b8:    30630002     andi  v1,v1,0x2
                bfc009bc:    1060fffc     beqz  v1,0xbfc009b0
                bfc009c0:    3c032000     lui   v1,0x2000
                bfc009c4:    ac620000     sw    v0,0(v1)
                bfc009c8:    03e00008     jr    ra
                bfc009cc:    00000000     nop
                ...
                bfc00e74:    26520001     addiu  s2,s2,1
                bfc00e78:    26100001     addiu  s0,s0,1
                bfc00e7c:    92050000     lbu    a1,0(s0)
                bfc00e80:    00000000     nop
                ...
                \end{verbatim}\\
 
    (Remember the register numbers: \$v0=0x02, \$v1=0x03, \$a1=0x05, \$s0=0x10, 
    \$s2=0x12)\\
 
    Note that, unlike previous versions of this project, all changes are logged
    with the address of the instruction that caused them.\\
 
    The log file format is hardcoded into vhdl package mips\_sim\_pkg
    and the software simulator C source that implement it. It will
    be probably modified as the project moves on so it is best if you verify
    all of this yourself with the project version you intend to use before 
    using this information.\\
 
    Note that the software simulation log and the modelsim log need not be the
    same size even if both CPUs behave identically; the one that spans a longer 
    simulated time will be longer.\\
    The point is that both need to be identical up to the last line of the
    shortest file.\\
 
\section{Console Output Logging}
\label{uart_logging}
 
    Every byte written to the UART TX register is logged (in ascii) to a text
    file which by default is '/syn/hw\_sim\_console.log'. Apart from the
    automatic insertion of a CR after every LF, the data is logged verbatim.\\
 
    Though the UART is included in the test bench, the actual UART operation is 
    bypassed: The test bench forces the 'tx ready' high so that the CPU never has
    to wait for a character to be transmitted. This is a simplification that 
    saves me the trouble to do a cycle-accurate simulation of the UART in the 
    software simulator.\\
 
    The UART input is not simulated at all, for simplicity. So, for example, the
    Adventure sample, which does read the console input, can't be properly 
    simulated past the first console input -- there is plenty of code to 
    simulate before that so this is no problem for the moment.\\
 
 
\section{Use of Modelsim Features}
\label{modelsim_dependencies}
 
    Apart from the format of the simulation scripts, which would be easy to port
    to any other simulation tool, the simulation test bench uses a feature of
    Modelsim 6.3 that is not even present in earlier versions -- SignalSpy.\\
 
    The test bench uses SignalSpy to examine internal cpu signals from the top
    entity, including the whole register bank. There is no other way to examine
    those signals in vhdl, unless you want to add them to the module interface.\\
 
    The test bench needs to access those signals in order to detect changes in
    the internal cpu state that should be logged. That is, it really needs to
    look at those signals if it is to be of any use.\\
 
    If you are using any other simulation tool, look for an alternative method
    to get those internal signals or just add them to the core interface. I
    would suggest adding a debug port of type record to mips\_cpu -- and hope the
    synthesis tool does not choke on it. Adding individual debug ports would be
    a PITA.\\
    I guess this is why Mentor people took the trouble to write SygnalSpy.\\
 
    I plan to move to Symphony EDA eventually, so I'll have to fix this.\\
 
    Using GHDL would be an option, except because it only supports vhdl. The
    project will use a SDRAM model in verilog for which I could not find a
    vhdl replacement. If the project is to be ported to GHDL (a very desirable 
    goal even if only because not everybody has access to Modelsim) this will 
    have to be worked around.\\
 
 

Go to most recent revision | Compare with Previous | Blame | View Log

powered by: WebSVN 2.1.0

© copyright 1999-2024 OpenCores.org, equivalent to Oliscience, all rights reserved. OpenCores®, registered trademark.