For mass generation of bus decoders, a register bit map must be associated with corresponding control, status or data signals. This SoC concept follows the MaSoCist register map design rules:
select
lines.
This allows to implement W1C
(write one to clear) behaviour, or optimized data in/out transfers.To skip the entire elaboration on myIRL auxiliaries, jump to the actual register map decoder implementation
The NamedBitfield
and Register
class is extended with flags:
import sys
sys.path.insert(0, "../../")
from myirl.library.registers import Register, NamedBitfield
from myirl.kernel.components import ContainerExtension, ContainerBase, Signal, ChildAlias
from myirl.library.bulksignals import BulkWrapperSig
from myhdl import intbv
from myirl.kernel.sensitivity import hdlmacro
from myirl import targets
class BF(NamedBitfield):
READONLY = 0x01
WRITEONLY = 0x02
def __init__(self, name, msb, lsb, flags = 0, default = None):
super().__init__(name, msb, lsb)
self.init = default # Initialization value when reset
self.flags = flags # Access flags
class Reg(Register):
VOLATILE = 0x04
READONLY = 0x01
WRITEONLY = 0x02
def __init__(self, size, bitfields, flags = 0):
super().__init__(size, bitfields)
self.flags = flags
def has_select(self, flag):
if (self.flags & Reg.VOLATILE) and not (self.flags & flag):
return True
return False
We then create a RegisterSignal
derived from the ContainerExtension
that takes a register as argument. To allow aliasing, we also need to be able to pass a signal dictionary, optionally.
This class also adds a few special assignment methods for its signal members, according to their WRITEONLY/READONLY configuration.
class RegisterSignal(ContainerExtension):
def __init__(self, name, reg, signals = None, virtual = None):
if name is None:
name = kernel.utils.get_uuid('reg_')
# Hack to allow cloning/aliasing
if signals is not None:
super().__init__(name, signals, template = reg, virtual = True, twoway = True)
return
if not isinstance(reg, Register):
raise TypeError("expects register as argument")
bitfields = reg.members().items()
signals = {
n : Signal(intbv(ival := i.init if i.init is not None else 0)[i.msb + 1-i.lsb:] if i.msb != i.lsb else bool(ival),
name = i.name)
for n, i in bitfields
}
# Selection signals ('W1C' etc.)
select_sigs = {}
if reg.flags & reg.VOLATILE:
if not reg.flags & reg.READONLY:
select_sigs["sel_w"] = Signal(bool(0))
if not reg.flags & reg.WRITEONLY:
select_sigs["sel_r"] = Signal(bool(0))
inputs = filter(lambda t: (t[1].flags & BF.WRITEONLY) == False, bitfields)
outputs = filter(lambda t: (t[1].flags & BF.READONLY) == False, bitfields)
input_container = self.create(name, 'read', { n : signals[n] for n, _ in inputs }, ())
output_container = self.create(name, 'write', { n : signals[n] for n, _ in outputs }, ())
select_container = self.create(name, 'sel', select_sigs, ())
sigs = {}
if input_container is not None:
sigs['read'] = input_container
if output_container is not None:
output_container.driven = True
output_container._aux = False
sigs['write'] = output_container
if select_container is not None:
select_container.driven = True
sigs['select'] = select_container
for n, s in sigs.items():
s.rename(name + '_' + n)
super().__init__(name, sigs, template = reg, virtual = True, twoway = True)
def create(self, name, mname, children, bases = tuple()):
"Dynamically create a subclass from self._type"
if len(children) == 0:
return None
d = { '_templ' : children, '_virtual' : True,
'_rank' : ContainerBase._rank }
sign = name + '_' + mname
container = type(sign, (BulkWrapperSig, *bases), d)
targets.vhdl.register_type(container, sign)
inst = container()
inst._populate(children)
return inst
def alias(self, name):
"We need a different .alias() method, as we pass a Register for a new object"
signals = {}
for n, s in self._members.items():
nn = n
sig = ChildAlias(self, s, n, is_member = False)
signals[nn] = sig
new = type(self)(name, self._template, signals)
new.rename(name) # Need explicit rename here XXX
return new
# Here we don't need a @hdlmacro, because Register.assign() already returns
# a generator
def assign(self, data):
d = {}
try:
readport_members = self.get_children()['read'].members().items()
for n, i in readport_members:
bf = self._template.bfmap[n]
d[n] = i
gen = self._template.assign(data, **d)
return gen
except KeyError:
return None
@hdlmacro
def select_reset(self):
try:
members = self.get_children()['select'].members().items()
gen = []
for n, i in members:
gen.append(i.set(False))
yield gen
except KeyError:
yield []
@hdlmacro
def set(self, other):
gen = []
try:
w = self.get_children()['write']
m = w.members()
for n, i in m.items():
bf = self._template.bfmap[n]
if bf.msb == bf.lsb:
gen.append(i.set(other[bf.msb]))
else:
gen.append(i.set(other[bf.msb + 1: bf.lsb]))
yield gen
except KeyError:
yield []
Add a few register with bit fields and flags:
reg01 = Reg(16,
[
BF("im", 3, 1, flags = BF.READONLY),
BF("ex", 7, 6),
BF("inv", 4, 4, flags = BF.WRITEONLY),
BF("mode", 14, 10, default = 2)
]
)
reg02 = Reg(16,
[
BF("gna", 6, 1, default = 8),
BF("reset", 7, 7, default = True)
],
flags = Reg.VOLATILE | Reg.WRITEONLY
)
# This is a description for an address map
regdesc = {
0x01: ['stat', reg01],
0x02: ['ctrl', reg02],
0x04: ['TXD', Reg(16, [ BF("DATA", 15, 0)], flags = Reg.WRITEONLY | Reg.VOLATILE) ],
0x05: ['RXD', Reg(16, [ BF("DATA", 15, 0)], flags = Reg.READONLY | Reg.VOLATILE)]
}
To turn this into a register decoder circuit, we create a factory function, returning a worker
process:
from myirl.kernel import sensitivity
from myirl.kernel.sig import ConstSig
from myirl import simulation
def gen_regdec(regmap, port, clk, reset, wr, addr, idata, odata,
REPORT_ILLEGAL_ACCESS = False,
RESET_DEFAULTS = False):
kwargs = { 'EDGE' : clk.POS }
if RESET_DEFAULTS:
kwargs['RESET'] = reset
@sensitivity.process(clk, **kwargs)
def worker(logic):
"""Creates a case/when flow the procedural way"""
cw, cr = [ logic.Case(addr) for _ in range(2) ]
N = addr.size()
_reset = []
for k, rdesc in regmap.items():
name, rd = rdesc[0], rdesc[1]
if rd.has_select(Reg.READONLY):
maybe_write_sel = port[name].select.sel_w.set(True)
else:
maybe_write_sel = None
if rd.has_select(Reg.WRITEONLY):
maybe_read_sel = port[name].select.sel_r.set(True)
else:
maybe_read_sel = None
reg = port[name]
_reset += [ reg.select_reset(), ]
s = ConstSig(k, N)
if not rd.flags & Reg.READONLY:
cw = cw.When(s)(reg.set(idata), maybe_write_sel)
if not rd.flags & Reg.WRITEONLY:
cr = cr.When(s)(reg.assign(odata), maybe_read_sel)
if REPORT_ILLEGAL_ACCESS:
cw = cw.Other(sim.raise_(ValueError("Illegal WRITE address")))
cr = cr.Other(sim.raise_(ValueError("Illegal READ address")))
else:
cw = cw.Other(None)
cr = cr.Other(None)
_if = logic.If(wr == True).Then(cw).Else(cr)
logic += _reset
logic += [_if ]
return worker
The register decoder for this specific memory mapped register has a dynamic registerbank
dictionary passed to the interface, containing the register in/out wires. This variable argument construct is inferred to a HDL description.
The actual register map decoder consists of the code below.
from myirl.emulation.myhdl import *
from myirl.library.portion import *
SigType = Signal.Type
Bool = SigType(bool)
Addr = SigType(intbv, 12)
Data = SigType(intbv, 16)
@block
def mmr_decode(
clk : ClkSignal,
reset : ResetSignal,
addr : Addr,
wr : Bool,
data_in : Data,
data_out : Data.Output,
REGDESC,
**registerbank
):
# We use a partially assigneable signal:
idata = PASignal(intbv()[len(data_out):])
# Then generate the decoder from the register map description passed:
wk = gen_regdec(REGDESC, registerbank, clk, reset, wr, addr, data_in, idata,
RESET_DEFAULTS = True)
@always(clk.posedge)
def drive():
data_out.next = idata
return instances()
We define an interface generation function that creates a signal dictionary out of the register description:
# Interface generation:
def gen_interface(rd):
d = {}
for k, rdesc in rd.items():
n, reg = rdesc[0], rdesc[1]
sig = RegisterSignal(n, reg)
sig.rename(n)
d[n] = sig
return d
We might pack all MMR signals into a port structure including auxiliary methods. We need to decorate them with @hdlmacro
in order to return a generator element usable within the myHDL @instance
.
@container()
class MMRPort:
_inputs = ['din', 'wr', 'addr']
_outputs = ['dout']
_other = ['clk', 'rst']
def __init__(self):
self.clk = ClkSignal()
self.wr = Signal(bool())
self.addr = Addr()
self.rst = ResetSignal(0, 1)
self.din, self.dout = [ Data() for _ in range(2) ]
@hdlmacro
def reset_sequence(self):
p = self
yield [
p.rst.set(True),
simulation.wait(2 * (p.clk.posedge, )),
p.rst.set(False)
]
@hdlmacro
def write_sequence(self, a, d):
p = self
yield [
p.addr.set(a),
p.din.set(d),
simulation.wait(p.clk.posedge),
p.wr.set(True),
simulation.wait(p.clk.posedge),
p.wr.set(False),
]
@hdlmacro
def assert_read(self, addr, data):
yield [
self.addr.set(addr),
self.wr.set(False),
simulation.wait(2 * (self.clk.posedge,)),
simulation.assert_(self.dout == data, "Read mismatch")
]
Finally, we run a reset/write on the decoder:
@block
def testbench(regdesc):
p = MMRPort()
clk = ClkSignal('clk')
mon_gna = Signal(intbv()[6:])
mon_select = Signal(bool())
debug = Signal(bool())
interface = gen_interface(regdesc)
wires = [
mon_gna.wireup(interface['ctrl'].read.gna),
mon_select.wireup(interface['ctrl'].select.sel_w),
p.clk.wireup(clk)
]
inst = mmr_decode(clk, p.rst, p.addr, p.wr, p.din, p.dout, regdesc, **interface )
@always(delay(2))
def clkgen():
clk.next = ~clk
ctrl = interface['ctrl']
stat = interface['stat']
@instance
def stimulus():
print("START")
debug.next = False
p.wr.next = False
p.addr.next = 0x001
p.reset_sequence()
stat.read.ex.next = 0
stat.read.mode.next = 4
stat.read.im.next = 2
p.assert_read(0x001, 0x1004)
p.write_sequence(0x002, 0xfa)
debug.next = True
yield clk.posedge
assert ctrl.select.sel_w == True
assert ctrl.write.gna == 0x3d
yield clk.negedge
assert ctrl.select.sel_w == False
p.write_sequence(0x001, 0x10)
assert stat.write.inv == True
yield 2 * (clk.posedge, )
print("DONE")
raise StopSimulation
return instances()
def test():
tb = testbench(regdesc)
f = tb.elab(targets.VHDL, elab_all = True)
# Turn 'debug' on for simulation output
run_ghdl(f, tb, debug = True, vcdfile = 'testbench.vcd')
return f
f = test()
DEBUG CREATE wrapper module for testbench (EmulationModule 'top_testbench') Creating process 'mmr_decode/drive' with sensitivity (clk'rising,) Creating process 'testbench/clkgen' with sensitivity ([ DeltaT 2 ns ],) Creating sequential 'testbench/stimulus' Elaborating component mmr_decode_s1_s1_s12_s1_s16_s16_d_1_2_4_5 DEBUG: Skip virtual/interface: 'ctrl_select.sel_w' DEBUG: Skip virtual/interface: 'TXD_select.sel_w' DEBUG: Skip virtual/interface: 'RXD_select.sel_r' DEBUG: Skip virtual/interface: 'TXD_write.DATA' DEBUG: Skip virtual/interface: 'ctrl_write.gna' DEBUG: Skip virtual/interface: 'ctrl_write.reset' DEBUG: Skip virtual/interface: 'stat_write.ex' DEBUG: Skip virtual/interface: 'stat_write.inv' DEBUG: Skip virtual/interface: 'stat_write.mode' Writing 'mmr_decode' to file /tmp/myirl_top_testbench_6qys188j/mmr_decode.vhdl Elaborating component testbench_d_1_2_4_5 DEBUG: Skip virtual/interface: 'stat_read.ex' DEBUG: Skip virtual/interface: 'stat_read.mode' DEBUG: Skip virtual/interface: 'stat_read.im' Writing 'testbench' to file /tmp/myirl_top_testbench_6qys188j/testbench.vhdl Creating library file /tmp/myirl_module_defs_5bphdbsz/module_defs.vhdl ==== COSIM stdout ==== ==== COSIM stderr ==== ==== COSIM stdout ==== analyze /home/testing/src/myhdl2/myirl/targets/../test/vhdl/txt_util.vhdl analyze /home/testing/src/myhdl2/myirl/targets/libmyirl.vhdl analyze /tmp/myirl_module_defs_5bphdbsz/module_defs.vhdl analyze /tmp/myirl_top_testbench_6qys188j/mmr_decode.vhdl analyze /tmp/myirl_top_testbench_6qys188j/testbench.vhdl elaborate testbench ==== COSIM stderr ==== ==== COSIM stdout ==== START DONE /tmp/myirl_top_testbench_6qys188j/testbench.vhdl:114:9:@42ns:(assertion failure): Stop Simulation /tmp/testbench:error: assertion failed in process .testbench(myirl).stimulus /tmp/testbench:error: simulation failed ==== COSIM stderr ====
../../myirl/kernel/components.py:138: UserWarning: Base `dict` (Port 'regdesc') does not add to the interface. Local signal instances will be created. Use BulkSignal classes instead. base.warnings.warn("""Base `dict` (Port '%s') does not add to the interface. ../../myirl/kernel/components.py:138: UserWarning: Base `dict` (Port 'REGDESC') does not add to the interface. Local signal instances will be created. Use BulkSignal classes instead. base.warnings.warn("""Base `dict` (Port '%s') does not add to the interface.
! cat -n {f[0]}
1 -- File generated from source: 2 -- /tmp/ipykernel_42989/810171885.py 3 -- (c) 2016-2021 section5.ch 4 -- Modifications may be lost, edit the source file instead. 5 6 library IEEE; 7 use IEEE.std_logic_1164.all; 8 use IEEE.numeric_std.all; 9 10 library work; 11 12 use work.module_defs.all; 13 use work.txt_util.all; 14 use work.myirl_conversion.all; 15 16 entity mmr_decode is 17 port ( 18 clk : in std_ulogic; 19 reset : in std_ulogic; 20 addr : in unsigned(11 downto 0); 21 wr : in std_ulogic; 22 data_in : in unsigned(15 downto 0); 23 data_out : out unsigned(15 downto 0); 24 stat_read : in t_stat_read; 25 stat_write : out t_stat_write; 26 ctrl_read : in t_ctrl_read; 27 ctrl_write : out t_ctrl_write; 28 ctrl_select : out t_ctrl_sel; 29 TXD_read : in t_TXD_read; 30 TXD_write : out t_TXD_write; 31 TXD_select : out t_TXD_sel; 32 RXD_read : in t_RXD_read; 33 RXD_write : out t_RXD_write; 34 RXD_select : out t_RXD_sel 35 ); 36 end entity mmr_decode; 37 38 architecture MyIRL of mmr_decode is 39 -- Local type declarations 40 -- Signal declarations 41 signal idata : unsigned(15 downto 0); 42 begin 43 44 worker: 45 process(clk, reset) 46 begin 47 if rising_edge(clk) then 48 if reset = '1' then 49 ctrl_select.sel_w <= '0'; 50 TXD_select.sel_w <= '0'; 51 RXD_select.sel_r <= '0'; 52 idata <= x"0000"; 53 TXD_write.DATA <= x"0000"; 54 ctrl_write.gna <= "001000"; 55 ctrl_write.reset <= '1'; 56 stat_write.ex <= "00"; 57 stat_write.inv <= '0'; 58 stat_write.mode <= "00010"; 59 else 60 ctrl_select.sel_w <= '0'; 61 TXD_select.sel_w <= '0'; 62 RXD_select.sel_r <= '0'; 63 if (wr = '1') then 64 case addr is 65 when x"001" => 66 stat_write.ex <= data_in(8-1 downto 6); 67 stat_write.inv <= data_in(4); 68 stat_write.mode <= data_in(15-1 downto 10); 69 when x"002" => 70 ctrl_write.gna <= data_in(7-1 downto 1); 71 ctrl_write.reset <= data_in(7); 72 ctrl_select.sel_w <= '1'; 73 when x"004" => 74 TXD_write.DATA <= data_in(16-1 downto 0); 75 TXD_select.sel_w <= '1'; 76 when others => 77 end case; 78 else 79 case addr is 80 when x"001" => 81 idata(3 downto 1) <= stat_read.im; 82 idata(7 downto 6) <= stat_read.ex; 83 idata(14 downto 10) <= stat_read.mode; 84 when x"005" => 85 idata(15 downto 0) <= RXD_read.DATA; 86 RXD_select.sel_r <= '1'; 87 when others => 88 end case; 89 end if; 90 end if; 91 end if; 92 end process; 93 94 drive: 95 process(clk) 96 begin 97 if rising_edge(clk) then 98 data_out <= idata; 99 end if; 100 end process; 101 end architecture MyIRL; 102
! cat {f[1]}
-- File generated from source: -- /tmp/ipykernel_42989/385898099.py -- (c) 2016-2021 section5.ch -- Modifications may be lost, edit the source file instead. library IEEE; use IEEE.std_logic_1164.all; use IEEE.numeric_std.all; library work; use work.module_defs.all; use work.txt_util.all; use work.myirl_conversion.all; entity testbench is end entity testbench; architecture MyIRL of testbench is -- Local type declarations -- Signal declarations signal bulkc209_out : t_MMRPort_out; signal stat_write : t_stat_write; signal ctrl_write : t_ctrl_write; signal ctrl_select : t_ctrl_sel; signal TXD_write : t_TXD_write; signal TXD_select : t_TXD_sel; signal RXD_write : t_RXD_write; signal RXD_select : t_RXD_sel; signal clk : std_ulogic := '0'; signal stat_read : t_stat_read; signal ctrl_read : t_ctrl_read; signal TXD_read : t_TXD_read; signal RXD_read : t_RXD_read; signal debug : std_ulogic; signal bulkc209_in : t_MMRPort_in; signal bulkc209_aux : t_MMRPort_aux; signal mon_gna : unsigned(5 downto 0); signal mon_select : std_ulogic; begin -- Instance mmr_decode inst_mmr_decode_0: entity work.mmr_decode port map ( clk => clk, reset => bulkc209_aux.rst, addr => bulkc209_in.addr, wr => bulkc209_in.wr, data_in => bulkc209_in.din, data_out => bulkc209_out.dout, stat_read => stat_read, stat_write => stat_write, ctrl_read => ctrl_read, ctrl_write => ctrl_write, ctrl_select => ctrl_select, TXD_read => TXD_read, TXD_write => TXD_write, TXD_select => TXD_select, RXD_read => RXD_read, RXD_write => RXD_write, RXD_select => RXD_select ); clkgen: clk <= not clk after 2 ns; stimulus: process begin print("START"); debug <= '0'; bulkc209_in.wr <= '0'; bulkc209_in.addr <= x"001"; bulkc209_aux.rst <= '1'; wait until rising_edge(bulkc209_aux.clk); wait until rising_edge(bulkc209_aux.clk); bulkc209_aux.rst <= '0'; stat_read.ex <= "00"; stat_read.mode <= "00100"; stat_read.im <= "010"; bulkc209_in.addr <= x"001"; bulkc209_in.wr <= '0'; wait until rising_edge(bulkc209_aux.clk); wait until rising_edge(bulkc209_aux.clk); assert (bulkc209_out.dout = x"1004") report "Read mismatch" severity failure; bulkc209_in.addr <= x"002"; bulkc209_in.din <= x"00fa"; wait until rising_edge(bulkc209_aux.clk); bulkc209_in.wr <= '1'; wait until rising_edge(bulkc209_aux.clk); bulkc209_in.wr <= '0'; debug <= '1'; wait until rising_edge(clk); assert (ctrl_select.sel_w = '1') report "Failed in /tmp/ipykernel_42989/385898099.py:testbench():46" severity failure; assert (ctrl_write.gna = "111101") report "Failed in /tmp/ipykernel_42989/385898099.py:testbench():47" severity failure; wait until falling_edge(clk); assert (ctrl_select.sel_w = '0') report "Failed in /tmp/ipykernel_42989/385898099.py:testbench():49" severity failure; bulkc209_in.addr <= x"001"; bulkc209_in.din <= x"0010"; wait until rising_edge(bulkc209_aux.clk); bulkc209_in.wr <= '1'; wait until rising_edge(bulkc209_aux.clk); bulkc209_in.wr <= '0'; assert (stat_write.inv = '1') report "Failed in /tmp/ipykernel_42989/385898099.py:testbench():52" severity failure; wait until rising_edge(clk); wait until rising_edge(clk); print("DONE"); assert false report "Stop Simulation" severity failure; wait; end process; mon_gna <= ctrl_read.gna; mon_select <= ctrl_select.sel_w; bulkc209_aux.clk <= clk; end architecture MyIRL;
# ! cat {mmr_decode.ctx.path_prefix}module_defs.vhdl
The *.vcd
format hides the MMRPort
record members from the trace. Therefore we need a few monitoring auxiliary signals.
import wavedraw
import nbwavedrom
TB = "testbench"
waveform = wavedraw.vcd2wave(TB+ ".vcd", TB + '.clk', None)
nbwavedrom.draw(waveform)