Simulation
Debugging
This guide describes some features that are useful for debugging a failing simulation or when running an interactive ad-hoc simulation from the console.
All major step comments in pattern source code will be simulation time-stamped and output to the console as shown below:
ss "Test basic 2-pin match loop"
# ...
ss "Test a block match loop"
# ...
The above will produce this output where the simulation time when each operation occurs is given in ns:
[INFO] 6.710[0.000] || 991700 ns: ===========================================================
[INFO] 6.710[0.001] || 991700 ns: Test basic 2-pin match loop
[INFO] 6.711[0.001] || 991700 ns: ===========================================================
[INFO] 6.711[0.001] || 1207500 ns: ===========================================================
[INFO] 6.712[0.000] || 1207500 ns: Test a block match loop
[INFO] 6.712[0.000] || 1207500 ns: ===========================================================
All log output created by application code will also be simulation time-stamped and synchronized to simulator progress:
Origen.log.info "Something important is happening!"
[INFO] 6.712[0.000] || 24210200 ns: Something important is happening!
Note that applications should avoid the use of puts
to output debug or status information to the console
because that will occur immediately and will not be time-stamped or synchronized with the simulator.
Additionally, the Origen testbench contains a register that can be used to mark events like this:
tester.marker = 0x1234
You can then view signal origen.debug.marker
in a wave viewer and search for the appropriate value to
locate the specific point in time when the mark was applied.
When generating for non-simulation tester targets that statement will do nothing and it can be safely left in production code.
The following information is logged whenever a pin mis-compare occurs within a register read transaction:
- The path (name) of the register
- Expected data
- Received (actual) data - requires support from the physical driver, see below
- Application stack trace to identify where in the application code the read originated
Here is an example:
ss "Now try and read and write a register"
dut.cmd.write!(0x1234_5678)
dut.cmd.read!(0x1234_5078) # Note that we just wrote ..567.. but are now expecting ..507..
[INFO] 4.098[0.000] || 86500 ns: ======================================================================
[INFO] 4.098[0.001] || 86500 ns: Now try and read and write a register
[INFO] 4.099[0.001] || 86500 ns: ======================================================================
[ERROR] 4.100[0.001] || 117400 ns: Miscompare on pin tdo, expected 0 received 1
[ERROR] 4.101[0.001] || 117600 ns: Miscompare on pin tdo, expected 0 received 1
[ERROR] 4.102[0.001] || 129100 ns: Errors occurred reading register cmd:
[ERROR] 4.102[0.001] || 129100 ns: cmd.d: expected 12345078 received 12345678
[ERROR] 4.103[0.001] || 129100 ns:
[ERROR] 4.104[0.001] || 129100 ns: /home/stephen/Code/github/origen_sim/pattern/test.rb:39:in `block in <top (required)>'
[ERROR] 4.110[0.006] || 129100 ns: /home/stephen/Code/github/origen_sim/pattern/test.rb:1:in `<top (required)>'
The received data resolution does depend on the physical protocol driver supplying meta-data when creating pin assertions (reads) that correspond to register bits, like this:
pin(:tdo).assert(bit.data, meta: { position: bit.position })
For reference, here is the update that was made to the JTAG driver to support this feature - OrigenJTAG - Add meta data for OrigenSim
If the driver has not provided this then a warning will be output and no received data will be given.
In some cases the protocol being used may generate failed compares that contain bit position meta data that does not map to the register being read. In this case a generic message will be displayed with the bit position that failed along with the register that was being read. It may be desirable to have application specific code interpret these bits (for example if it is a status bit). Origen provides a hook for this. Below is an example of how to implement a custom interpreter.
if tester.sim?
tester.out_of_bounds_handler = proc do |position, received, expected, reg|
Origen.log.error "Got data ouside of #{reg.name} during the transaction, bit[#{position}]: expected #{expected}, received #{received}"
Origen.log.error "ECC error during read of #{reg.path}.#{reg.name}" if position == 39
end
end
This feature will automatically be enabled for any reads launched via the register object itself, e.g.
my_reg.read!
, but not for reads launched by calling the read_register
method manually,
e.g. dut.read_register(my_reg)
.
If your application has a tendency to do the latter, then the following modification can be made to your
read_register
method to make OrigenSim aware of such transactions:
def read_register(reg_or_value, options = {})
# Make OrigenSim aware of all transactions to enable failed transaction reporting
tester.read_register(reg_or_value, options) do
# Existing implementation here
end
end
This feature will also work in the case of the read object being a value rather than a register object.
The execution of an Origen simulation is fully controlled by Origen/Ruby, this means that if you insert a regular Ruby debugger breakpoint into your application code then you can step through the simulation in real time.
When a simulation is running most of the communication is one-way, Origen tells the simulator what to do, and for performance reasons there is no handshake between Origen and the simulator at every instruction. Instead, Origen fires off instructions into a buffer, the simulator executes them as fast as it can, and then Origen periodically waits for the simulator to catch up if it is running too far ahead.
If you have entered a breakpoint and you suspect that the simulator may be still catching up, you can run:
tester.sync_up
When that method returns you are guaranteed that the simulator is at the same point.
Note that most of the time you will not need to do this manually since Origen will automatically sync up for any operation that involves reading data from the simulation, e.g. peeking or reading registers.
If you have a wave viewer open during the debug session it may still look like the simulation is running behind, or the wave viewer may appear to hang if you have tried to refresh it. This is because the simulator is buffering output that has yet to be written to the wave dump.
You can force it to flush the buffer and update the wave viewer by running:
tester.flush
Note that flushing will internally call a sync_up
so you don’t have to do both of these manually.
The methods tester.simulator.peek
and tester.simulator.poke
are available for reading and writing values
to internal DUT nets respectively.
See the section on Direct DUT Manipulation for more details on these.
Referencing a register from the console will show you what Origen thinks the register currently contains:
dut.my_block.my_reg
=>
0x10008000 - :my_reg
===============================================================================================================
│ 15 │ 14 │ 13 │ 12 │ 11 │ 10 │ 9 │ 8 │
│ d[15:8] │
│ 0x0 │
├─────────────┼─────────────┼─────────────┼─────────────┼─────────────┼─────────────┼─────────────┼─────────────┤
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
│ d[7:0] │
│ 0x0 │
└─────────────┴─────────────┴─────────────┴─────────────┴─────────────┴─────────────┴─────────────┴─────────────┘
However, this may not be what the simulation currently reflects. To see what the simulation holds, call sync
on the register:
dut.my_block.my_reg.sync
=>
0x10008000 - :my_reg
===============================================================================================================
│ 15 │ 14 │ 13 │ 12 │ 11 │ 10 │ 9 │ 8 │
│ d[15:8] │
│ 0x8E │
├─────────────┼─────────────┼─────────────┼─────────────┼─────────────┼─────────────┼─────────────┼─────────────┤
│ 7 │ 6 │ 5 │ 4 │ 3 │ 2 │ 1 │ 0 │
│ d[7:0] │
│ 0xFF │
└─────────────┴─────────────┴─────────────┴─────────────┴─────────────┴─────────────┴─────────────┴─────────────┘
A convenience API exists for this by appending !
to the register name:
dut.my_block.my_reg!
Note that this works by firing off a conventional read_register
request to your DUT model/controller.
That means that it should work for breakpoints in the majority of application code, however if you have stopped it or stepped into a place in low-level code, such as in the middle of a prior transaction, then this feature may not work.
If you have just written to a register, it may kick off some operation within the DUT and time (clock cycles) will be required before you will be able to observe the response.
It is important to understand that the simulator is also paused during a breakpoint and therefore simulation time is not continuing to run while you are at the console.
Simulation time can be advanced by calling the usual tester.wait
API, however these convenience APIs exist for advancing
time during a debugger breakpoint (and they can also be used in source code if you wish):
10.cycles
10.ns!
10.us!
10.ms!
10.s!
Note that 10 is just used as an example here, you can apply any number that you want.
You can query the current error count by running:
tester.simulator.error_count # => 0