Вы находитесь на странице: 1из 1

КАК СТАТЬ АВТОРОМ Как я в студенчестве подсел на компьютеры одного бренда и, повзрослев, встретил их снова

Все потоки Разработка Администрирование Дизайн Менеджмент Маркетинг Научпоп Войти Регистрация

atrosinenko 16 июня 2019 в 21:41 Реклама

Часть 2: RocketChip: подключаем оперативную


память
Open source, Assembler, Scala, FPGA, DIY или Сделай сам

Tutorial

В предыдущей части мы собрали микроконтроллер


вообще без оперативной памяти на базе ПЛИС
Altera/Intel. Однако на плате есть разъём с
установленным SO-DIMM DDR2 1Gb, который,
очевидно, хочется использовать. Для этого нам
потребуется обернуть DDR2-контроллер с
интерфейсом ALTMEMPHY в модуль, понятный для
протокола работы с памятью TileLink, используемого
повсюду в RocketChip. Под катом — тактильная
отладка, брутфорс программирование и ГРАБЛИ.

Как известно, в Computer Science есть две главные проблемы: инвалидация кешей и именование
переменных. На КДПВ вы видите редкий момент — две главные проблемы CS встретили друг друга и
что-то замышляют.

DISCLAIMER: В дополнение к предупреждению из предыдущей статьи настоятельно рекомендую


дочитать статью до конца перед повторением опытов, во избежание повреждения ПЛИС, модуля
памяти или цепей питания.

В этот раз хотелось если не загрузить Linux, то хотя бы подключить оперативную память, коей на моей
плате аж целый гигабайт (а можно поставить до четырёх). Критерием успеха предлагается
рассматривать возможность читать и писать через связку GDB+OpenOCD, в том числе по адресам, не
выровненным на 16 байтов (ширина одного запроса в память). На первый взгляд, нужно просто чуточку
поправить конфиг, не может же генератор SoC не поддерживать оперативную память из коробки.
Поддерживать-то он поддерживает, но через интерфейс MIG (ну, и, возможно, ещё какой-нибудь
интерфейс от Microsemi). Через стандартный интерфейс AXI4 тоже поддерживает, но его, насколько я
понимаю, не так то просто заполучить (по крайней мере, не осваивая Platform Designer).

Лирическое отступление: Существует, насколько я понимаю, довольно популярная серия


«внутричиповых» интерфейсов AXI, разработанная ARM. Тут можно было бы подумать, что оно всё
насквозь патентованое и закрытое. Но после того, как я зарегистрировался (безо всяких
«университетских программ» и прочего — просто по e-mail и заполнению анкеты) и получил доступ к
спецификации, меня ждало приятное удивление. Я, конечно, не юрист, но похоже, что стандарт
довольно таки открытый: вы либо обязаны использовать лицензированные ядра от ARM, либо вообще
не претендовать на совместимость с ARM, и тогда вроде всё ОК. Но вообще, конечно, читайте
лицензию, читайте с юристами и т.д.

Мартышка и TileLink (басня)


Задача казалась довольно простой, и я открыл описание уже имевшегося в проекте от поставщика
платы модуля ddr2_64bit:

Собственность Intel и вообще

module ddr2_64bit (
local_address,
local_write_req,
local_read_req,
local_burstbegin,
local_wdata,
local_be,
local_size,
global_reset_n,
pll_ref_clk,
soft_reset_n,
local_ready,
local_rdata,
local_rdata_valid,
local_refresh_ack,
local_init_done,
reset_phy_clk_n,
mem_odt,
mem_cs_n,
mem_cke,
mem_addr,
mem_ba,
mem_ras_n,
mem_cas_n,
mem_we_n,
mem_dm,
phy_clk,
aux_full_rate_clk,
aux_half_rate_clk,
reset_request_n,
mem_clk,
mem_clk_n,
mem_dq,
mem_dqs);

input [25:0] local_address;


input local_write_req;
input local_read_req;
input local_burstbegin;
input [127:0] local_wdata;
input [15:0] local_be;
input [2:0] local_size;
input global_reset_n;
input pll_ref_clk;
input soft_reset_n;
output local_ready;
output [127:0] local_rdata;
output local_rdata_valid;
output local_refresh_ack;
output local_init_done;
output reset_phy_clk_n;
output [1:0] mem_odt;
output [1:0] mem_cs_n;
output [1:0] mem_cke;
output [13:0] mem_addr;
output [1:0] mem_ba;
output mem_ras_n;
output mem_cas_n;
output mem_we_n;
output [7:0] mem_dm;
output phy_clk;
output aux_full_rate_clk;
output aux_half_rate_clk;
output reset_request_n;
inout [1:0] mem_clk;
inout [1:0] mem_clk_n;
inout [63:0] mem_dq;
inout [7:0] mem_dqs;

...

Народная мудрость гласит: «Любую документацию на русском языке нужно начинать со слов: "Итак,
оно не работает"». Но здесь не совсем интуитивно понятный интерфейс, поэтому всё же почитаем. В
описании нам тут же рассказывают, что работа с DDR2 — дело непростое. Нужно настроить PLL,
провести некую калибровку, крекс-фекс-пекс, выставился сигнал local_init_done, можно работать.
Вообще, логика именования здесь примерно следующая: имена с префиксами local_ — это
«пользовательский» интерфейс, порты mem_ нужно непосредственно вывести на ножки, подключённые к
модулю памяти, на pll_ref_clk нужно подать тактовый сигнал с указанной при настройке модуля
частотой — из него будут получены остальные частоты, ну и всякие входы-выходы reset и выходы
частот, синхронно с которыми должен работать пользовательский интерфейс.

Давайте создадим описание внешних сигналов к памяти и интерфейса модуля ddr2_64bit:

trait MemIf

trait MemIf {
val local_init_done = Output(Bool())

val global_reset_n = Input(Bool())


val pll_ref_clk = Input(Clock())
val soft_reset_n = Input(Bool())

val reset_phy_clk_n = Output(Clock())


val mem_odt = Output(UInt(2.W))
val mem_cs_n = Output(UInt(2.W))
val mem_cke = Output(UInt(2.W))
val mem_addr = Output(UInt(14.W))
val mem_ba = Output(UInt(2.W))
val mem_ras_n = Output(UInt(1.W))
val mem_cas_n = Output(UInt(1.W))
val mem_we_n = Output(UInt(1.W))
val mem_dm = Output(UInt(8.W))
val phy_clk = Output(Clock())
val aux_full_rate_clk = Output(Clock())
val aux_half_rate_clk = Output(Clock())
val reset_request_n = Output(Bool())
val mem_clk = Analog(2.W)
val mem_clk_n = Analog(2.W)
val mem_dq = Analog(64.W)
val mem_dqs = Analog(8.W)

def connectFrom(mem_if: MemIf): Unit = {


local_init_done := mem_if.local_init_done

mem_if.global_reset_n := global_reset_n
mem_if.pll_ref_clk := pll_ref_clk
mem_if.soft_reset_n := soft_reset_n

reset_phy_clk_n := mem_if.reset_phy_clk_n
mem_odt <> mem_if.mem_odt
mem_cs_n <> mem_if.mem_cs_n
mem_cke <> mem_if.mem_cke
mem_addr <> mem_if.mem_addr
mem_ba <> mem_if.mem_ba
mem_ras_n <> mem_if.mem_ras_n
mem_cas_n <> mem_if.mem_cas_n
mem_we_n <> mem_if.mem_we_n
mem_dm <> mem_if.mem_dm

mem_clk <> mem_if.mem_clk


mem_clk_n <> mem_if.mem_clk_n
mem_dq <> mem_if.mem_dq
mem_dqs <> mem_if.mem_dqs

phy_clk := mem_if.phy_clk
aux_full_rate_clk := mem_if.aux_full_rate_clk
aux_half_rate_clk := mem_if.aux_half_rate_clk
reset_request_n := mem_if.reset_request_n
}
}

class MemIfBundle extends Bundle with MemIf

class dd2_64bit

class ddr2_64bit extends BlackBox {


override val io = IO(new MemIfBundle {
val local_address = Input(UInt(26.W))
val local_write_req = Input(Bool())
val local_read_req = Input(Bool())
val local_burstbegin = Input(Bool())
val local_wdata = Input(UInt(128.W))
val local_be = Input(UInt(16.W))
val local_size = Input(UInt(3.W))
val local_ready = Output(Bool())
val local_rdata = Output(UInt(128.W))
val local_rdata_valid = Output(Bool())
val local_refresh_ack = Output(Bool())
})
}

Тут меня поджидал первый букетик граблей: во первых, насмотревшись на класс ROMGenerator, я
подумал, что и контроллер памяти можно выдернуть из глубин дизайна через глобальную переменную,
а Chisel как-нибудь сам провода пробросит. Не получилось. Поэтому пришлось сделать жгут проводов
MemIfBundle, который протягивался по всей иерархии. Почему же он не торчит из BlackBox-а, и не
подключается разом? Дело в том, что у BlackBox все внешние порты запихнуты в val io = IO(new Bundle
{ ... }). Если в бандле весь MemIfBundle сделать одной переменной, то имя этой переменной будет
сделано префиксом для имён всех портов, и имена банально не сойдутся с интерфейсом блока.
Наверное, можно сделать как-то более адекватно, но пока оставим так.

Далее по аналогии с другими TileLink-устройствами (преимущественно живущими в rocket-


chip/src/main/scala/tilelink), и в особенности, BootROM, опишем свой интерфейс к контроллеру памяти:

class AltmemphyDDR2RAM(implicit p: Parameters) extends LazyModule {

val MemoryPortParams(MasterPortParams(base, size, beatBytes, _, _, executable), 1) = p(ExtMe


m).get

val node = TLManagerNode(Seq(TLManagerPortParameters(


Seq(TLManagerParameters(
address = AddressSet.misaligned(base, size),
resources = new SimpleDevice("ram", Seq("sifive,altmemphy0")).reg("mem"),
regionType = RegionType.UNCACHED,
executable = executable,
supportsGet = TransferSizes(1, 16),
supportsPutFull = TransferSizes(1, 16),
fifoId = Some(0)
)),
beatBytes = 16
)))

override lazy val module = new AltmemphyDDR2RAMImp(this)


}

class AltmemphyDDR2RAMImp(_outer: AltmemphyDDR2RAM)(implicit p: Parameters)


extends LazyModuleImp(_outer) {

val (in, edge) = _outer.node.in(0)


val ddr2 = Module(new ddr2_64bit)
val mem_if = IO(new MemIfBundle)

// TODO здесь дорисовать сову


}

trait HasAltmemphyDDR2 { this: BaseSubsystem =>


val dtb: DTB
val mem_ctrl = LazyModule(new AltmemphyDDR2RAM)
mem_ctrl.node := mbus.toDRAMController(Some("altmemphy-ddr2"))()
}

trait HasAltmemphyDDR2Imp extends LazyModuleImp {


val outer: HasAltmemphyDDR2
val mem_if = IO(new MemIfBundle)
mem_if <> outer.mem_ctrl.module.mem_if
}

По стандартному ключу ExtMem мы извлекаем из конфига SoC параметры внешней памяти (вот этот
странный синтаксис позволяет по аналогии с паттерн-матчингом сказать «я знаю, что мне вернут
экземпляр case class MemoryPortParameters (это гарантируется типом ключа на этапе компиляции Scala-
кода, при условии, что в рантайме мы не упадём, вынимая содержимое из Option[MemoryPortParams],
равного None, но тогда нечего было контроллер памяти создавать в System.scala...), так вот, сам case class
мне не нужен, а некоторые его поля нужны»). Далее мы создаём manager port TileLink-устройства
(протокол TileLink обеспечивает взаимодействие практически всего, что связано с памятью:
контроллера DDR и других memory-mapped устройств, кешей процессора, возможно, ещё чего-то, у
каждого устройства может быть по нескольку портов, каждое устройство может быть и manager, и
client). beatBytes, насколько я понимаю, задаёт размер одной транзакции, а у нас обмен с контроллером
ведётся по 16 байт. HasAltmemphyDDR2 и HasAltmemphyDDR2Imp мы подмешаем в нужных местах в
System.scala, напишем конфиг

class BigZeowaaConfig extends Config (


new WithNBreakpoints(2) ++
new WithNExtTopInterrupts(0) ++
new WithExtMemSize(1l << 30) ++
new WithNMemoryChannels(1) ++
new WithCacheBlockBytes(16) ++
new WithNBigCores(1) ++
new WithJtagDTM ++
new BaseConfig
)

Сделав некий «набросок совы» в AltmemphyDDR2RAMImp, я синтезировал дизайн (что-то всего на ~30MHz,
хорошо, что я тактируюсь от 25MHz) и, положив пальцы на модули памяти и микросхему ПЛИС, залил
его в плату. Тут я увидел, что такое настоящий интуитивно понятный интерфейс: это когда ты даёшь в
gdb команду на запись в память, и по зависшему процессору и обожжённым чувствующим сильный
нагрев пальцам понимаешь, что нужно срочно нажать на плате сброс и поправить контроллер.

Читаем документацию на контроллер DDR2


Видимо, пришло время почитать документацию на контроллер дальше списка портов. Так, что тут у
нас?.. Упс, оказывается, входы-выходы с префиксом local_ должны выставляться синхронно не с
pll_ref_clk, который 25MHz, а либо с phy_clk, выдающим половинную частоту памяти для half-rate
контроллера, либо, в нашем случае, aux_half_rate_clk (может, всё-таки aux_full_rate_clk?), выдающим
полную частоту памяти, а она, на минуточку, 166MHz.

Стало быть, нужно пересекать границы частотных доменов. По старой памяти решил воспользоваться
защёлками, точнее цепочкой из них:

+-+ +-+ +-+ +-+


--| |--| |--| |--| |--->
+-+ +-+ +-+ +-+
| | | |
---+ | | |
inclk | | |
| | |
--------+----+ |
outclk |
|
------------------+
output enable

Но, повозившись часок, пришёл к выводу, что не осилю на «скалярных» защёлках две очереди (в
высокочастотный домен и обратно), каждая из которых будет иметь противонаправленные сигналы
(ready и valid), да ещё и так, чтобы быть уверенным, что какой-нибудь битик не отстанет на такт-другой
по дороге. Ещё через некоторое время я понял, что и описать синхронизацию на ready-valid без
общего тактового сигнала — тоже задача сродни созданию неблокирующих структур данных в том
смысле, что думать и формально доказывать нужно много, ошибиться легко, заметить трудно, а
главное, всё уже реализовано до нас: у Intel есть примитив dcfifo, который представляет собой очередь
конфигурируемой длины и ширины, читается и пишется которая из разных частотных доменов. В итоге я
воспользовался экспериментальной возможностью свежего Chisel, а именно, параметризованными
black box-ами:

class FIFO (val width: Int, lglength: Int) extends BlackBox(Map(


"intended_device_family" -> StringParam("Cyclone IV E"),
"lpm_showahead" -> StringParam("OFF"),
"lpm_type" -> StringParam("dcfifo"),
"lpm_widthu" -> IntParam(lglength),
"overflow_checking" -> StringParam("ON"),
"rdsync_delaypipe" -> IntParam(5),
"underflow_checking" -> StringParam("ON"),
"use_eab" -> StringParam("ON"),
"wrsync_delaypipe" -> IntParam(5),

"lpm_width" -> IntParam(width),


"lpm_numwords" -> IntParam(1 << lglength)
)) {
override val io = IO(new Bundle {
val data = Input(UInt(width.W))
val rdclk = Input(Clock())
val rdreq = Input(Bool())
val wrclk = Input(Clock())
val wrreq = Input(Bool())
val q = Output(UInt(width.W))
val rdempty = Output(Bool())
val wrfull = Output(Bool())
})

override def desiredName: String = "dcfifo"


}

И написал простенькую биндилку произвольных типов данных:

object FIFO {
def apply[T <: Data](
lglength: Int,
output: T, outclk: Clock,
input: T, inclk: Clock
): FIFO = {
val res = Module(new FIFO(width = output.widthOption.get, lglength = lglength))
require(input.getWidth == res.width)
output := res.io.q.asTypeOf(output)
res.io.rdclk := outclk
res.io.data := input.asUInt()
res.io.wrclk := inclk
res
}
}

Отладка
После этого код превратился в перекладывание сообщений между доменами через две уже
однонаправленных очереди: tl_req / ddr_req и ddr_resp / tl_resp (то, что имеет префикс tl_, тактируется
вместе с TileLink, то, что ddr_ — вместе с контроллером памяти). Проблема в том, что всё всё равно
дедлочилось, а иногда и изрядно грелось. И если причиной перегрева оказалось одновременное
выставление local_read_req и local_write_req, то с дедлоками так легко побороться не получилось. Код
при этом представлял из себя что-то вроде

class AltmemphyDDR2RAMImp(_outer: AltmemphyDDR2RAM)(implicit p: Parameters)


extends LazyModuleImp(_outer) {

val addrSize = log2Ceil(_outer.size / 16)

val (in, edge) = _outer.node.in(0)


val ddr2 = Module(new ddr2_64bit)
require(ddr2.io.local_address.getWidth == addrSize)
val tl_clock = clock
val ddr_clock = ddr2.io.aux_full_rate_clk
val mem_if = IO(new MemIfBundle)

class DdrRequest extends Bundle {


val size = UInt(in.a.bits.size.widthOption.get.W)
val source = UInt(in.a.bits.source.widthOption.get.W)
val address = UInt(addrSize.W)
val be = UInt(16.W)
val wdata = UInt(128.W)
val is_reading = Bool()
}

val tl_req = Wire(new DdrRequest)


val ddr_req = Wire(new DdrRequest)
val fifo_req = FIFO(2, ddr_req, ddr_clock, tl_req, clock)

class DdrResponce extends Bundle {


val is_reading = Bool()
val size = UInt(in.d.bits.size.widthOption.get.W)
val source = UInt(in.d.bits.source.widthOption.get.W)
val rdata = UInt(128.W)
}

val tl_resp = Wire(new DdrResponce)


val ddr_resp = Wire(new DdrResponce)
val fifo_resp = FIFO(2, tl_resp, clock, ddr_resp, ddr_clock)

// логика общения с TileLink

withClock(ddr_clock) {
// логика общения с контроллером
}

Чтобы локализовать проблему, решил банально закомментировать весь код внутри


withClock(ddr_clock) (не правда ли, визуально похоже на создание потока) и заменить его заглушкой,
которая точно работает:

withClock (ddr_clock) {
ddr_resp.rdata := 0.U
ddr_resp.is_reading := ddr_req.is_reading
ddr_resp.size := ddr_req.size
ddr_resp.source := ddr_req.source

val will_read = Wire(!fifo_req.io.rdempty && !fifo_resp.io.wrfull)


fifo_req.io.rdreq := will_read
fifo_resp.io.wrreq := RegNext(will_read)
}

Как я уже потом понял, эта заглушка тоже не работала по причине, что конструкция Wire(...), которую
я добавил «для надёжности», чтобы показать, что это именно именованный провод, на самом деле
использовала аргумент лишь как прототип для создания типа своего значения, но не привязывала его
к выражению-аргументу. Также при попытке прочитать, что же всё-таки сгенерировалось, я понял,
что в режиме симуляции имеется богатый выбор assertion-ов по поводу несоблюдения протокола
TileLink. Они мне ещё наверняка пригодятся позже, но пока обошлось без попытки запустить
симуляцию — а в чём её запускать? Verilator наверняка не знает про Alter-овские IP Cores, ModelSim
Starter Edition скорее всего откажется симулировать такой огромный проект, но у меня он ещё и ругался
на отсутствие модели контроллера для симуляции. А чтобы её сгенерировать, наверняка нужно сначала
перейти на новую версию контроллера (потому что старый был настроен в древнем Quartus-е).

На самом деле, блоки кода были взяты из почти работающей версии, а не той, что активно
отлаживалась за несколько часов до этого. Но вам же лучше ;) Кстати, постоянно пересобирать дизайн
можно быстрее, если настройку WithNBigCores(1) заменить на WithNSmallCores(1) — с точки зрения
базовой функциональности контроллера памяти разницы, вроде бы, нет. И ещё маленькая хитрость:
чтобы не вбивать в gdb каждый раз одни и те же команды (там, по крайней мере у меня, нет
сохранения истории команд между сессиями), можно просто сразу в командной строке набрать что-то
вроде

../../rocket-tools/bin/riscv32-unknown-elf-gdb -q -ex "target remote :3333" -ex "x/x 0x8000000


0"
../../rocket-tools/bin/riscv32-unknown-elf-gdb -q -ex "target remote :3333" -ex "set variable *
0x80000000=0x1234"

и запускать по мере надобности штатными средствами командного интерпретатора.

Итог
В итоге был получен такой вот код работы с контроллером:

withClock(ddr_clock) {
val rreq = RegInit(false.B) // запрос чтения (ещё не принят)
val wreq = RegInit(false.B) // запрос записи (ещё не принят)
val rreq_pending = RegInit(false.B) // запрос чтения (ждём данные)

ddr2.io.local_read_req := rreq
ddr2.io.local_write_req := wreq

// какие-то магические константы :)


ddr2.io.local_size := 1.U
ddr2.io.local_burstbegin := true.B

// данные из запроса (надеюсь на буферизованность вывода q FIFO)


ddr2.io.local_address := ddr_req.address
ddr2.io.local_be := ddr_req.be
ddr2.io.local_wdata := ddr_req.wdata

// копируем информацию, какой запрос обслуживаем


ddr_resp.is_reading := ddr_req.is_reading
ddr_resp.size := ddr_req.size
ddr_resp.source := ddr_req.source

// читаем следующий запрос, если готово **вообщё всё**


val will_read_request = !fifo_req.io.rdempty &&
!rreq && !wreq && !rreq_pending && ddr2.io.local_ready
// отвечаем, если есть что сказать
val will_respond = !fifo_resp.io.wrfull &&
( (rreq_pending && ddr2.io.local_rdata_valid) ||
(wreq && ddr2.io.local_ready))
val request_is_read = RegNext(will_read_request)
fifo_req.io.rdreq := will_read_request
fifo_resp.io.wrreq := will_respond

// прочитан запрос, заказанный на предыдущем такте


when (request_is_read) {
rreq := ddr_req.is_reading
rreq_pending := ddr_req.is_reading
wreq := !ddr_req.is_reading
}
when (will_respond) {
rreq := false.B
wreq := false.B
ddr_resp.rdata := ddr2.io.local_rdata
}
// прочитанных данных ещё нет, но запрос ушёл
when (rreq && ddr2.io.local_ready) {
rreq := false.B
}
}

Тут мы всё-таки немного поменяем критерий завершённости: я уже видел, как безо всякой работы с
памятью записанные данные как будто бы читаются, потому что кеш. Поэтому скомпилируем
простенький кусочек кода:

#include <stdint.h>

static volatile uint8_t *x = (uint8_t *)0x80000000u;

void entry()
{
for (int i = 0; i < 1<<24; ++i) {
x[i] = i;
}
}

../../rocket-tools/bin/riscv64-unknown-elf-gcc test.c -S -O1


РЕКОМЕНДУЕМ

В итоге получим следующий фрагмент ассемблерного листинга, инициализирующий первые 16 Мб У программиста из США остались две
памяти: попытки для открытия
самоуничтожающейся флешки. Там
ключ к биткойнам на $237 млн
li a5,1
138k 295
slli a5,a5,31
li a3,129
slli a3,a3,24
Светодиодная лампа Экономка 15 Вт за
.L2:
39 рублей
andi a4,a5,0xff 29,3k 137
sb a4,0(a5)
addi a5,a5,1 Илон Маск продаст все свое
bne a5,a3,.L2 имущество и потратит около $188
млрд для колонизации Марса
64,1k 788
Его и вставим в начала bootrom/xip/leds.S. Теперь на одном лишь кеше вряд ли всё сможет держаться.
Осталось запустить Makefile, пересобрать проект в Quartus, залить его в плату, подключиться
Москва — Берлин — Хельсинки —
OpenOCD+GDB и… Предположительно, ура, победа: Копенгаген за 4 года
17,8k 218
$ ../../rocket-tools/bin/riscv32-unknown-elf-gdb -q -ex "target remote :3333"
Remote debugging using :3333 Все имена персонажей из «Властелина
warning: No executable has been specified and target does not support колец» говорят нам что-то. А что
determining executable automatically. Try using the "file" command. именно?
0x0000000000010014 in ?? ()
41,7k 55
(gdb) x/x 0x80000000
0x80000000: 0x03020100
(gdb) x/x 0x80000100
Скоро будет самая беззащитная
0x80000100: 0x03020100
страна. Системный взгляд на
(gdb) x/x 0x80000111
проблемы информационной
0x80000111: 0x14131211
безопасности в России
(gdb) x/x 0x80010110 37,8k 400
0x80010110: 0x13121110
(gdb) x/x 0x80010120
Не просто цифровизация: IT-
0x80010120: 0x23222120
платформа МКБ для малого и среднего
бизнеса
Мегапост
Так ли это, узнаем в следующей серии (я пока тоже не могу сказать про производительность,
стабильность и т.д.).

Код: AltmemphyDDR2RAM.scala. Редакторский дайджест


Присылаем лучшие статьи раз в месяц
Теги: risc-v, rocketchip, chisel, fpga, fpga intel, fpga altera, ddr, altmemphy
Электропочта
Хабы: Open source, Assembler, Scala, FPGA, DIY или Сделай сам

+16 40 5,4k 2 Поделиться

108,0 0,0
Карма Рейтинг

Анатолий Тросиненко @ atrosinenko


Программист

Сайт ВКонтакте Github

ПОХОЖИЕ ПУБЛИКАЦИИ МИНУТОЧКУ ВНИМАНИЯ

2 сентября 2020 в 19:06

Еще немного RISC-V


+18 6,2k 44 0

7 июля 2019 в 19:14 Мегапост


Часть 3: Почти что грузим Linux с SD-карты на RocketChip
+18 5,5k 46 12
В натуре уязвимость в твоей
инфраструктуре
10 июня 2019 в 13:34

Часть 1: RISC-V / RocketChip в неестественной среде обитания


+22 6,5k 54 0

Мегапост
ВАКАНСИИ
Комбо для сталевара: VR, Java и
Ведущий инженер побольше R&D
от 150 000 ₽ • Миландр • Зеленоград

Scala Senior / Teamlead developer


от 250 000 до 350 000 ₽ • AutoFAQ.ai • Москва • Можно удаленно

Разработчик С /С++
от 110 000 ₽ • VMS Software • Санкт-Петербург • Можно удаленно

Team Lead / Руководитель команды разработки (Java,Scala)


от 170 000 до 300 000 ₽ • WhoIsBlogger • Можно удаленно

Системный программист ОС Linux


от 80 000 до 150 000 ₽ • КА «ЦИБИТ» • Москва

Больше вакансий на Хабр Карьере

Реклама

eleps.ru
ОТКРЫТЬ
Радионож с частотой 2,64 МГц

Комментарии 2 ЧТО ОБСУЖДАЮТ

khim 17 июня 2019 в 03:31 +2 Сейчас Вчера Неделя


Странно, что у вас gdb историю не сохраняет… Точно в .gdbinit написано set history save on ???
Опасные мифы о клещах
14,7k 85

atrosinenko 17 июня 2019 в 09:20 +2


Москва — Берлин — Хельсинки —
О, точно! Спасибо! Но умение выполнять пачку команд с терминала тоже пригодится :) Только там уже,
Копенгаген за 4 года
наверное, лучше будет использовать -x script-file
17,8k 218

Атака на Капитолий не оправдывает


расширение слежки за населением
Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста. 3,6k 28

Олды в ИТ
382 2

Какую систему мобильной аналитики


выбрать для стартапа?
Мегапост

САМОЕ ЧИТАЕМОЕ

Сутки Неделя Месяц

Британец выкинул жесткий диск с четвертью миллиарда долларов в биткоинах и предложил 70


миллионов за поиски
+11 14,8k 4 28

Опасные мифы о клещах


+16 14,7k 39 85

Все имена персонажей из «Властелина колец» говорят нам что-то. А что именно?
+45 41,7k 64 55

Бывшие и настоящие разработчики рассказали о процессе создания Cyberpunk 2077. Игру начали
делать только в 2016 году
+28 16,2k 7 76

Шифровальщик бушует, учётки перебираются, база утекла: что делать? [ТЕСТ]


Мегатест

Ваш аккаунт Разделы Информация Услуги

Войти Публикации Устройство сайта Реклама

Регистрация Новости Для авторов Тарифы

Хабы Для компаний Контент

Компании Документы Семинары

Пользователи Соглашение Мегапроекты

Песочница Конфиденциальность Мерч

© 2006 – 2021 «Habr» Настройка языка О сайте Служба поддержки Мобильная версия