UCSBarchlab / PyRTL

A collection of classes providing simple hardware specification, simulation, tracing, and testing suitable for teaching and research. Simplicity, usability, clarity, and extensibility are the overarching goals, rather than performance or optimization.
http://ucsbarchlab.github.io/PyRTL
BSD 3-Clause "New" or "Revised" License
253 stars 76 forks source link

Explicit reset signal? #445

Closed cbatten closed 3 months ago

cbatten commented 3 months ago

Hi PyRTL team! Long time lurker, first time poster ... Is there a way to get access to an explicit reset signal at the toplevel of a PyRTL component?

Let's say I have this Verilog module:

module TopModule
(
  input  logic       clk,
  input  logic       reset,
  input  logic [7:0] in_,
  output logic [7:0] out
);

  // Sequential logic

  logic [7:0] reg_out;

  always @( posedge clk ) begin
    if ( reset )
      reg_out <= 0;
    else
      reg_out <= in_;
  end

  // Combinational logic

  logic [7:0] temp_wire;

  always @(*) begin
    temp_wire = reg_out + 1;
  end

  // Structural connections

  assign out = temp_wire;

endmodule

I want to model something equivalent in PyRTL so I came up with this:

from pyrtl import *

def TopModule( in_ ):

  # Declare outputs

  out = WireVector(8)

  # Sequential logic

  reg_out = Register( 8, reset_value=0 )
  reg_out.next <<= in_

  # Combinational logic

  temp_wire = WireVector(8)
  temp_wire <<= reg_out + 1

  # Structural connections

  out <<= temp_wire

  # Return outputs

  return out

I can simulate this fine and reg_out is correctly reset to zero at the beginning of the simulation, but how do I toggle the reset signal in the middle of the simulation? Is there a way to explicitly write the reset port in PyRTL?

timsherwood commented 3 months ago

Hi! Great to hear from you :)

In PyRTL reset exists outside of the signal set available for direct control by the user (mostly because we wanted to be clearly separate it from logic -- both so as to not confuse the tools but also so beginners are not tempted to be "creative" with reset which leads to all sorts of problems). If you assume synchronous reset and that every register is properly coded with a reset (which is the verilog that pyrtl should generate by default) there are couple ways to handle this in pyrtl simulation.

1) You can just do multiple simulations (which is usually what we do). A refactored version of the above code with simulation might look like the following:

import pyrtl

def TopModule( top_in, top_out ):
    assert top_in.bitwidth == 8, 'top only works for bitwidth 8'
    reg_out = pyrtl.Register(8)
    reg_out.next <<= top_in
    top_out <<= reg_out + 1

top_in = pyrtl.Input(8,'top_in')
top_out = pyrtl.Output(8,'top_out')
TopModule( top_in, top_out )

sim1 = pyrtl.Simulation()
sim1.step_multiple({'top_in': [0,1,5,8,8,9,10]})

sim2 = pyrtl.Simulation()
sim2.step_multiple({'top_in': [4,3,2]})

sim1.tracer.render_trace()
sim2.tracer.render_trace()

If you really want to simulate only the registers getting reset leaving memory blocks in place (as should be the case for FPGA with synchronous reset) it is pretty easy to just reach into the simulation and set the register next values to anything that you want. Here is some code that does that, using the default register values assigned to the registers along with the simulation:

import pyrtl

def TopModule( top_in, top_out ):
    assert top_in.bitwidth == 8, 'top only works for bitwidth 8'
    reg_out = pyrtl.Register(8)
    reg_out.next <<= top_in
    top_out <<= reg_out + 1

top_in = pyrtl.Input(8,'top_in')
top_out = pyrtl.Output(8,'top_out')
TopModule( top_in, top_out )

def reset_registers(simulation):
    block = simulation.block
    for reg in block.wirevector_subset(pyrtl.Register):
        reset_value = reg.reset_value if reg.reset_value else 0
        simulation.regvalue[reg] = reset_value

sim = pyrtl.Simulation()
sim.step_multiple({'top_in': [0,1,5,8,8,9,10]})
reset_registers(sim)
sim.step_multiple({'top_in': [11,12,13]})
sim.tracer.render_trace()

I have not thought deeply about the code above and if it covers all the cases it needs to (it should get all the registers and reset the in the same way as the verilog code, but it might mess with assertions or the jit-based simulators). Perhaps it is time to revisit reset semantics -- if there was a use case that multiple simulation does not cover easily we can certainly consider adding it.

cbatten commented 3 months ago

OK ... this is kind of what I thought. I am experimenting with a variety of different Python-embedded DSLs and trying to create a unified way to test components each written in a different Python-embedded DSL. Some of my tests test reset behavior (i.e., start the simulation, apply some test inputs, verify the test outputs, reset the component, apply some inputs, verify the outputs) ... I think for now I will just have to skip these tests when using PyRTL ... thanks for the quick feedback!