def elaborate(self, _: Platform) -> Module: """Implements the logic of a transparent latch.""" m = Module() internal_reg = Signal(self.size, reset=0, reset_less=True) # Local clock domain so we can clock data into the # internal memory on the negative edge of le. le_clk = ClockDomain("le_clk", clk_edge="neg", local=True) m.domains.le_clk = le_clk le_clk.clk = self.le m.d.le_clk += internal_reg.eq(self.data_in) m.d.comb += self.data_out.eq(Mux(self.n_oe, 0, internal_reg)) with m.If(self.le & ~self.n_oe): m.d.comb += self.data_out.eq(self.data_in) return m
def elaborate(self, _: Platform) -> Module: """Implements the logic of an asynchronous memory. Essentially implements the wr-controlled write cycle, where the oe signal doesn't matter. We can't really implement the delays involved, so be safe with your signals :) """ m = Module() # Local clock domain so we can clock data into the memory # on the positive edge of n_wr. wr_clk = ClockDomain("wr_clk", local=True) m.domains.wr_clk = wr_clk wr_clk.clk = self.n_wr m.d.comb += self.data_out.eq(0) with m.If(~self.n_oe & self.n_wr): m.d.comb += self.data_out.eq(self._mem[self.addr]) m.d.wr_clk += self._mem[self.addr].eq(self.data_in) return m
def formal(cls) -> Tuple[Module, List[Signal]]: """Formal verification for the async memory. Note that you MUST have multiclock on in the sby file, because there is more than one clock in the system -- the default formal clock and the local clock inside the memory. """ m = Module() m.submodules.mem = mem = AsyncMemory(width=32, addr_lines=5) # Assume "good practices": # * n_oe and n_wr are never simultaneously 0, and any changes # are separated by at least a cycle to allow buffers to turn off. # * memory address remains stable throughout a write cycle, and # is also stable just before a write cycle. m.d.comb += Assume(mem.n_oe | mem.n_wr) # Paren placement is very important! While Python logical operators # and, or have lower precedence than ==, the bitwise operators # &, |, ^ have *higher* precedence. It can be confusing when it looks # like you're writing a boolean expression, but you're actually writing # a bitwise expression. with m.If(Fell(mem.n_oe)): m.d.comb += Assume((mem.n_wr == 1) & (Past(mem.n_wr) == 1)) with m.If(Fell(mem.n_wr)): m.d.comb += Assume((mem.n_oe == 1) & (Past(mem.n_oe) == 1)) with m.If(Rose(mem.n_wr) | (mem.n_wr == 0)): m.d.comb += Assume(Stable(mem.addr)) m.d.comb += Cover((mem.data_out == 0xAAAAAAAA) & (Past(mem.data_out) == 0xBBBBBBBB)) # Make sure that when the output is disabled, the output is zero, and # when enabled, it's whatever we're pointing at in memory. with m.If(mem.n_oe == 1): m.d.comb += Assert(mem.data_out == 0) with m.Else(): m.d.comb += Assert(mem.data_out == mem._mem[mem.addr]) # If we just wrote data, make sure that cell that we pointed at # for writing now contains the data we wrote. with m.If(Rose(mem.n_wr)): m.d.comb += Assert(mem._mem[Past(mem.addr)] == Past(mem.data_in)) # Pick an address, any address. check_addr = AnyConst(5) # We assert that unless that address is written, its data will not # change. To know when we've written the data, we have to create # a clock domain to let us save the data when written. saved_data_clk = ClockDomain("saved_data_clk") m.domains.saved_data_clk = saved_data_clk saved_data_clk.clk = mem.n_wr saved_data = Signal(32) with m.If(mem.addr == check_addr): m.d.saved_data_clk += saved_data.eq(mem.data_in) with m.If(Initial()): m.d.comb += Assume(saved_data == mem._mem[check_addr]) m.d.comb += Assume(mem.n_wr == 1) with m.Else(): m.d.comb += Assert(saved_data == mem._mem[check_addr]) return m, [mem.addr, mem.data_in, mem.n_wr, mem.n_oe]
def formal(cls) -> Tuple[Module, List[Signal]]: """Formal verification for the register card.""" m = Module() ph1 = ClockDomain("ph1") ph2 = ClockDomain("ph2") regs = RegCard() m.domains += [ph1, ph2] m.submodules += regs # Generate the ph1 and ph2 clocks. cycle_count = Signal(8, reset=0, reset_less=True) phase_count = Signal(3, reset=0, reset_less=True) m.d.sync += phase_count.eq(phase_count + 1) with m.Switch(phase_count): with m.Case(0, 1, 2): m.d.comb += ph1.clk.eq(1) with m.Default(): m.d.comb += ph1.clk.eq(0) with m.Switch(phase_count): with m.Case(1, 4): m.d.comb += ph2.clk.eq(0) with m.Default(): m.d.comb += ph2.clk.eq(1) with m.If(phase_count == 5): m.d.sync += phase_count.eq(0) m.d.sync += cycle_count.eq(cycle_count + 1) # This is how we expect to use the card. with m.If(phase_count > 0): m.d.comb += [ Assume(Stable(regs.reg_x)), Assume(Stable(regs.reg_y)), Assume(Stable(regs.reg_z)), Assume(Stable(regs.reg_page)), Assume(Stable(regs.reg_to_x)), Assume(Stable(regs.reg_to_y)), Assume(Stable(regs.data_z)), ] # Figure out how to get to the point where X and Y are nonzero and different. m.d.comb += Cover((regs.data_x != 0) & (regs.data_y != 0) & (regs.data_x != regs.data_y)) # X and Y buses should not change during a cycle, except for the first phase with m.Switch(phase_count): with m.Case(2, 3, 4, 5): with m.If(regs.data_x != 0): m.d.comb += Assert(Stable(regs.data_x)) with m.If(regs.data_y != 0): m.d.comb += Assert(Stable(regs.data_y)) # X and Y buses should be zero if there is no data transfer. with m.If(regs.reg_to_x == 0): m.d.comb += Assert(regs.data_x == 0) with m.If(regs.reg_to_y == 0): m.d.comb += Assert(regs.data_y == 0) with m.If(phase_count > 0): # X and Y buses should be zero if we read from register 0. with m.If(regs.reg_to_x & (regs.reg_x == 0)): m.d.comb += Assert(regs.data_x == 0) with m.If(regs.reg_to_y & (regs.reg_y == 0)): m.d.comb += Assert(regs.data_y == 0) write_pulse = Signal() m.d.comb += write_pulse.eq(phase_count != 4) # On write, the data should have been written to both banks. past_mem_addr = Signal(6) m.d.comb += past_mem_addr[:5].eq(Past(regs.reg_z)) m.d.comb += past_mem_addr[5].eq(Past(regs.reg_page)) past_z = Past(regs.data_z) with m.If(Rose(write_pulse)): m.d.comb += Assert(regs._x_bank._mem[past_mem_addr] == past_z) m.d.comb += Assert(regs._y_bank._mem[past_mem_addr] == past_z) # Pick an register, any register, except 0. We assert that unless # it is written, its data will not change. check_addr = AnyConst(5) check_page = AnyConst(1) saved_data = Signal(32) stored_x_data = Signal(32) stored_y_data = Signal(32) write_pulse_domain = ClockDomain("write_pulse_domain", local=True) m.domains.write_pulse_domain = write_pulse_domain write_pulse_domain.clk = write_pulse mem_addr = Signal(6) m.d.comb += Assume(check_addr != 0) m.d.comb += [ mem_addr[:5].eq(check_addr), mem_addr[5].eq(check_page), stored_x_data.eq(regs._x_bank._mem[mem_addr]), stored_y_data.eq(regs._y_bank._mem[mem_addr]), ] with m.If((regs.reg_z == check_addr) & (regs.reg_page == check_page)): m.d.write_pulse_domain += saved_data.eq(regs.data_z) with m.If(Initial()): m.d.comb += Assume(saved_data == stored_x_data) m.d.comb += Assume(stored_x_data == stored_y_data) with m.Else(): m.d.comb += Assert(saved_data == stored_x_data) m.d.comb += Assert(saved_data == stored_y_data) return m, [regs.data_z, regs.reg_to_x, regs.reg_to_y, regs.reg_x, regs.reg_y, regs.reg_z, regs.reg_page, ph1.clk, ph2.clk, saved_data, stored_x_data, stored_y_data]