Current section
Files
Jump to
Current section
Files
lib/danm/verilog_printing.ex
defmodule Danm.VerilogPrinting do
@moduledoc """
Generate Verilog file for downstream tools
"""
alias Danm.Entity
alias Danm.WireExpr
alias Danm.BlackBox
alias Danm.Schematic
alias Danm.Sink
alias Danm.ComboLogic
alias Danm.BundleLogic
alias Danm.ChoiceLogic
alias Danm.ConditionLogic
alias Danm.CaseLogic
alias Danm.SeqLogic
alias Danm.FiniteStateMachine
alias Danm.Assertion
defstruct dict: %{},
stack: [],
stream: nil,
refs: %{}
@doc ~S"""
generate a single verilog file that have everything
"""
def generate_full_verilog(s, in: dir) do
f = File.open!("#{dir}/#{s.name}.v", [:write, :delayed_write, :utf8])
IO.puts(f, "// This file is generated by DANM on #{DateTime.utc_now()}")
%__MODULE__{stream: f}
|> setup_key_ref(module_to_key(s), s.name)
|> print_full_verilog(s)
File.close(f)
end
defp current_design(state), do: hd(state.stack)
defp begin_print(state, ref, s) do
%{state |
refs: Map.put(state.refs, ref, :ongoing),
stack: [s | state.stack ]}
end
defp end_print(state, ref) do
%{state |
refs: Map.put(state.refs, ref, :done),
stack: tl(state.stack) }
end
defp print_full_verilog(state, s) do
ref = Map.fetch!(state.dict, module_to_key(s))
case Map.fetch!(state.refs, ref) do
:done -> state
:ongoing -> raise "Infinite recursive design in #{s.name}"
:todo ->
state
|> begin_print(ref, s)
|> print_current_design(ref)
|> end_print(ref)
end
end
defp inlined?(s) do
case s.__struct__ do
BlackBox -> false
Schematic -> false
_ -> true
end
end
defp print_current_design(state, ref) do
s = current_design(state)
case s.__struct__ do
BlackBox -> copy_self_verilog(state, ref)
Schematic ->
s
|> Schematic.sort_sub_modules(except: &inlined?/1)
|> Enum.reduce(print_self_verilog(state, ref), fn i_name, state ->
print_full_verilog(state, Map.fetch!(s.insts, i_name)) end)
end
end
defp copy_self_verilog(state, ref) do
b = current_design(state)
f = state.stream
if ref != b.name, do: raise "A black box cannot be uniquified, got #{ref} expect #{b.name}"
# copy everything, and add a return to make sure format is proper
IO.puts(f, File.read!(b.src))
state
end
defp unique_name_like(name, from: dict) do
cond do
Map.has_key?(dict, name) -> unique_name_like(name, from: dict, salt: 1)
true -> name
end
end
defp unique_name_like(name, from: dict, salt: s) do
salted = "#{name}_#{s}"
cond do
Map.has_key?(dict, salted) -> unique_name_like(name, from: dict, salt: s + 1)
true -> salted
end
end
defp print_self_verilog(state, ref) do
s = current_design(state)
f = state.stream
sorted_ports = BlackBox.sort_ports(s)
port_string = sorted_ports |> Enum.map(&verilog_escape/1) |> Enum.join(",\n\t")
IO.write(f, ~s"""
/**
#{Schematic.doc_string(s)}
*/
module #{ref}(
#{port_string});
""")
Enum.each(sorted_ports, fn p_name ->
{dir, width} = Map.fetch!(s.ports, p_name)
case width do
1 -> IO.puts(f, " #{dir} #{verilog_escape(p_name)};")
_ -> IO.puts(f, " #{dir} [#{width - 1}:0] #{verilog_escape(p_name)};")
end
end)
map = Schematic.wire_width_map(s)
s.wires
|> Map.keys()
|> Enum.sort(:asc)
|> Enum.each(fn w_name ->
width = Map.fetch!(map, w_name)
wire_type = wire_type(s, w_name)
case width do
1 -> IO.puts(f, " #{wire_type} #{verilog_escape(w_name)};")
_ -> IO.puts(f, " #{wire_type} [#{width - 1}:0] #{verilog_escape(w_name)};")
end
end)
map = Schematic.pin_to_wire_map(s)
state =
s
|> Schematic.sort_sub_modules()
|> Enum.reduce(state, fn i_name, state ->
case Map.fetch!(s.insts, i_name).__struct__ do
ChoiceLogic -> print_one_choice_logic(state, i_name)
ConditionLogic -> print_one_condition_logic(state, i_name)
CaseLogic -> print_one_case_logic(state, i_name)
SeqLogic -> print_one_seq_logic(state, i_name)
FiniteStateMachine -> print_one_fsm_logic(state, i_name)
Sink -> print_one_sink(state, i_name)
Assertion -> print_one_assertion(state, i_name)
t when t in [ComboLogic, BundleLogic] ->
print_one_simple_logic(state, i_name)
t when t in [BlackBox, Schematic] ->
print_one_instance(state, i_name, with: map)
end
end)
IO.puts(f, "endmodule // #{ref}\n")
state
end
defp wire_type(s, w_name) do
{di, _} = Schematic.driver_of_wire(s, Map.fetch!(s.wires, w_name))
case di do
:self -> "wire"
_ -> case Map.fetch!(s.insts, di).__struct__ do
BlackBox -> "wire"
Schematic -> "wire"
ComboLogic -> "wire"
BundleLogic -> "wire"
_ -> "reg"
end
end
end
defp module_to_key(s) do
case s.__struct__ do
BlackBox -> s.name
Schematic -> {s.name, s.params}
# anything else should not hava a key
end
end
defp key_to_ref(state, k) do
case Map.get(state.dict, k) do
nil ->
name = case k do
{name, _} -> name
name -> name
end
unique_name_like(name, from: state.refs)
ref -> ref
end
end
defp setup_key_ref(state, key, ref) do
%{state |
dict: Map.put(state.dict, key, ref),
refs: case Map.get(state.refs, ref) do
nil -> Map.put(state.refs, ref, :todo)
_ -> state.refs
end}
end
defp print_one_instance(state, i_name, with: map) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
key = module_to_key(inst)
ref = key_to_ref(state, key)
IO.write(f, ~s"""
// instance of #{Entity.type_string(inst)}
#{ref} #{verilog_escape(i_name)}(
""")
conns_str =
inst
|> BlackBox.sort_ports()
|> Enum.filter(fn p_name -> Map.has_key?(map, "#{i_name}/#{p_name}") end)
|> Enum.map(fn p_name ->
w_name = Map.fetch!(map, "#{i_name}/#{p_name}")
".#{verilog_escape(p_name)}(#{verilog_escape(w_name)})"
end)
|> Enum.join(",\n\t")
IO.puts(f, "\t#{conns_str});")
# only print defparam for black boxes. schematic has parameters passed in the generated code
if inst.__struct__ == BlackBox do
inst.params
|> Map.keys()
|> Enum.sort(:asc)
|> Enum.each(fn p_name ->
p_value = Map.fetch!(inst.params, p_name)
IO.puts(f, " defparam #{verilog_escape(i_name)}.#{verilog_escape(p_name)} = #{p_value};")
end)
IO.write(f, "\n")
end
setup_key_ref(state, key, ref)
end
defp print_one_sink(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, "// instance of #{Entity.type_string(inst)}")
inst.inputs
|> Map.keys()
|> Enum.sort(:asc)
|> Enum.each(fn p_name ->
IO.puts(f, "//\t sink(#{verilog_escape(p_name)});")
end)
state
end
defp print_one_simple_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.write(f, " assign ")
print_simple_logic_core(inst, i_name, "=", 0, f)
state
end
defp indent(n), do: String.duplicate("\t", div(n, 8)) <> String.duplicate(" ", rem(n, 8))
defp print_simple_logic_core(inst, i_name, assign, indent, f) do
str = case inst.__struct__ do
ComboLogic -> verilog_string(inst.expr)
BundleLogic -> BundleLogic.expr_string(inst, &verilog_escape/1)
end
IO.puts(f, "#{indent(indent)}#{verilog_escape(i_name)} #{assign} #{str};")
end
defp print_one_choice_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, " always @(#{sensitivity_string(inst)})")
print_choice_logic_core(inst, i_name, "=", 8, f)
state
end
defp print_choice_logic_core(inst, i_name, assign, indent, f) do
w = ChoiceLogic.cond_width(inst)
IO.puts(f, "#{indent(indent)}case (#{verilog_string(inst.condition)})")
Enum.reduce(inst.choices, 0, fn c, i ->
IO.puts(f, "#{indent(indent+4)}#{w}'b#{pad_string(i, 2, w)}: #{verilog_escape(i_name)} #{assign} #{verilog_string(c)};")
i + 1
end)
IO.puts(f, "#{indent(indent)}endcase")
end
defp print_one_condition_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, " always @(#{sensitivity_string(inst)})")
print_condition_logic_core(inst, i_name, "=", 8, f)
state
end
defp print_condition_logic_core(inst, i_name, assign, indent, f) do
inst.conditions
|> Enum.zip(inst.choices)
|> Enum.reduce(0, fn {co, ch}, i ->
case i do
0 ->
IO.puts(f, "#{indent(indent)}if (#{verilog_string(co)})")
_->
case co do
{:const, _, v} when v > 0 -> IO.puts(f, "#{indent(indent)}else")
_ ->
IO.puts(f, "#{indent(indent)}else if (#{verilog_string(co)})")
end
end
IO.puts(f, "#{indent(indent+4)}#{verilog_escape(i_name)} #{assign} #{verilog_string(ch)};")
i + 1
end)
end
defp print_one_case_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, " always @(#{sensitivity_string(inst)})")
print_case_logic_core(inst, i_name, "=", 8, f)
state
end
defp print_case_logic_core(inst, i_name, assign, indent, f) do
IO.puts(f, "#{indent(indent)}case (#{verilog_string(inst.condition)})")
inst.cases
|> Enum.zip(inst.choices)
|> Enum.reduce(0, fn {co, ch}, i ->
IO.puts(f, "#{indent(indent+4)}#{verilog_string(co)}}: #{verilog_escape(i_name)} #{assign} #{verilog_string(ch)};")
i + 1
end)
IO.puts(f, "#{indent(indent)}endcase")
end
defp print_one_seq_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, " always @(posedge #{verilog_escape(inst.clk)})")
case inst.core.__struct__ do
ChoiceLogic ->
print_choice_logic_core(inst.core, i_name, "<=", 8, f)
ConditionLogic ->
print_condition_logic_core(inst.core, i_name, "<=", 8, f)
CaseLogic ->
print_case_logic_core(inst.core, i_name, "<=", 8, f)
_->
print_simple_logic_core(inst.core, i_name, "<=", 8, f)
end
state
end
defp print_one_fsm_logic(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
IO.puts(f, " always @(posedge #{verilog_escape(inst.clk)})")
IO.puts(f, "\tcase (#{verilog_escape(i_name)})")
Enum.each(inst.graph, fn {state, transit} ->
IO.puts(f, "\t #{inst.width}'d#{state}:")
Enum.reduce(transit, 0, fn {co, ns}, i ->
case i do
0 -> IO.puts(f, "\t\tif (#{verilog_string(co)})")
_->
case co do
{:const, _, v} when v > 0 -> IO.puts(f, "\t\telse")
_ -> IO.puts(f, "\t\telse if (#{verilog_string(co)})")
end
end
IO.puts(f, "\t\t #{verilog_escape(i_name)} <= #{inst.width}'d#{ns};")
i + 1
end)
end)
IO.puts(f, "\t default: #{verilog_escape(i_name)} <= #{inst.width}'d0;")
IO.puts(f, "\tendcase")
state
end
defp print_one_assertion(state, i_name) do
s = current_design(state)
f = state.stream
inst = Map.fetch!(s.insts, i_name)
case inst.clk do
nil -> IO.puts(f, " always @(#{sensitivity_string(inst)})")
clk -> IO.puts(f, " always @(posedge #{verilog_escape(clk)})")
end
IO.write(f, ~s"""
\tif (($stime > #{inst.silent_time})&&#{verilog_string(inst.expr)}) begin
\t $fdisplay(32'h80000002, "Assertion #{s.name}.#{i_name} failed at %%d", $stime);
\t $finish;
\tend
""")
state
end
defp pad_string(n, b, w) do
str = Integer.to_string(n, b)
String.duplicate("0", w-String.length(str)) <> str
end
defp sensitivity_string(inst) do
inst.inputs |> Map.keys() |> Enum.map(&verilog_escape/1) |> Enum.join(" or ")
end
defp verilog_string(term), do: WireExpr.ast_string(term, &verilog_escape/1)
defp verilog_escape(str) do
cond do
String.contains?(str, "/") -> "\\" <> str <> " "
true -> str
end
end
end